使用来自 PyQt5 循环的项目刷新 QTableView 的内容

Refresh content of QTableView with items comming from loop in PyQt5

我正在开发一个用于管理虚拟环境的小型 GUI。在主要 window 中,我想在 table 视图中显示默认目录中的现有虚拟环境。这到目前为止有效。

现在我注意到,如果我选择不同的默认目录,我必须关闭 GUI 并再次打开它才能看到内容。不幸的是,我在我的计划中没有考虑到这一点(我在Python方面还是有点缺乏经验)。

我想添加一个按钮,您可以使用该按钮更新 table 视图的内容。同时,settings.py中已有的按钮okButton(确认选择标准目录的输入)也应该更新table视图.

我尝试使用 pyqtsignal()pyqtslot(),但我不明白如何将其应用到我的代码中。 table 视图的数据(例如:版本、路径...)来自位于 organize.py 中的循环。项目收集在列表中,然后显示在 table.

如何通过单击按钮刷新视图?我必须修改我的代码结构吗?

以下是代码的最少可重现部分:

如果有需要也可以看看repository here。没有商业背景。


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

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

  • venvs找的函数不应该填一个list,而是return一个list,需要的时候可以调用

  • 你的方法returns the vens有错误,例如它不验证"bin"是否存在,也不要构建与“/”连接的路由",而是使用 os.path.join().

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

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

综合以上,解决方案是:

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