【问题标题】:What are the mechanics of the default delegate for item views in Qt?Qt 中项目视图的默认委托的机制是什么?
【发布时间】:2016-03-03 13:59:16
【问题描述】:

短版

QTreeView 使用的默认委托是什么?特别是我想找到它的paint() 方法?

加长版

我是 Python 用户 (Pyside/PyQt),并且正在使用自定义委托来重新创建 QTreeView 的一些功能。因此,我试图找到QTreeView 中使用的默认委托和绘制方法。最好解释一下它是如何工作的。

跨帖

我在 Qt 中心 (http://www.qtcentre.org/threads/64458-Finding-default-delegate-for-QTreeView?) 发布了同样的问题。

【问题讨论】:

    标签: qt pyqt pyside


    【解决方案1】:

    tl;dr

    所有项目视图的默认委托是QStyledItemDelegate。它的paint() 方法调用drawControl()(在qcommonstyle.cpp 中定义)来绘制每个项目。因此,请仔细阅读 qcommonstyle.cpp 以了解有关如何绘制每个项目的详细信息。


    长篇回答

    喜欢简洁的人应该阅读上面的tl;drdocumentation on Styles in Item Views。如果您仍然被卡住(许多 Python 用户可能会遇到),那么这个答案的其余部分应该会有所帮助。

    我。默认委托是 QStyledItemDelegate

    QStyledItemDelegate 是视图中项目的默认委托。这在Qt's Model/View Programming overview中有明确说明:

    自 Qt 4.4 起,默认委托实现由 QStyledItemDelegate,它被 Qt 用作默认委托 标准视图。

    docs for QStyledItemDelegate 提供了更多细节:

    当在 Qt 项目视图中显示来自模型的数据时,例如 QTableView, 单个项目由代表绘制。此外,当一个项目 已编辑,它提供了一个编辑器小部件,该小部件放置在 进行编辑时的项目视图。 QStyledItemDelegate 是 所有 Qt 项目视图的默认委托,并安装在它们之上 创建它们的时间。

    总之,如果您想了解任何项目视图(不仅仅是树视图,还包括表格和列表)的基本机制,请研究 QStyledItemDelegate

    qstyleditemdleegate.cpp 中的paint() 方法定义为on line 419 of the doxygenated code base。让我们看看最后两行:

        QStyle *style = widget ? widget->style() : QApplication::style();
        style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
    

    这里发生了两件事。首先,设置样式——通常为QApplication.style()。其次,调用该样式的drawControl() 方法来绘制正在绘制的项目。就是这样。这实际上是QStyledItemDelegate.paint() 的最后一行!

    因此,如果您真的想弄清楚默认委托是如何绘制事物的,我们实际上必须研究样式,它正在做所有真正的工作。这就是我们将在本文档的其余部分中做的事情。

    二。 QStyle:是什么赋予了代表风格?

    当使用 Qt 显示任何内容时,它会根据您在实例化 QApplication 时以系统特定方式选择的某种样式进行绘制。来自the docs for QStyle

    QStyle 类是封装外观的抽象基类 和 GUI 的感觉。 Qt 包含一组模拟的 QStyle 子类 Qt 支持的不同平台的样式(QWindowsStyle, QMacStyle、QMotifStyle 等)。默认情况下,这些样式内置于 QtGui 库。

    在 Qt 中,您可以在 src/gui/styles/N.cpp 中找到样式 N 的源代码。

    每种样式都包含用于在 GUI 中绘制所有内容的基本操作的实现,从树形视图到下拉菜单。标准样式,例如QWindowsStyle,继承了QCommonStyle 的大部分方法。每种特定样式通常仅包含与该共同基础的微小偏差。因此,对qcommonstyle.cpp 的仔细研究将揭示Qt 开发人员发现对绘制GUI 的所有部分有用的基本功能。它的重要性怎么强调都不为过。

    在下文中,我们将研究与图纸视图项相关的部分。

    三。 QStyle.drawControl():对代理进行内窥镜检查

    如上所述,了解绘制视图的基本机制需要检查 qcommonstyle.cpp 中的 drawControl() 的实现,该实现从第 1197 行开始。请注意以下内容,当我提到行号时没有提及一个文件名,按照惯例我指的是qcommonstyle.cpp in the doxygenated code base

    documentation for QStyle.drawControl() 具有指导意义:

    QStyle.drawControl(元素、选项、画家)

    参数:

    • 元素——QStyle.ControlElement

    • 选项 – QtGui.QStyleOption

    • 画家 – PySide.QtGui.QPainter

    使用提供的带有样式的画家绘制给定元素 选项指定的选项...。​​选项参数是一个指针 QStyleOption 对象并包含所有需要的信息 绘制所需的元素。

    调用者通过传递QStyle.ControlElement 标志告诉drawControl() 它试图绘制什么类型的元素。控件元素是向用户显示信息的窗口的高级组件:例如复选框、按钮和菜单项。这里列举了所有的控制元素:

    http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#ControlElement-enum

    回想在对QStyledItemDelegate.paint() 的调用中发送的控制元素是CE_ItemViewItem,它只是要在项目视图中显示的项目。在QCommonStyle.drawControl() 中,CE_ItemViewItem 案例从第 2153 行开始。让我们深入研究。

    A. subElementRect():大小很重要

    正确设置每个项目的大小和布局是关键。这是drawControl() 计算的第一件事。为了获取此信息,它调用subElementRect()(在第 2313 行定义,并在第 2158 行首次调用)。例如,我们有:

    QRect textRect = subElementRect(SE_ItemViewItemText, vopt, widget);
    

    第一个参数是QStyle.SubElement 标志,在本例中为SE_ItemViewItemText。样式子元素表示控制元素的组成部分。视图中的每个项目都有三个可能的子元素:复选框、图标和文本;显然,SE_ItemViewItemText 子元素代表文本。这里列举了所有可能的子元素:

    http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#SubElement-enum

    subElementRect() 方法包含子元素枚举中的所有案例:我们的 SE_ItemViewItemText 案例从第 3015 行开始。

    注意subElementRect() 返回一个QRect,它使用整数精度定义平面中的一个矩形,并且可以用四个整数(左、上、宽、高)构造。例如r1 = QRect(100, 200, 11, 16)。这为子元素指定了它的大小以及它将在视口中绘制的 x,y 位置。

    subElementRect() 实际上调用viewItemLayout()(在第 999 行定义)来完成真正的工作,这是一个两步过程。首先,viewItemLayout() 使用viewItemSize() 计算子元素的高度和宽度。其次,它计算子元素的 x 和 y 位置。让我们依次考虑这些操作。

    1. viewItemLayout():计算子元素的宽高

    从第 1003 行开始,viewItemLayout() 调用 viewItemSize()(在第 838 行定义),它计算子元素所需的高度和宽度。

    viewItemSize() 在哪里获得诸如标题栏高度之类的默认数字?这是像素度量的领域。像素度量是由单个像素值表示的与样式相关的大小。例如,Style.PM_IndicatorWidth 返回复选框指示器的宽度,QStyle.PM_TitleBarHeight 返回应用程序样式的标题栏高度。这里列举了所有不同的QStyle.PixelMetrics:

    http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#PixelMetric-enum

    您可以使用QStyle.pixelMetric() 检索给定像素度量的值,viewItemSize() 中经常使用该值。 pixelMetric() 的第一个输入是枚举中的 QStyle.PixelMetrics 之一。在qcommonstyle.cpp 中,pixelMetric() 的实现从第 4367 行开始。

    例如,viewItemSize() 使用以下方法计算复选框的宽度和高度(如果需要):

    return QSize(proxyStyle->pixelMetric(QStyle::PM_IndicatorWidth, option, widget),
        proxyStyle->pixelMetric(QStyle::PM_IndicatorHeight, option, widget));
    

    请注意,pixelMetric() 不仅在 viewItemSize() 中使用,而且无处不在。它用于计算许多 GUI 元素的度量属性,从窗口边框到图标,并贯穿于qcommonstyle.cpp。基本上,每当您需要知道您的样式为大小不变的某些图形元素(如复选框)使用了多少像素时,该样式将调用像素度量。

    2。 viewItemLayout():魔方子元素获取x/y位置

    viewItemLayout() 的第二部分专门用于组织刚刚计算出宽度和高度的子元素的布局。也就是说,它需要找到它们的 x 和 y 位置才能完成将值填充到QRects,例如textRect。子元素的组织方式将根据视图的一般设置(例如,它是左右方向还是左右方向)而有所不同。因此,viewItemLayout() 会根据这些因素计算每个子元素的最终静止位置。

    如果您需要有关如何在自定义委托中重新实现 sizeHint() 的线索,subElementRect() 可能是有用的提示和技巧来源。特别是,viewItemSize() 可能包含有用的花絮和相关的像素指标,当您希望自定义视图与默认值非常匹配时,您可能想要查询。

    一旦为文本、图标和复选框子元素计算了QRects,drawControl() 继续前进,最后开始绘制。

    B.为背景着色:变得原始

    首先为项目填充背景(第 2163 行):

    drawPrimitive(PE_PanelItemViewItem, option, painter);
    

    这很像对QStyle.drawControl() 的调用,但第一个参数不是控制元素,而是原始元素(PE)。正如drawControl() 是一种控制所有高级控制元素绘制的大型扩展方法一样,QStyle.drawPrimitive() 在 GUI 中绘制大部分低级原始图形元素(例如项目视图中的矩形背景)。这些较低级别的单元,即原始元素,在此列举:

    http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#PrimitiveElement-enum

    文档说,“原始元素是一种常见的 GUI 元素,例如复选框指示器或按钮斜面。”例如,PE_PanelItemViewItem 原始元素是“项目视图中项目的背景”。

    每个样式都必须有一个drawPrimitive() 的实现,我们的实现从第 140 行开始。在那里,您可以详细了解它是如何执行其原始绘制操作的。这是有关如何在自定义委托中实际使用paint() 命令的有用提示来源。

    QStyle.drawPrimitive()PE_PanelItemViewItem 案例的重新实现从第 773 行开始。它首先根据项目的状态选择适当的背景颜色。它通过查询项目的QStyle.StateFlag 来做到这一点。 option.state 包含描述项目当时状态的状态标志(是否启用、选择、正在编辑等?)。这些状态不仅用于后端,而且在自定义委托中重新实现 QStyledItemDelegate.paint() 时可能需要使用它。您可以在此处找到QStyle.StateFlags 的枚举:

    http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#StateFlag-enum

    选择正确的颜色后,drawPrimitive() 然后使用QPainter.fillRect() 用该颜色填充适当的区域(第 786 行):

    p->fillRect(vopt->rect, vopt->backgroundBrush);
    

    QPainter.fillRect() 是为自己实现自定义委托时非常有用的方法。

    处理完背景后,drawControl() 继续完成项目的绘制,从复选框(第 2165 行)开始,然后是图标(第 2185 行),最后是文本(第 2194 行)。我们不会详细讨论这一点,但我将简要讨论它是如何绘制文本的。

    C.绘制文本:叛变

    首先,项目的状态用于指定文本的颜色。这使用了刚才讨论的QStyle.StateFlags。然后drawControl() 将文本绘制职责踢到自定义方法viewItemDrawText()(在第921 行定义):

    viewItemDrawText(painter, vopt, textRect);
    

    此方法将上面描述的画家、选项和文本矩形(A 部分)作为参数。注意 option 参数非常重要:它是一个QStyleOption 类,通过它在样式中传递内容(它包括图标、检查状态和文本属性)。

    从选项中提取文本后,将其合并到QtGui.QTextLine 中,该QtGui.QTextLayout 将添加到QtGui.QTextLayout 中。最终的省略(即,使用省略号,取决于自动换行设置)文本由 QPainter.drawText()(参见第 983 行)绘制,这是 Qt 的原始绘画操作之一。

    坦率地说,很多viewItemDrawText() 都用于处理自动换行。这就是我们开始深入了解 Qt 的一些内容的地方,这些内容是 Python 用户永远不会看到的,更不用说修补了。例如,它使用QStackTextEngine 类。我鼓励你谷歌“qstacktextengine pyqt”,看看 Python 用户出现这种情况的频率有多低。如果您对此感兴趣,请尝试一下!

    四。总结

    如果您想访问默认委托的底层绘制机制,您最终将在 qcommonstyle.cpp 中研究 QStyle.drawControl() 的实现,这是一个 6000 行的文件野兽。这个练习对于弄清楚用于计算大小和绘制项目包含的原始图形元素的确切过程非常有帮助。但是,有时,这种野兽可能会非常可怕且无济于事,例如在处理自动换行时。在这些情况下,您可能只需要为您的委托找出所需功能的自定义实现。

    最后,既然我们已经看到了工作原理的全局视图,我们可以更好地了解QStyle 的文档有多么有用,尤其是Styles in Item Views 部分。在那里,我们发现了以下启示性的爱情金块:

    视图中项目的绘制由委托执行。 Qt的 默认委托QStyledItemDelegate也用于计算 项目(及其子元素)的边界矩形......当 QStyledItemDelegate 绘制它的项目,它绘制 CE_ItemViewItems...当 实现一种样式来自定义项目视图的绘制,您需要 检查QCommonStyle(和任何其他子类)的实现 你的风格继承自)。通过这种方式,您可以了解哪些以及如何 其他样式元素已经绘制,然后您可以重新实现 应该以不同方式绘制的元素的绘画。

    所以基本上原始帖子的答案是,“他们说什么。”

    【讨论】:

    • 我觉得这篇文章很有见地,感谢您花时间写它!我对 Qt 的委托框架有点失望,但最终使用 QAbstractItemView.openPersistentEditor 跳过绘画,并且您的大部分帖子都实现了 QStyledItemDelegate.sizeHint,然后在 QStyledItemDelegate.setEditorData(index) 的末尾调用 QStyledItemDelegate.emit(index)
    • @hansonap 非常感谢,我很高兴它有帮助 - 这篇文章绝对是爱的劳动。
    猜你喜欢
    • 2020-11-13
    • 1970-01-01
    • 1970-01-01
    • 2022-11-04
    • 1970-01-01
    • 2013-12-30
    • 2019-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多