python - QTableView 不会将预期的 FocusIn/FocusOut 事件发送到 eventFilter

标签 python pyqt qtablewidget qtablewidgetitem qevent

我有一个带有 float 或复杂条目的 QTableWidget,需要大量水平空间。通过字符串格式显示减少位数的值效果很好,但显然在编辑和存储表中的条目时我失去了精度。

我通过使用事件过滤器找到了 QLineEdit 小部件的解决方案:FocusIn 事件将存储的值完全精确地复制到 QLineEdit 文本字段、FocusOut 事件或Return_Key 存储更改的值并用减少的位数覆盖文本字段。

对 QTableWidgets 使用相同的方法会给我带来以下(可能相关)问题:

  • FocusIn 和 FocusOut 事件未按预期生成:当我双击某个项目时,会收到 FocusOut 事件,单击另一个项目会生成 FocusIn 事件
  • 我无法复制已编辑的选定项目的内容,我始终获得未编辑的值。
  • 通过单击来选择某个项目不会产生事件。

我尝试评估 QTableWidgetItem 事件,但我没有收到任何事件 - 我是否需要在每个 QTableWidgetItem 上设置事件过滤器?如果是这样,每次调整表格大小时是否需要断开 QTableWidgetItem eventFilters 的连接(这在我的应用程序中经常发生)?使用 QLineEdit 小部件填充我的表格是否有意义?

附加的 MWE 并不小,但我可以进一步缩小它。

# -*- coding: utf-8 -*-
#from PyQt5.QWidgets import ( ...)
from PyQt4.QtGui import (QApplication, QWidget, QTableWidget, QTableWidgetItem, 
                         QLabel, QVBoxLayout)
import PyQt4.QtCore as QtCore
from PyQt4.QtCore import QEvent
from numpy.random import randn

class EventTable (QWidget):
    def __init__(self, parent = None):
        super(EventTable, self).__init__(parent)
        self.myTable = QTableWidget(self)
        self.myTable.installEventFilter(self) # route all events to self.eventFilter()

        myQVBoxLayout = QVBoxLayout()
        myQVBoxLayout.addWidget(self.myTable)
        self.setLayout(myQVBoxLayout)

        self.rows = 3; self.columns = 4 # table + data dimensions
        self.data = randn(self.rows, self.columns) # initial data
        self._update_table() # create table 

    def eventFilter(self, source, event):
        if isinstance(source, (QTableWidget, QTableWidgetItem)):
#            print(type(source).__name__, event.type()) #too much noise
            if event.type() == QEvent.FocusIn:  # 8: enter widget
                print(type(source).__name__, "focus in")
                self.item_edited = False
                self._update_table_item() # focus: display data with full precision
                return True # event processing stops here

            elif event.type() == QEvent.KeyPress:
                print(type(source).__name__, "key pressed")
                self.item_edited = True # table item has been changed
                key = event.key() # key press: 6, key release: 7
                if key in {QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter}: # store entry
                    self._store_item() # store edited data in self.data
                    return True
                elif key == QtCore.Qt.Key_Escape: # revert changes
                    self.item_edited = False
                    self._update_table() # update table from self.data
                    return True

            elif event.type() == QEvent.FocusOut: # 9: leave widget
                print(type(source).__name__, "focus out")
                self._store_item() 
                self._update_table_item() # no focus: use reduced precision
                return True

        return super(EventTable, self).eventFilter(source, event)

    def _update_table(self):
        """(Re-)Create the table with rounded data from self.data """
        self.myTable.setRowCount(self.rows)
        self.myTable.setColumnCount(self.columns)
        for col in range(self.columns):
            for row in range(self.rows):
                self.myTable.setItem(row,col, 
                        QTableWidgetItem(str("{:.3g}".format(self.data[row][col]))))
        self.myTable.resizeColumnsToContents()
        self.myTable.resizeRowsToContents()

    def _update_table_item(self, source = None):
        """ Re-)Create the current table item with full or reduced precision. """
        row = self.myTable.currentRow()
        col = self.myTable.currentColumn()
        item = self.myTable.item(row, col)
        if item: # is something selected?
            if not item.isSelected(): # no focus, show self.data[row][col] with red. precision
                print("\n_update_item (not selected):", row, col)
                item.setText(str("{:.3g}".format(self.data[row][col])))
            else: #  in focus, show self.data[row][col] with full precision
                item.setText(str(self.data[row][col]))
                print("\n_update_item (selected):", row, col)

    def _store_item(self):
        """ Store the content of item in self.data """
        if self.item_edited:
            row = self.myTable.currentRow()
            col = self.myTable.currentColumn()
            item_txt = self.myTable.item(row, col).text()
            self.data[row][col] = float(str(item_txt))
            print("\n_store_entry - current item/data:", item_txt, self.data[row][col])



