QTableView 在 PyQt5/PySide2 中的每个选择上加载数据时锁定主窗体 [重复]

Posted

技术标签:

【中文标题】QTableView 在 PyQt5/PySide2 中的每个选择上加载数据时锁定主窗体 [重复]【英文标题】:QTableView locks the main form while loading data on each select in PyQt5/PySide2 [duplicate] 【发布时间】:2022-01-18 02:36:13 【问题描述】:

我使用每 2 秒进行一次选择的循环将数据加载到 qtableview 中。碰巧在选择运行时屏幕被锁定并且数据被加载到视图中,每次选择都会带来更新的数据,但我无法单击被锁定的单选按钮。我不知道如何实现一种使用 dataChanged.emit() 更新视图的方法,也许我什至不需要运行 select 这么多次,我用英文查找了一些信息但我听不懂我可以实施的方式。我想要的是它不会锁定,这样我就可以通过视图数据(服务器可用)的可视化来标记单选按钮以更新服务器。

https://i.stack.imgur.com/pfikz.png

模型类代码:

class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        
    def data(self, index, role):
        if role == Qt.TextAlignmentRole:
            value = self._data[index.row()][index.column()]

            if isinstance(value, int) or isinstance(value, float):
                return Qt.AlignHCenter

        if role == Qt.DisplayRole:

            return self._data[index.row()][index.column()]


    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

主类代码:

class Ui_Form(object):
    def setupUi(self, Form):

    self.pushButton_query.clicked.connect(self.executa_query)
    
    def executa_query(self):
        flag = 0
        while flag <= execucao:
            self.select_dados()
            time.sleep(segundos)
            flag += 1
            if flag == execucao:
                self.status_execucao = True

    def select_dados(self):
        dados = []
        sql = """
              SELECT * FROM(
                SELECT (SELECT COUNT(1)
                          FROM GUIA_ITEM G
                         WHERE G.GUIA_COD_ID = ID_GUIA),
                        S.*
                  FROM SAPIA_LOG S
                 WHERE DATA > TRUNC(SYSDATE) - 1
                 ORDER BY 2 DESC)
               WHERE ROWNUM <= 20
              """
        self.cur.execute(sql)
        for x in self.cur:
            dados.append([int(x[0]),int(x[2]),int(x[2]),x[3].strftime("%d/%m/%Y %H:%M:%S"),x[4],x[5],x[6],x[7]])

        self.model = TableModel(dados)
        self.tableView.setModel(self.model)
        self.tableView.resizeColumnsToContents()
        self.tableView.resizeRowsToContents()
        QApplication.processEvents()

【问题讨论】:

阻塞函数应该永远在 UI 环境中使用,因为它们会阻塞事件循环。如果要定时调用函数,请使用QTimer。也就是说,不要使用自定义模型进行查询,而是考虑QSqlQueryModel。最后,编辑 pyuic 生成的文件被认为是不好的做法。请参阅有关using Designer的官方指南 谢谢@musicamante,我真的用过QTimer,它工作得很好,再次感谢您的提示,我尝试为Oracle(QOCI)生成驱动程序,但我不能,多次尝试错误我无法解决的编译,所以我使用 cx_Oracle。我还将阅读有关使用 qtdesigner 的指南,并按照最佳实践重写代码。 【参考方案1】:

这是因为只使用了一个线程。单线程不能同时处理 GUI 和循环任务。所以你应该使用 QThread 创建另一个线程。这是一个例子。

from PyQt5.QtCore import QThread, pyqtSignal, QAbstractTableModel
from PyQt5.QtWidgets import QPushButton

class Worker(QThread):
    # pyqtSignal(type)
    progress_signal = pyqtSignal()
    finished = pyqtSignal()
  
    def __init__(self, parent=None):
      super(Worker, self).__init__(parent)

    def run(self):
      # Do Task
      main_class = MainClass()
      something = main_class.do_something()
      self.progress_signal.emit(something)
      # End
      self.finished.emit()
    
class MainClass():
    def do_something(self):
      return something

class GUIClass(QAbstractTableModel):
    def __init__(self):
        super().__init__()
        self.ui()
    def ui(self):
        # add layout ...
        # ...

        self.pushButton_query = QPushButton()
        self.pushButton_query.clicked.connect(self.main)

    def main(self):
      self.thread = QThread(parent=self)
      self.worker = Worker()
      self.worker.moveToThread(self.thread)
      self.thread.start()

      self.thread.started.connect(self.worker.run)
      self.thread.finished.connect(self.thread.deleteLater)
      self.worker.finished.connect(self.worker.deleteLater)

      self.worker.progress_signal(self.result_from_worker)
      # or use lambda
    
      self.worker.finished.connect(lambda: self.something)
      self.thread.quit()
      # self.thread.exit()

    def result_from_worker(self):
        # do something
        pass  

【讨论】:

一些重要的注意事项: 1.progress_signal是一个信号,它是不可调用的,所以self.worker.progress_signal(...)会抛出一个execption(应该是self.worker.progress_signal.connect(...)); 2. 项目模型不应该负责创建或“拥有”小部件; 3. 您的导入缺少 QAbstractTableModel 和 QPushButton; 4.QThread.quit()等同于QThread.exit(); 5.连接到finished的lambda缺少self.something的括号,但由于它没有参数,使用lambda是没有意义的,只需self.worker.finished.connect(self.something); 6.在启动线程后立即退出线程确实不是一个好主意,因为它可能会阻止连接的函数实际处理; 7. 除非实际需要事件循环,否则从 QThread 子类化通常被认为更好更容易(至少对于 Python 而言):它们不是相同的,这是需要考虑的一个重要方面; 8. 覆盖(可能)先前设置的实例属性的对象的创建可能导致崩溃或至少导致线程不工作,因为同时目标已被删除; 9.标准缩进使用4个空格; @musicamante 谢谢大家的热情回答 :-) 这是我的第一个回答。我没有使用 IDE,只是为这个答案输入了它。所以有很多错误。我会仔细考虑这些 cmets :) 没有害处,我只是为了保证答案的质量而指出问题:-) 从头脑中编写整个代码没有问题(相反,这是一个很好的练习! ),但您也需要格外小心,因为您没有使用可以帮助您发现问题的 IDE,并且由于问题和答案的放置方式,您可能无论如何都无法测试代码。请记住,您可以随时edit您的答案随时,并通过考虑以上几点来改进它。

以上是关于QTableView 在 PyQt5/PySide2 中的每个选择上加载数据时锁定主窗体 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PyQt5 / PySide2 中从一个类访问属性到另一个类

是否可以在 PyQt/PySide2 中为 QLineEdit 的文本制作“破碎”边框

能够在 PyQt5 中一次打开多个对话框的单窗口模式?

PyQt4 - QTableView - 如何循环 QTableView

QTableView() 仅在选择时更新更改

如何在调整 QTableView 大小时动态更改列数?