我创建了一个gui,应该允许它在QLineEdit中写一些注释。如果我不使用自定义方法,而只是通过基本的QTableView显示数据,则可以正常看到所有内容,但是当我开始使用自定义方法(由于日期,组合框等)时,除QLineEdit外,每个小部件都可以正常工作,不会显示模型数据,并且拒绝显示我写入的任何数据(尽管在编辑过程中我可以看到输入的内容)。
我为此进行了一个多星期的尝试,尝试了许多不同的方法(并创建了更多的错误)。我目前的怀疑是由于某种原因我没有与正确的QLineEdit小部件进行交互,并且我的代码以某种方式在连接到模型的那一列的顶部另外创建了一个。 尽管如此,我还是通过遵循基本示例来创建此代码的,但我无法看到问题出在哪里,也无法调试,因为python调试器会跳过所有默认实现(因为它们是C ++,我想)。
最小的示例仍然有点胖,因此我将其放入文件中:download
我只希望第二栏小部件显示文本:
简而言之,他们应该始终显示文本。
只需运行并观察第二列(也尝试进行编辑)。 TableView设置负责小部件交互的委托并管理模型。我希望问题出在TableView和Delegate之间。
编辑:有人要求我粘贴简约示例而不是文件,所以就在这里。
#!/usr/bin/env python3
from collections import OrderedDict
from PyQt5 import QtCore, QtWidgets
import pandas as pd
def validate(value, expected_type):
""" Returns an object of expected_type, with the passed value if possible, the default value otherwise. """
if type(value) == QtCore.QVariant:
value = None if value.isNull() or not value.isValid() else value.value()
value = expected_type() if value is None else expected_type(str(value))
return value
class ComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
super().__init__(parent)
def data(self):
return self.currentIndex()
def set_data(self, value):
print("ComboBox received {}".format(type(value)))
print("value {}".format(value))
value = int(validate(value, float))
print("valid value {}".format(value))
self.setCurrentIndex(value)
self.update()
class InfoTypeSelector(ComboBox):
def __init__(self, parent=None):
super().__init__(parent)
self.addItems(["A1", "A2", "A3", "A4"])
class DateEdit(QtWidgets.QDateEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setCalendarPopup(True)
def data(self):
return self.date().toString()
def set_data(self, value):
print("DateEdit received {}".format(type(value)))
print("value {}".format(value))
value = validate(value, str)
print("valid value {}".format(value))
self.setDate(QtCore.QDate.fromString(value))
self.update()
class TimeEdit(QtWidgets.QTimeEdit):
def data(self):
return self.time().toString()
def set_data(self, value):
print("TimeEdit received {}".format(type(value)))
print("value {}".format(value.value()))
value = validate(value, str)
print("valid value {}".format(value))
self.setTime(QtCore.QTime.fromString(value))
self.update()
class LineEdit(QtWidgets.QLineEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setAlignment(QtCore.Qt.AlignLeft)
def data(self):
return str(self.text())
def set_data(self, value):
print("LineEdit received {}".format(type(value)))
print("value {}".format(value))
value = validate(value, str)
print("valid value {}".format(value))
self.setText(value)
self.update()
def displayText(self):
print("dysplaying: {}".format(super().displayText()))
return super().displayText()
def setText(self, value):
print("updating: {}".format(value))
super().setText(value)
class CheckBox(QtWidgets.QCheckBox):
def data(self):
return self.checkState()
def set_data(self, value):
print("CheckBox received {}".format(type(value)))
print("value {}".format(value))
value = validate(value, bool)
print("valid value {}".format(value))
self.setCheckState(value)
self.update()
class Delegate(QtWidgets.QStyledItemDelegate):
""" This handles widget creation, allowing to pass a dataframe and a 2d index on construction. """
def __init__(self, factories, model):
""" data is the dataframe used for initialisation of the widgets, as well as to store the widget values
so that they can be serialized.
factories is a list of widget generators (their type), indexed by the column index. """
super().__init__()
self.factories = factories
self.model = model
def createEditor(self, parent, option, index):
if self.factories[index.column()] == str:
return super().createEditor(parent, option, index)
value = self.model.data(index)
widget = self.factories[index.column()](parent)
widget.set_data(value)
return widget
def setModelData(self, widget, model, index):
if self.factories[index.column()] == str:
super().setModelData(widget, model, index)
return
self.model.setData(index, widget.data())
class TableView(QtWidgets.QTableView):
def __init__(self, name, dataframe, gui_column_types, default_column_values, parent=None, editable=False):
""" dataframe holds the serializable data that store the internal state of the widgets in guiframe.
gui_column_types is a list of types, ordered as the columns of guiframe should be. """
super().__init__(parent)
self.setSortingEnabled(True)
self.setObjectName(name)
self.editable = editable
self.gui_column_types = gui_column_types
self.default_column_values = default_column_values
self.model = SerializableModel(dataframe, default_column_values, self, editable=self.editable)
# Delegate Setup
delegate = Delegate(gui_column_types, self.model)
self.setItemDelegate(delegate)
# Show the edit widget as soon as the user clicks in the cell (needed for item delegate)
self.setEditTriggers(self.CurrentChanged)
self.setModel(self.model)
def init(self):
# self.model.init()
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
self.enable_cell(row, column)
def row_add(self):
# This cleans filters. If it didn't the added row might be filtered out and thus be invisible to the user.
#0. clean filters
#1. add to data
self.model.row_append()
return
def row_delete(self):
# This can be done while filtering.
#0. remove from data
#1. remove from filtered
return
def enable_cell(self, row, column):
if self.gui_column_types[column] == str:
return
index = self.model.index(row, column)
self.openPersistentEditor(index)
def model_data(self):
return self.model.dataframe
class SerializableModel(QtCore.QAbstractTableModel):
def __init__(self, dataframe, default_column_values, table_view, parent=None, editable=False):
"empty_row is a list of types ordered according to the columns in the dataframe, which represents an empty row"
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self.empty_row = pd.DataFrame(data=default_column_values, columns=list(dataframe.columns))
self.dataframe = dataframe
self.filtered = self.dataframe
self.table_view = table_view
self.editable = editable
def flags(self, index):
defaults = super().flags(index)
if self.editable:
return defaults | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable
return defaults | QtCore.Qt.ItemIsEnabled
def data(self, index, role=QtCore.Qt.DisplayRole):
# print("reading data {} {}".format(index.row(), index.column()))
if role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignCenter
if not index.isValid() or role != QtCore.Qt.DisplayRole or index.row() >= self.filtered.shape[0]:
return QtCore.QVariant()
value = self.filtered.iat[index.row(), index.column()]
return QtCore.QVariant(str(value))
def setData(self, index, value, role=QtCore.Qt.EditRole):
column = self.filtered.columns[index.column()]
column_dtype = self.filtered[column].dtype
if column_dtype != object:
value = None if value == '' else column_dtype.type(value)
row = self.filtered.index.values[index.row()]
self.dataframe.iat[row, index.column()] = value
self.dataChanged.emit(index, index)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return self.filtered.shape[0] if not parent.isValid() else 0
def columnCount(self, parent=QtCore.QModelIndex()):
return self.filtered.shape[1] if not parent.isValid() else 0
def insertRows(self, row, count, parent=QtCore.QModelIndex()):
""" Since we only need append, we assume row == rowCount. """
for _ in range(count):
self.dataframe = self.dataframe.append(self.empty_row, ignore_index=True)
return True
def row_append(self, count=1, parent=QtCore.QModelIndex()):
""" Reset filters, to ensure the new rows are not hidden by them, and appends rows with default values. """
self.filtered = self.dataframe
self.beginInsertRows()
self.insertRows(self.rowCount(), count, parent)
self.endInsertRows()
self.layoutChanged.emit()
DF_PROTOTYPE_GLOBAL = pd.DataFrame(data=OrderedDict((
("Type", [int()]),
("Details", [str()]),
)))
DF_PROTOTYPE_GLOBAL_SAVED = pd.DataFrame(data=OrderedDict((
("Type", [2, 0]),
("Details", ["item 1", "item 2"]),
)))
DF_PROTOTYPE_GLOBAL_GUIFRAME_TYPES = [InfoTypeSelector, LineEdit]
class InfoManager:
def __init__(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_InfoWindow()
self.ui.setupUi(self.window)
# Overwrites
self.ui.view_global.setParent(None)
self.ui.view_global = TableView("view_global", DF_PROTOTYPE_GLOBAL_SAVED,
DF_PROTOTYPE_GLOBAL_GUIFRAME_TYPES,
DF_PROTOTYPE_GLOBAL,
parent=self.ui.tab_global, editable=True)
self.ui.verticalLayout_4.addWidget(self.ui.view_global)
self.ui.view_global.init()
# self.ui.view_notes.setParent(None)
# self.ui.view_notes = iwl.DataFrameWidget(name="view_notes", parent=self.ui.tab_notes, df=data[1], editable=True)
# self.ui.verticalLayout_2.addWidget(self.ui.view_notes)
#
# self.ui.view_events.setParent(None)
# self.ui.view_events = iwl.DataFrameWidget(name="view_events", parent=self.ui.tab_events, df=data[2], editable=True)
# self.ui.verticalLayout.addWidget(self.ui.view_events)
def show(self):
self.window.show()
class Ui_InfoWindow(object):
def setupUi(self, InfoWindow):
InfoWindow.setObjectName("InfoWindow")
InfoWindow.resize(1000, 748)
self.centralwidget = QtWidgets.QWidget(InfoWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_5.addLayout(self.horizontalLayout)
self.info_tabs = QtWidgets.QTabWidget(self.centralwidget)
self.info_tabs.setObjectName("info_tabs")
self.tab_global = QtWidgets.QWidget()
self.tab_global.setObjectName("tab_global")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_global)
self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.view_global = QtWidgets.QTableView(self.tab_global)
self.view_global.setSortingEnabled(True)
self.view_global.setObjectName("view_global")
self.verticalLayout_4.addWidget(self.view_global)
self.info_tabs.addTab(self.tab_global, "")
self.verticalLayout_5.addWidget(self.info_tabs)
InfoWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(InfoWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1000, 20))
self.menubar.setObjectName("menubar")
InfoWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(InfoWindow)
self.statusbar.setObjectName("statusbar")
InfoWindow.setStatusBar(self.statusbar)
self.action_email_tab = QtWidgets.QAction(InfoWindow)
self.action_email_tab.setObjectName("action_email_tab")
self.action_exit = QtWidgets.QAction(InfoWindow)
self.action_exit.setObjectName("action_exit")
self.retranslateUi(InfoWindow)
self.info_tabs.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(InfoWindow)
def retranslateUi(self, InfoWindow):
_translate = QtCore.QCoreApplication.translate
InfoWindow.setWindowTitle(_translate("InfoWindow", "Info Window"))
self.info_tabs.setTabText(self.info_tabs.indexOf(self.tab_global), _translate("InfoWindow", "Global"))
self.action_email_tab.setText(_translate("InfoWindow", "Email Current Tab"))
self.action_exit.setText(_translate("InfoWindow", "Exit"))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
info_manager = InfoManager()
info_manager.show()
sys.exit(app.exec_())
答案 0 :(得分:2)
您的代码存在一些问题,最重要的是太令人费解,而 far 成为Minimal, Reproducible Example。有关更多信息,请参见此答案的底部。
首先,您要根据factories[column]
检查数据类型,这将返回字段编辑器 class ,这没有什么意义。您可能应该为小部件类数据类型设置一个属性,并可能使用try / except语句查找该数据类型是否实际上是您要查找的数据类型(在您的情况下为str
),否则让Qt返回数据类型的标准编辑器。
然后,由于您使用的是持久性编辑器,因此必须记住,每当提交数据时,您将分别获得以下内容:
QAbstractItemDelegate.commitData
:代表说需要设置一些数据QAbstractItemDelegate.setModelData
:委托人尝试将数据设置为其模型QAbstractItemModel.setData
:该模型根据委托人setModelData
告诉的内容设置数据QAbstractItemModel.commitData
:模型“保存”数据,例如,将数据提交给当前SQL数据库达QSql[Table|Query]Model
s QAbstractItemModel.dataChanged
:模型向每个感兴趣的零件发出信号某些数据已更改(如果前一个setData
返回True
)QAbstractItemDelegate.setEditorData
:[对此情况很重要],委托根据模型数据将 back 数据设置给编辑器,因为它是< em>持久一个,但唯一,如果其数据是user property(这有点令人费解,但是经过一番思考,我可以理解为什么)您的问题恰好在最后一部分:每当您设置数据时,数据都会更改为模型,但是编辑器数据不会相应更新,然后使用“用户属性”数据再次更新。
>我相信将这种方法添加到代理可能即可:
def setEditorData(self, widget, index):
# check if the data type is compatible
try:
if self.factories[index.columnn()].dataType:
widget.set_data(index.data())
except:
# whatever else
最终无关的笔记。
我知道很难获得好 MRE(尤其是 minimum 部分),但是请记住,虽然这可能会花费您 a 时间(我知道,相信我),确实有99%的时间值得您付出努力,这有两个原因。
首先,它将使人们(无论如何都愿意为您提供帮助)更倾向于实际帮助您:如果某人发现他/她自己过多地参与了您的代码理解,而不是实际尝试寻找解决方案,那么很可能他们将首先放弃;实际上,我花了超过10分钟的时间来了解代码的实际工作方式。不好啊
我可以假设这里至少有20个用户(超过way more than a million)可以回答您的问题;想象一下,如果每个人都决定花5分钟以上的时间来理解您的问题(总共花了将近2个小时),并且他们可能会在第一分钟之后放弃很多(如果不是全部的话) ,却找不到有用的答案。
其次,您甚至可以自己找到问题所在。这不仅可以解决您的问题,而且可以让您同时学到更多。
长话短说:很难,但是提供一个最小可重现的示例总是值得他们付出努力的。