QTableView 不会将预期的 FocusIn / FocusOut 事件发送到 eventFilter
QTableView doesn't send expected FocusIn / FocusOut events to eventFilter
我有一个带有浮动或复杂条目的 QTableWidget,需要大量水平 space。通过字符串格式显示减少位数的值效果很好,但显然我在 table.
中编辑和存储条目时会降低精度
我通过使用事件过滤器找到了 QLineEdit 小部件的解决方案:FocusIn
事件将存储的值完全精确地复制到 QLineEdit 文本字段,FocusOut
事件或 Return_Key
存储更改的值并用减少的位数覆盖文本字段。
对 QTableWidgets 使用相同的方法给我带来了以下(可能相关的)问题:
- FocusIn 和 FocusOut 事件没有按预期生成:当我双击一个项目时,我得到一个 FocusOut 事件,点击另一个项目产生一个 FocusIn 事件
- 我无法复制我编辑过的、选中的项目的内容,我总是得到未编辑的值。
- 通过单击选择项目不会产生事件。
我已尝试评估 QTableWidgetItem 事件,但我没有收到任何事件 - 我是否需要在每个 QTableWidgetItem 上设置事件过滤器?如果是这样,我是否需要在每次调整 table 大小时断开 QTableWidgetItem eventFilters(在我的应用程序中经常这样做)?用 QLineEdit 小部件代替填充我的 table 有意义吗?
附加的 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_())
你的做法完全错了。这些类型的 use-cases 已经由现有的 API 提供,因此有几种可用的解决方案比您目前拥有的要简单得多。
可能最简单的方法是使用 QStyledItemDelegate and reimplement its dispalyText 方法。这将允许您将完整值存储在 table 中,但以不同的格式显示它们。编辑时,将始终显示完整值(作为字符串):
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 来解决这个问题。但是,QTableWidgetItem
和 QStandardItem
都将 DisplayRole
和 EditRole
视为一个角色,这意味着有必要重新实现它们的 data
和 setData
方法来获得所需的功能。
我有一个带有浮动或复杂条目的 QTableWidget,需要大量水平 space。通过字符串格式显示减少位数的值效果很好,但显然我在 table.
中编辑和存储条目时会降低精度我通过使用事件过滤器找到了 QLineEdit 小部件的解决方案:FocusIn
事件将存储的值完全精确地复制到 QLineEdit 文本字段,FocusOut
事件或 Return_Key
存储更改的值并用减少的位数覆盖文本字段。
对 QTableWidgets 使用相同的方法给我带来了以下(可能相关的)问题:
- FocusIn 和 FocusOut 事件没有按预期生成:当我双击一个项目时,我得到一个 FocusOut 事件,点击另一个项目产生一个 FocusIn 事件
- 我无法复制我编辑过的、选中的项目的内容,我总是得到未编辑的值。
- 通过单击选择项目不会产生事件。
我已尝试评估 QTableWidgetItem 事件,但我没有收到任何事件 - 我是否需要在每个 QTableWidgetItem 上设置事件过滤器?如果是这样,我是否需要在每次调整 table 大小时断开 QTableWidgetItem eventFilters(在我的应用程序中经常这样做)?用 QLineEdit 小部件代替填充我的 table 有意义吗?
附加的 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_())
你的做法完全错了。这些类型的 use-cases 已经由现有的 API 提供,因此有几种可用的解决方案比您目前拥有的要简单得多。
可能最简单的方法是使用 QStyledItemDelegate and reimplement its dispalyText 方法。这将允许您将完整值存储在 table 中,但以不同的格式显示它们。编辑时,将始终显示完整值(作为字符串):
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 来解决这个问题。但是,QTableWidgetItem
和 QStandardItem
都将 DisplayRole
和 EditRole
视为一个角色,这意味着有必要重新实现它们的 data
和 setData
方法来获得所需的功能。