【问题标题】:PyQt5 get data from model to show custom widget in QListViewPyQt5 从模型中获取数据以在 QListView 中显示自定义小部件
【发布时间】:2020-09-30 11:47:36
【问题描述】:

我正在使用 PyQt5 并尝试使用一些自定义数据结构 (Recipe) 的列表创建一个 GUI,并且我将自定义小部件定义为 QWidget 的子类,它描述了它应该如何显示。 我正在尝试使用 MVC,所以我有一个 QListView 并且我将 QAbstractListModel 子类化。 我希望列表包含我的自定义小部件而不仅仅是文本,因此我还为其定义了一个返回该小部件的项目委托。也许我对这三个组件(模型、项目委托、视图)如何协同工作的理解存在缺陷,因为我不确定如何访问模型的 data() 方法返回的任何内容并将其转换为特定的小部件我想在该列表单元格中显示。

示例代码:

class Recipe:
    pass # custom data structure

class RecipeWidget(QWidget, RecipeWidgetUI):
    def __init__(self, recipe, *args, **kwargs):
        super(RecipeWidget, self).__init__(*args, **kwargs)
        self.recipe = recipe
        self.setupUi(self)
        # some code here to populate the UI elements with data from self.recipe

class RecipeListModel(QAbstractListModel):
    def __init__(self, recipes, *args, **kwargs):
        super(RecipeListModel, self).__init__(*args, **kwargs)
        self.recipes = recipes
    def rowCount(self, index):
        return len(self.recipes)
    def data(self, index, role):
        if role == Qt.DisplayRole:
            return str(self.recipes[index.row()]) # this is the part I'm confused about

class RecipeItemDelegate(QItemDelegate):
    def __init__(self, parent):
        QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        return RecipeWidget(recipe, parent=parent) # where do I get recipe from??

class MainWindow(...):
    def __init__(self, ...):
        ...
        self.model = RecipeListModel(recipes)
        self.listView.setModel(self.model)
        self.listView.setItemDelegate(RecipeItemDelegate(self.listView))

我知道模型中的data(...) 函数应该返回一个字符串;我不明白的是:

  • 如何改为返回我的Recipe 数据结构?我是否必须以某种方式对其进行序列化,然后再反序列化?
  • RecipeItemDelegate 在哪里查看 data(...) 函数返回的内容以便正确构造配方对象?

【问题讨论】:

  • 我不确定我是否理解您的问题。可以使用index.data(role) 访问项目数据,您可以通过使用index.data() 添加关键字参数来填充createEditor 中的RecipeWidget,或者更好的是使用setEditorData()

标签: python user-interface model-view-controller pyqt5


【解决方案1】:

每个项目可以包含多个基于角色的数据属性。这些数据属性是 QVariants。您可以在 QVariant 中嵌入很多不同的东西,甚至是自定义对象。

我会说最好的做法是为 Display 角色使用一个字符串,并为其他事情使用 Qt.UserRole(或 Qt.UserRole + n)。您的委托仍然可以通过在查询项目数据时提供该角色来访问这些。

虽然我使用 C++ 进行开发,但我并不精通如何使用 PyQt5 的 QVariant 自定义类型。请注意,如果您使用 Qt for Python 而不是 PyQt5,则无需使用 QVariant,只需设置和获取任何自定义 python 变量。

【讨论】:

  • 从 PyQt5 开始,QVariants 被透明地转换为 python 或 PyQt 对象(在 PyQt4 中使用 SIPv2 时也是如此)。
  • data 函数中的返回更改为return self.recipes[index.row()] 并在RecipeItemDelegate 中将配方设置为recipe = index.data(QtCore.Qt.DisplayRole) 会导致将空白行添加到列表中。而createEditor(...) 函数似乎从未被调用...
【解决方案2】:
  1. model.data() 需要为所需角色提供数据。在您的情况下,您可能决定使用 Qt.DisplayRole 来表示数据以供显示。你可以return self.recipes[index.row()]。但是如果要支持拖放,QT需要能够序列化数据。现在让我们在 self.recipes 中提供 python 索引,它可以被序列化。 return index.row() # which happens to be python index in the self.recipes
  2. RecipeItemDelegate 需要能够使用来自 model.data() 的数据引用 Recipe 对象。所以,让我们初始化 RecipeItemDelegate(parent, parent.model().recipes)
  3. RecipeItemDelegate.paint(self,painter, option, index) 应实施以绘制“显示”。在paint() 内部,index.data(Qt.DisplayRole) 获取配方中的数据行(恰好是index.row())。 self.reference_to_recipes[index.data(Qt.DisplayRole)] 获取 Recipe 对象。然后你拿起“painter”,从你得到的数据中绘制信息,在“option”中指定区域和样式。另请参阅 QT 文档中的 QItemDelegate::paint。

如果您希望代理支持编辑,则:

  1. createEditor() 应该创建用于更改模型数据的小部件
  2. setEditorData() 应该使用要操作的数据填充小部件。喜欢editor.set_recipe(self.reference_to_recipes[index.data(Qt.DisplayRole)])
  3. updateEditorGeometry() 确保编辑器相对于项目视图正确显示。
  4. setModelData() 将更新的数据返回给模型。像model.setData(index, editor.get_modified_recipe(), Qt.EditRole) 当然,model.setData() 需要能够在 Qt.EditRole 上处理 Recipe 对象

【讨论】:

  • 我明白了;所以我的实际小部件绘制逻辑需要在paint(...) 中实现。有没有办法轻松绘制整个小部件?还是必须逐个组件地绘制?我在网上看到的大多数示例都使用QPainter 来进行非常底层的绘制,例如一次绘制一个矩形。
  • 检查 QT 文档中的 QWidget::render()。
  • 我现在在 QItemDelegate 中有一个 paint 方法和 recipeWidget.render(painter, QtCore.QPoint(option.rect.x(), option.rect.y()), QtGui.QRegion(0, 0, option.rect.width(), option.rect.height())) 但仍然没有呈现任何内容,即使我已经确定该方法实际上正在被调用,并且 recipeWidget 是每次都设置正确。
  • void QWidget::render(QPaintDevice *target, const QPoint &targetOffset = QPoint(), const QRegion &sourceRegion = QRegion(), QWidget::RenderFlags renderFlags = RenderFlags(DrawWindowBackground | DrawChildren)) === 》QPaintDevice类是可以用QPainter绘制的对象的基类。 (这是一个你用画家画的地方,而不是画家本身。就像你用笔在纸上画画,而不是在笔本身上)
  • 所以我应该使用painter.device() 而不是painter
猜你喜欢
  • 2023-02-13
  • 2017-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-04
  • 1970-01-01
相关资源
最近更新 更多