if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    mainw = EventTable()
    app.setActiveWindow(mainw) 
    mainw.show()
    sys.exit(app.exec_())

最佳答案

你的处理方式完全错误。现有的 API 已经满足了这些类型的用例,因此有几种可用的解决方案比您当前拥有的简单得多。

可能最简单的是使用 QStyledItemDelegate并重新实现其dispalyText方法。这将允许您将完整值存储在表中,但以不同的方式显示它们。编辑时,将始终显示完整值(作为字符串):

from PyQt4.QtGui import (QApplication, QWidget, QTableWidget, QTableWidgetItem,
                         QLabel, QVBoxLayout,QStyledItemDelegate)
import PyQt4.QtCore as QtCore
from PyQt4.QtCore import QEvent
from numpy.random import randn

class ItemDelegate(QStyledItemDelegate):
    def displayText(self, text, locale):
        return "{:.3g}".format(float(text))

class EventTable (QWidget):
    def __init__(self, parent = None):
        super(EventTable, self).__init__(parent)
        self.myTable = QTableWidget(self)
        self.myTable.setItemDelegate(ItemDelegate(self))
        myQVBoxLayout = QVBoxLayout()
        myQVBoxLayout.addWidget(self.myTable)
        self.setLayout(myQVBoxLayout)
        self.rows = 3; self.columns = 4 # table + data dimensions
        self.data = randn(self.rows, self.columns) # initial data
        self._update_table() # create table

    def _update_table(self):
        self.myTable.setRowCount(self.rows)
        self.myTable.setColumnCount(self.columns)
        for col in range(self.columns):
            for row in range(self.rows):
                item = QTableWidgetItem(str(self.data[row][col]))
                self.myTable.setItem(row, col, item)
        self.myTable.resizeColumnsToContents()
        self.myTable.resizeRowsToContents()

if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    mainw = EventTable()
    app.setActiveWindow(mainw)
    mainw.show()
    sys.exit(app.exec_())
<小时/>

注意:使用 item roles 很诱人来解决这个问题。但是,QTableWidgetItemQStandardItem 都将 DisplayRoleEditRole 视为一个角色,这意味着有必要重新实现其 datasetData 方法以获得所需的功能。

关于python - QTableView 不会将预期的 FocusIn/FocusOut 事件发送到 eventFilter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41852285/

相关文章:

python - matplotlib 文本标题未出现

python - 具有不同 x 和 y 误差的 Pyplot 误差线

python - 如何保留尾随零?

python - 如何在 PyQt 中创建操纵杆/ Controller 小部件?

python-3.x - 如何验证 QTableWidget 中的单元格?

python - 如何加速 HTTP 请求

python - PyQt5 - 组合框中有条件的颜色字段 - qsqltablemodel

python - PyQt5安装错误(QtCore模块错误: Unable to create the C++ code)

qt - 如何更改 QTableWidget 中空白单元格的背景颜色

python - PySide/PyQt 中 QTableWidget 的 QMenu