PyQt5中从循环中获取项目刷新QTableView内容

3
我正在开发一个用于管理虚拟环境的小型 GUI。在主窗口中,我想要显示来自默认目录的现有虚拟环境并以表格视图呈现。到目前为止,这个功能已经实现了。

现在我注意到,如果我选择其他默认目录,我必须关闭 GUI 然后再次打开才能看到内容。不幸的是,在我的计划中没有考虑到这一点(我在 Python 中还有些经验不足)。

我想要添加一个按钮,通过该按钮可以更新表格视图的内容。同时,在 settings.py 中的现有按钮 `okButton`(它会确认所选标准目录的输入)也应该更新表格视图。

我尝试使用 `pyqtsignal()` 和 `pyqtslot()`,但我不知道如何将其应用到我的代码中。表格视图的数据(例如版本、路径等)来自于 organize.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_())

1个回答

3

您的代码存在以下错误或问题:

  • Venvs查找的函数不应填充列表,而应返回列表,以便在需要时调用它

  • 返回vens的方法存在错误,例如它没有验证“bin”是否存在,还未使用os.path.join()来连接路径。

  • 不要使用相对路径,而要构建绝对路径。

  • 创建一个存储venv信息的数据结构。

考虑到上述问题,解决方案为:

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_())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接