Reputation: 1495
I am working on a small GUI for managing virtual environments. In the main window, I want to display existing virtual environments from a default directory in a table view. This works so far.
Now I've noticed that if I choose a different default directory, I have to close the GUI and open it again to see the content. Unfortunately, I did not consider that in my plans (I'm still a bit inexperienced in Python).
I would like to add a button with which you can update the contents of the table view. At the same time, the existing button okButton
in settings.py (which confirms the input of the selected standard directory) should also update the table view.
I tried to use pyqtsignal()
and pyqtslot()
, but I don't understand how to apply that to my code. The data (for example: version, path ...) for the table view comes from a loop located in organize.py. The items are collected in a list and then displayed in the table.
How can I refresh the view with a button click? Do I have to revise my code structure?
Here are the minimal reproducable parts of the code:
You can also take look at the repository here if you want. There is no commercial background.
main_ui.py
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import organize
import settings
class Ui_MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setupUi()
def setupUi(self):
self.selectDefaultDir = settings.SetDefaultDirectory()
self.setWindowTitle("MainWindow")
self.setGeometry(430, 335, 750, 330)
centralwidget = QWidget(self)
self.v_Layout_1 = QVBoxLayout()
self.v_Layout_2 = QVBoxLayout(centralwidget)
selectButton = QPushButton(
"Set default dir", clicked=self.selectButton_clicked
)
# venv table
venvTable = QTableView(centralwidget)
venvTable.verticalHeader().setVisible(False)
venvTable.setSelectionBehavior(QAbstractItemView.SelectRows)
venvTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
venvTable.setAlternatingRowColors(True)
# adjust vertical headers
v_HeaderTV2 = venvTable.verticalHeader()
v_HeaderTV2.setVisible(False)
v_HeaderTV2.setDefaultSectionSize(27.5)
# adjust (horizontal) headers
h_HeaderTV2 = venvTable.horizontalHeader()
h_HeaderTV2.setDefaultAlignment(Qt.AlignLeft)
h_HeaderTV2.setDefaultSectionSize(180)
h_HeaderTV2.setStretchLastSection(True)
# set table view model
self.modelTV2 = QStandardItemModel(centralwidget)
self.modelTV2.setColumnCount(3)
self.modelTV2.setHorizontalHeaderLabels(
["Venv Name", "Version", "Path"]
)
venvTable.setModel(self.modelTV2)
self.v_Layout_1.addWidget(venvTable)
self.v_Layout_1.addWidget(selectButton)
self.v_Layout_2.addLayout(self.v_Layout_1)
self.setCentralWidget(centralwidget)
def popVenvTable(self):
"""
Populate the venv table view.
"""
for i in range(len(organize.venvDirs)):
self.modelTV2.insertRow(0)
self.modelTV2.setItem(0, 0, QStandardItem(organize.venvDirs[i]))
self.modelTV2.setItem(0, 1, QStandardItem(organize.venvVers[i]))
self.modelTV2.setItem(0, 2, QStandardItem(organize.venvPath[i]))
def selectButton_clicked(self):
self.selectDefaultDir.exec_()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
ui = Ui_MainWindow()
ui.popVenvTable()
ui.show()
sys.exit(app.exec_())
organize.py
# -*- coding: utf-8 -*-
from subprocess import Popen, PIPE
import os
#]===========================================================================[#
#] GET VENVS FROM DEFAULT DIRECTORY [#=======================================[#
#]===========================================================================[#
venvDirs, venvVers, venvPath = [], [], []
def getVenvs():
"""
Get the sub directories (venv directories) from the default directory.
"""
# get the path (str) to the default dir from file
with open("def/default", 'r') as default:
defDir = default.read()
default.close()
# get all folders inside the selected default dir
subDirs = os.listdir(defDir)
# loop over the subdirs of the selected default dir
for i, _dir in enumerate(subDirs):
# if there's a 'bin' folder within the subdir, and if it contains a
# file named 'python', then try to get the version
if ("bin" in os.listdir('/'.join([defDir, _dir]))
and "python" in os.listdir('/'.join([defDir, _dir, "bin"]))):
try:
getVers = Popen(
['/'.join([defDir, _dir, "bin", "python"]), "-V"],
stdout=PIPE, universal_newlines=True
)
venvVersion = getVers.communicate()[0].strip()
except Exception as err:
# in case there's a file named 'python' but
# isn't a python executable
print(
err.args[1]+':',
"[list index:", str(i)+']',
'/'.join([defDir, _dir, "bin"])
)
continue
venvDirs.append(_dir)
venvVers.append(venvVersion)
venvPath.append(defDir)
getVenvs()
if __name__ == "__main__":
for i in range(len(venvDirs)):
print(venvDirs[i])
print(venvVers[i])
print(venvPath[i])
settings.py
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class SetDefaultDirectory(QDialog):
"""
Set the default directory, where to look for virtual environments.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#]===================================================================[#
#] WINDOW SETTINGS [#================================================[#
#]===================================================================[#
self.setWindowTitle("Set Default Directory")
self.setGeometry(600, 365, 500, 100)
self.setFixedSize(500, 100)
v_Layout = QVBoxLayout(self)
h_Layout = QHBoxLayout()
gridLayout = QGridLayout()
defaultDirLabel = QLabel("Default Venv Directory:")
self.defaultDirLineEdit = QLineEdit()
defaultDirLabel.setBuddy(self.defaultDirLineEdit)
folder_icon = QIcon.fromTheme("folder")
selectDirToolButton = QToolButton(
toolTip="Browse",
clicked=self.selectDirTButton_clicked
)
selectDirToolButton.setFixedSize(26, 27)
selectDirToolButton.setIcon(folder_icon)
horizontalLine = QFrame()
horizontalLine.setFrameShape(QFrame.HLine)
horizontalLine.setFrameShadow(QFrame.Sunken)
cancelButton = QPushButton(
"Cancel", clicked=self.close
)
okButton = QPushButton(
"OK", clicked=self.okButton_clicked
)
spacerItem = QSpacerItem(
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
)
gridLayout.addWidget(defaultDirLabel, 0, 0, 1, 1)
gridLayout.addWidget(self.defaultDirLineEdit, 0, 1, 1, 1)
gridLayout.addWidget(selectDirToolButton, 0, 2, 1, 1)
h_Layout.addItem(spacerItem)
h_Layout.addWidget(okButton, 0, Qt.AlignBottom)
h_Layout.addWidget(cancelButton, 0, Qt.AlignBottom)
v_Layout.addLayout(gridLayout)
v_Layout.addWidget(horizontalLine)
v_Layout.addLayout(h_Layout)
def selectDirTButton_clicked(self):
"""
Select directory which should be set as default.
"""
fileDiag = QFileDialog()
directory = fileDiag.getExistingDirectory()
self.defaultDirLineEdit.setText(directory)
def okButton_clicked(self):
"""
Store the absolute path (as str) to the selected dir in 'def/default'.
"""
with open("def/default", 'w') as default:
default.write(self.defaultDirLineEdit.text())
default.close()
self.close()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
settingsUI = SetDefaultDirectory()
settingsUI.show()
sys.exit(app.exec_())
Upvotes: 3
Views: 1464
Reputation: 243947
Your code has the following errors or problems:
The function that the venvs look for should not fill a list but return a list, so you can call it whenever you need it
Your method that returns the vens has errors like for example it does not verify if "bin" exists or not, also do not build the routes joining with "/", instead it uses os.path.join().
Do not use relative paths but build absolute paths.
Create a data structure that stores the information of the venvs
Considering the above, the solution is:
main_ui.py
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
import organize
import settings
class Ui_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setupUi()
def setupUi(self):
self.selectDefaultDir = settings.SetDefaultDirectory()
self.setWindowTitle("MainWindow")
self.setGeometry(430, 335, 750, 330)
centralwidget = QtWidgets.QWidget(self)
self.v_Layout_1 = QtWidgets.QVBoxLayout()
self.v_Layout_2 = QtWidgets.QVBoxLayout(centralwidget)
selectButton = QtWidgets.QPushButton(
"Set default dir", clicked=self.selectButton_clicked
)
# venv table
venvTable = QtWidgets.QTableView(
centralwidget,
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows,
editTriggers=QtWidgets.QAbstractItemView.NoEditTriggers,
alternatingRowColors=True,
)
# adjust vertical headers
v_HeaderTV2 = venvTable.verticalHeader()
v_HeaderTV2.hide()
v_HeaderTV2.setDefaultSectionSize(27.5)
# adjust (horizontal) headers
h_HeaderTV2 = venvTable.horizontalHeader()
h_HeaderTV2.setDefaultAlignment(QtCore.Qt.AlignLeft)
h_HeaderTV2.setDefaultSectionSize(180)
h_HeaderTV2.setStretchLastSection(True)
# set table view model
self.modelTV2 = QtGui.QStandardItemModel(0, 3, centralwidget)
self.modelTV2.setHorizontalHeaderLabels(["Venv Name", "Version", "Path"])
venvTable.setModel(self.modelTV2)
self.v_Layout_1.addWidget(venvTable)
self.v_Layout_1.addWidget(selectButton)
self.v_Layout_2.addLayout(self.v_Layout_1)
self.setCentralWidget(centralwidget)
def popVenvTable(self):
"""
Populate the venv table view.
"""
self.modelTV2.setRowCount(0)
for info in organize.get_venvs_default():
self.modelTV2.insertRow(0)
for i, text in enumerate((info.name, info.version, info.directory)):
self.modelTV2.setItem(0, i, QtGui.QStandardItem(text))
print(info)
def selectButton_clicked(self):
if self.selectDefaultDir.exec_() == QtWidgets.QDialog.Accepted:
self.popVenvTable()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = Ui_MainWindow()
ui.popVenvTable()
ui.show()
sys.exit(app.exec_())
organize.py
# -*- coding: utf-8 -*-
import os
from subprocess import Popen, PIPE
from dataclasses import dataclass
@dataclass
class VenvInfo:
name: str
directory: str
version: str
def get_venvs(path):
if not os.path.isdir(path):
return []
infos = []
for i, _dir in enumerate(os.listdir(path)):
bin_folder = os.path.join(path, _dir, "bin")
if not os.path.isdir(bin_folder):
continue
python_binary = os.path.join(bin_folder, "python")
if not os.path.isfile(python_binary):
continue
try:
res = Popen([python_binary, "-V"], stdout=PIPE, universal_newlines=True)
out, _ = res.communicate()
version = out.strip()
info = VenvInfo(_dir, path, version)
infos.append(info)
except Exception as err:
print(f"{err.args[1]} : [list index: {i} ] {python_binary}")
return infos
def get_venvs_default():
current_dir = os.path.dirname(os.path.realpath(__file__))
default_file = os.path.join(current_dir, "def", "default")
if os.path.isfile(default_file):
with open(default_file, "r") as f:
default_dir = f.read()
return get_venvs(default_dir)
return []
if __name__ == "__main__":
for venv in get_venvs_default():
print(venv.name, venv.version, venv.directory)
settings.py
# -*- coding: utf-8 -*-
import os
from PyQt5 import QtCore, QtGui, QtWidgets
class SetDefaultDirectory(QtWidgets.QDialog):
"""
Set the default directory, where to look for virtual environments.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# ]===================================================================[#
# ] WINDOW SETTINGS [#================================================[#
# ]===================================================================[#
self.setWindowTitle("Set Default Directory")
self.move(600, 365)
self.setFixedSize(500, 100)
v_Layout = QtWidgets.QVBoxLayout(self)
h_Layout = QtWidgets.QHBoxLayout()
gridLayout = QtWidgets.QGridLayout()
defaultDirLabel = QtWidgets.QLabel("Default Venv Directory:")
self.defaultDirLineEdit = QtWidgets.QLineEdit()
defaultDirLabel.setBuddy(self.defaultDirLineEdit)
folder_icon = QtGui.QIcon.fromTheme("folder")
selectDirToolButton = QtWidgets.QToolButton(
toolTip="Browse", clicked=self.selectDirTButton_clicked, icon=folder_icon
)
selectDirToolButton.setFixedSize(26, 27)
horizontalLine = QtWidgets.QFrame(
frameShape=QtWidgets.QFrame.HLine, frameShadow=QtWidgets.QFrame.Sunken
)
cancelButton = QtWidgets.QPushButton("Cancel", clicked=self.reject)
okButton = QtWidgets.QPushButton("OK", clicked=self.okButton_clicked)
gridLayout.addWidget(defaultDirLabel, 0, 0, 1, 1)
gridLayout.addWidget(self.defaultDirLineEdit, 0, 1, 1, 1)
gridLayout.addWidget(selectDirToolButton, 0, 2, 1, 1)
h_Layout.addStretch()
h_Layout.addWidget(okButton, 0, QtCore.Qt.AlignBottom)
h_Layout.addWidget(cancelButton, 0, QtCore.Qt.AlignBottom)
v_Layout.addLayout(gridLayout)
v_Layout.addWidget(horizontalLine)
v_Layout.addLayout(h_Layout)
def selectDirTButton_clicked(self):
"""
Select directory which should be set as default.
"""
directory = QtWidgets.QFileDialog.getExistingDirectory()
self.defaultDirLineEdit.setText(directory)
def okButton_clicked(self):
"""
Store the absolute path (as str) to the selected dir in 'def/default'.
"""
current_dir = os.path.dirname(os.path.realpath(__file__))
default_file = os.path.join(current_dir, "def", "default")
with open(default_file, "w") as default:
default.write(self.defaultDirLineEdit.text())
self.accept()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
settingsUI = SetDefaultDirectory()
settingsUI.show()
sys.exit(app.exec_())
Upvotes: 3