Model/View 具有复杂小部件表示的 QListView
Model/View QListView with complex widget representation
我正在尝试在 PyQT5 应用程序中显示 QListView 以显示一些数据。
我的目标是用复杂的表示法来表示这些数据,而不仅仅是文本行。
因此,我的 QListView 应该“实例化”一个从 Ui 文件加载的新 QWidget,并显示具有该 Ui 表示的每个元素。
我的问题是 Windows.
的布局
我有一个main window,里面有一个QSplitter,左边是QListView,右边是一些widgets(Label, textedit, ...)
QMainWindow 设计器:
我想将 QListView 的每一行表示为这个小部件:
迷你小部件设计器:
似乎如果 QListView 在这样的小部件容器中:
Arbo 对象检查员:
然后我有一个奇怪的行为,子窗口小部件显示在彼此之上:
ListView 小部件堆叠:
但是如果我删除 QListView 的容器
OkWindow 设计师:
以及检查员的看法
它们显示正确
按预期工作:
知道为什么吗?
我的代码和在 Model/View QListView 中实现复杂小部件表示的方法有什么问题吗?
应用示例代码如下:
import typing
from PyQt5 import QtCore, uic
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt5.QtWidgets import (
QApplication,
QStyledItemDelegate,
QWidget, QMainWindow, )
class Editor(QWidget):
def __init__(self, index, parent=None):
super().__init__(parent)
uic.loadUi('step.ui', self)
value = index.model().data(index, Qt.ItemDataRole)
self.label.setText(value)
class MyListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._data_list = []
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any:
if role == Qt.ItemDataRole:
return self._data_list[index.row()]
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return len(self._data_list)
@property
def data_list(self):
return self._data_list
@data_list.setter
def data_list(self, data_list):
self.beginResetModel()
self._data_list = data_list.copy()
self.endResetModel()
class StyledItemDelegate(QStyledItemDelegate):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self.editor = None
def createEditor(self, parent, option, index):
self.editor = Editor(index, parent)
return self.editor
def sizeHint(self, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtCore.QSize:
if self.editor:
return self.editor.sizeHint()
else:
return super().sizeHint(option, index)
data = ["Apple", "Strawberry", "Cherry"]
class MainWindow(QMainWindow):
def __init__(self, qapp):
"""
Init main window
"""
super().__init__()
self.qapp = qapp
# Not working main_window ui file
# uic.loadUi('main_window.ui', self)
# Working main_window ui file
uic.loadUi('main_window_ok.ui', self)
self.load_data()
def load_data(self):
controller = MyCtrl(self)
controller.load_datalist()
class MyCtrl:
def __init__(self, parent: QMainWindow):
self.parent = parent
def load_datalist(self):
self.model = MyListModel()
self.model.data_list = data
self.parent.listView.setModel(self.model)
delegate = StyledItemDelegate(self.parent.listView)
self.parent.listView.setItemDelegate(delegate)
for i in range(self.model.rowCount()):
index = self.model.index(i, 0)
self.parent.listView.openPersistentEditor(index)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = MainWindow(app)
main.showMaximized()
sys.exit(app.exec_())
main_window.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>642</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="listView"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QSlider" name="horizontalSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
main_window_ok.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>799</width>
<height>642</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QSlider" name="horizontalSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>799</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
step.ui 文件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>387</width>
<height>173</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QDial" name="dial"/>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
创建一个存储 QWidget 的变量是无用且危险的,因为例如您将只有最后一个小部件或更糟的是访问已被删除的小部件。相反,您必须使用角色来存储和获取大小,默认角色是 Qt::SizeHintRole.
from PyQt5.uic import loadUi
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QSize
from PyQt5.QtWidgets import (
QApplication,
QStyledItemDelegate,
QWidget,
QMainWindow,
)
class Editor(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
loadUi("step.ui", self)
class MyListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._data_list = list()
self._size_hints = dict()
def rowCount(self, parent=QModelIndex()):
if parent.isValid():
return 0
return len(self._data_list)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
return self._data_list[index.row()]
elif role == Qt.SizeHintRole:
return self._size_hints.get(index.row(), QSize(100, 30))
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.SizeHintRole:
self._size_hints[index.row()] = value
self.dataChanged.emit(index, index, (role,))
return True
return False
@property
def data_list(self):
return self._data_list
@data_list.setter
def data_list(self, data_list):
self.beginResetModel()
self._data_list = data_list.copy()
self.endResetModel()
class StyledItemDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = Editor(parent)
model = index.model()
model.setData(index, editor.sizeHint(), Qt.SizeHintRole)
return editor
def setEditorData(self, editor, index):
value = index.data()
editor.label.setText(value)
data = ["Apple", "Strawberry", "Cherry"]
class MainWindow(QMainWindow):
def __init__(self):
"""
Init main window
"""
super().__init__()
loadUi("main_window.ui", self)
class MyCtrl:
def __init__(self, view):
self.view = view
def load_datalist(self):
self.model = MyListModel()
self.model.data_list = data
self.view.listView.setModel(self.model)
delegate = StyledItemDelegate(self.view.listView)
self.view.listView.setItemDelegate(delegate)
for i in range(self.model.rowCount()):
index = self.model.index(i, 0)
self.view.listView.openPersistentEditor(index)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = MainWindow()
main.showMaximized()
controller = MyCtrl(main)
controller.load_datalist()
sys.exit(app.exec_())
我正在尝试在 PyQT5 应用程序中显示 QListView 以显示一些数据。
我的目标是用复杂的表示法来表示这些数据,而不仅仅是文本行。
因此,我的 QListView 应该“实例化”一个从 Ui 文件加载的新 QWidget,并显示具有该 Ui 表示的每个元素。
我的问题是 Windows.
的布局我有一个main window,里面有一个QSplitter,左边是QListView,右边是一些widgets(Label, textedit, ...)
QMainWindow 设计器:
我想将 QListView 的每一行表示为这个小部件:
迷你小部件设计器:
似乎如果 QListView 在这样的小部件容器中:
Arbo 对象检查员:
然后我有一个奇怪的行为,子窗口小部件显示在彼此之上:
ListView 小部件堆叠:
但是如果我删除 QListView 的容器 OkWindow 设计师:
以及检查员的看法
它们显示正确
按预期工作:
知道为什么吗?
我的代码和在 Model/View QListView 中实现复杂小部件表示的方法有什么问题吗?
应用示例代码如下:
import typing
from PyQt5 import QtCore, uic
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt5.QtWidgets import (
QApplication,
QStyledItemDelegate,
QWidget, QMainWindow, )
class Editor(QWidget):
def __init__(self, index, parent=None):
super().__init__(parent)
uic.loadUi('step.ui', self)
value = index.model().data(index, Qt.ItemDataRole)
self.label.setText(value)
class MyListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._data_list = []
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any:
if role == Qt.ItemDataRole:
return self._data_list[index.row()]
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return len(self._data_list)
@property
def data_list(self):
return self._data_list
@data_list.setter
def data_list(self, data_list):
self.beginResetModel()
self._data_list = data_list.copy()
self.endResetModel()
class StyledItemDelegate(QStyledItemDelegate):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self.editor = None
def createEditor(self, parent, option, index):
self.editor = Editor(index, parent)
return self.editor
def sizeHint(self, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtCore.QSize:
if self.editor:
return self.editor.sizeHint()
else:
return super().sizeHint(option, index)
data = ["Apple", "Strawberry", "Cherry"]
class MainWindow(QMainWindow):
def __init__(self, qapp):
"""
Init main window
"""
super().__init__()
self.qapp = qapp
# Not working main_window ui file
# uic.loadUi('main_window.ui', self)
# Working main_window ui file
uic.loadUi('main_window_ok.ui', self)
self.load_data()
def load_data(self):
controller = MyCtrl(self)
controller.load_datalist()
class MyCtrl:
def __init__(self, parent: QMainWindow):
self.parent = parent
def load_datalist(self):
self.model = MyListModel()
self.model.data_list = data
self.parent.listView.setModel(self.model)
delegate = StyledItemDelegate(self.parent.listView)
self.parent.listView.setItemDelegate(delegate)
for i in range(self.model.rowCount()):
index = self.model.index(i, 0)
self.parent.listView.openPersistentEditor(index)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = MainWindow(app)
main.showMaximized()
sys.exit(app.exec_())
main_window.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>642</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="listView"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QSlider" name="horizontalSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
main_window_ok.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>799</width>
<height>642</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QSlider" name="horizontalSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>799</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
step.ui 文件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>387</width>
<height>173</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QDial" name="dial"/>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
创建一个存储 QWidget 的变量是无用且危险的,因为例如您将只有最后一个小部件或更糟的是访问已被删除的小部件。相反,您必须使用角色来存储和获取大小,默认角色是 Qt::SizeHintRole.
from PyQt5.uic import loadUi
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QSize
from PyQt5.QtWidgets import (
QApplication,
QStyledItemDelegate,
QWidget,
QMainWindow,
)
class Editor(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
loadUi("step.ui", self)
class MyListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._data_list = list()
self._size_hints = dict()
def rowCount(self, parent=QModelIndex()):
if parent.isValid():
return 0
return len(self._data_list)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
return self._data_list[index.row()]
elif role == Qt.SizeHintRole:
return self._size_hints.get(index.row(), QSize(100, 30))
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.SizeHintRole:
self._size_hints[index.row()] = value
self.dataChanged.emit(index, index, (role,))
return True
return False
@property
def data_list(self):
return self._data_list
@data_list.setter
def data_list(self, data_list):
self.beginResetModel()
self._data_list = data_list.copy()
self.endResetModel()
class StyledItemDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = Editor(parent)
model = index.model()
model.setData(index, editor.sizeHint(), Qt.SizeHintRole)
return editor
def setEditorData(self, editor, index):
value = index.data()
editor.label.setText(value)
data = ["Apple", "Strawberry", "Cherry"]
class MainWindow(QMainWindow):
def __init__(self):
"""
Init main window
"""
super().__init__()
loadUi("main_window.ui", self)
class MyCtrl:
def __init__(self, view):
self.view = view
def load_datalist(self):
self.model = MyListModel()
self.model.data_list = data
self.view.listView.setModel(self.model)
delegate = StyledItemDelegate(self.view.listView)
self.view.listView.setItemDelegate(delegate)
for i in range(self.model.rowCount()):
index = self.model.index(i, 0)
self.view.listView.openPersistentEditor(index)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = MainWindow()
main.showMaximized()
controller = MyCtrl(main)
controller.load_datalist()
sys.exit(app.exec_())