QTableView doesn't send expected FocusIn / FocusOut events to eventFilter

左心房为你撑大大i 提交于 2019-12-11 04:25:24

问题


I have a QTableWidget with floats or complex entries that need a lot of horizontal space. Displaying the values with reduced number of digits via string formatting works fine, but obviously I loose precision when editing and storing entries in the table.

I have found a solution for QLineEdit widgets by using an eventFilter: A FocusIn event copies the stored value with full precision to the QLineEdit textfield, a FocusOut event or a Return_Key stores the changed value and overwrites the text field with reduced number of digits.

Using the same approach with a QTableWidgets gives me the following (possibly related) problems:

  • FocusIn and FocusOut events are not generated as expected: When I double-click on an item, I get a FocusOut event, clicking on another item produces a FocusIn event
  • I can't copy the content of my edited, selected item, I always get the unedited value.
  • Selecting an item by clicking on it doesn't produce an event.

I've tried evaluating QTableWidgetItem events, but I don't receive any - do I need to setup an event filter on every QTableWidgetItem? If so, do I need to disconnect the QTableWidgetItem eventFilters every time I resize the table (which do frequently in my application)? Would it make sense to populate my table with QLineEdit widgets instead?

The attached MWE is not exactly small, but I could shrink it any further.

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

回答1:


You're going about this in completely the wrong way. These kinds of use-cases are already catered for by the existing APIs, so there are several solutions available that are much simpler than what you currently have.

Probably the simplest of all would be to use a QStyledItemDelegate and reimplement its dispalyText method. This will allow you to store the full values in the table, but format them differently for display. When editing, the full value will always be shown (as a string):

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

NB: it's tempting to use item roles to solve this issue. However, both QTableWidgetItem and QStandardItem treat the DisplayRole and EditRole as one role, which means it would be necessary to reimplement their data and setData methods to get the required functionality.



来源:https://stackoverflow.com/questions/41852285/qtableview-doesnt-send-expected-focusin-focusout-events-to-eventfilter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!