Qt 中项目视图的默认委托机制是什么?

What are the mechanics of the default delegate for item views in Qt?

短版

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

更长的版本

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

交叉post

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

tl;dr

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


Long-form回答

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

我。默认委托是 QStyledItemDelegate

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

中有明确说明

Since Qt 4.4, the default delegate implementation is provided by QStyledItemDelegate, and this is used as the default delegate by Qt's standard views.

docs for QStyledItemDelegate 提供了更多细节:

When displaying data from models in Qt item views, e.g., a QTableView, the individual items are drawn by a delegate. Also, when an item is edited, it provides an editor widget, which is placed on top of the item view while editing takes place. QStyledItemDelegate is the default delegate for all Qt item views, and is installed upon them when they are created.

总之,如果您想了解任何项目视图(不仅是树视图,还有表格和列表)的基本机制,请学习 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 时选择的某种样式以 system-specific 方式绘制的。来自 the docs for QStyle:

The QStyle class is an abstract base class that encapsulates the look and feel of a GUI. Qt contains a set of QStyle subclasses that emulate the styles of the different platforms supported by Qt (QWindowsStyle, QMacStyle, QMotifStyle, etc.). By default, these styles are built into the QtGui library.

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

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

接下来,我们将检查与绘图视图项相关的部分。

三. QStyle.drawControl(): 对委托进行内窥镜检查

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

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

QStyle.drawControl(element, option, painter)

Parameters:

  • element – QStyle.ControlElement

  • option – QtGui.QStyleOption

  • painter – PySide.QtGui.QPainter

Draws the given element with the provided painter with the style options specified by option.... The option parameter is a pointer to a QStyleOption object and contains all the information required to draw the desired element.

调用者通过向 drawControl() 传递一个 QStyle.ControlElement 标志来告诉 drawControl() 它试图绘制什么类型的元素。控件元素是 window 的 higher-level 组件,向用户显示信息:诸如复选框、按钮和菜单项之类的东西。此处列举了所有控制元素:

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

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

一个。 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() returns a QRect,它在平面上定义了一个矩形,使用整数精度,可以用四个整数(left, top, width, height ).例如r1 = QRect(100, 200, 11, 16)。这为子元素指定了它的大小以及它将在视口中绘制的 x,y 位置。

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

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

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

viewItemSize() 从哪里获得诸如标题栏高度之类的默认值?这是像素度量的省份。像素度量是由单个像素值表示的 style-dependent 大小。例如,Style.PM_IndicatorWidth returns 复选框指示符的宽度,以及 QStyle.PM_TitleBarHeight returns 应用程序样式的标题栏高度。此处列举了所有不同的 QStyle.PixelMetric

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

您可以使用 QStyle.pixelMetric() 检索给定像素指标的值,这在 viewItemSize() 中被大量使用。 pixelMetric() 的第一个输入是枚举中的 QStyle.PixelMetric 之一。在 qcommonstyle.cpp 中,pixelMetric() 的实现从第 4367 行开始。

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

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

请注意 pixelMetric() 不仅用于 viewItemSize(),而且无处不在。它用于计算许多 GUI 元素的度量属性,从 window 边框到图标,并且遍及 qcommonstyle.cpp。基本上,只要您需要知道您的样式用于某些大小不变的图形元素(如复选框)的像素数,样式就会调用像素指标。

2。 viewItemLayout():魔方子元素得到x/y个位置

viewItemLayout()的第二部分专门组织刚刚计算出宽高的子元素的布局。也就是说,它需要找到它们的x和y位置来完成将值填充到QRect中,例如textRect。子元素将根据视图的一般设置进行不同的组织(例如,它是 right-left 还是 left-right 导向)。因此,viewItemLayout()根据这些因素计算每个子元素的最终静止位置。

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

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

乙。给背景上色:变得原始

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

drawPrimitive(PE_PanelItemViewItem, option, painter);

这很像对QStyle.drawControl()的调用,但第一个参数不是控制元素,而是原始元素(PE)。正如 drawControl() 是一个庞大的方法,它控制所有 higher-level 控件元素的绘制,QStyle.drawPrimitive() 绘制了 GUI 中的大部分 lower-level 原始图形元素(例如项目视图中的矩形背景)。这些lower-level个单位,原始元素,列举在这里:

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

文档说,"A primitive element is a common GUI element, such as a checkbox indicator or button bevel." 例如,PE_PanelItemViewItem 基本元素是“[T]项目视图中项目的背景。”

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

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

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 部分)描述的画家、选项和文本矩形作为参数。请注意选项参数非常重要:它是一个 QStyleOption class,通过它在样式中传递内容(它包括图标、检查状态和文本属性)。

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

坦率地说,大量 viewItemDrawText() 花在处理自动换行上。这是我们开始了解 Qt 的一些内容的地方,Python 用户从来没有打算看到,更不用说修补了。例如,它使用 QStackTextEngine class。我鼓励您 Google 'qstacktextengine pyqt' 看看 Python 用户出现这种情况的频率有多低。如果您对此感兴趣,请尝试一下!

四.摘要

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

最后,既然我们已经看到了事情如何运作的 big-picture 视图,我们可以更好地理解 QStyle 文档的帮助,尤其是 Styles in Item Views 部分.在那里,我们找到了以下具有启发性的爱情金块:

The painting of items in views is performed by a delegate. Qt’s default delegate, QStyledItemDelegate, is also used for calculating bounding rectangles of items (and their sub-elements) …When QStyledItemDelegate paints its items, it draws CE_ItemViewItems…When implementing a style to customize drawing of item views, you need to check the implementation of QCommonStyle (and any other subclasses from which your style inherits). This way, you find out which and how other style elements are already painted, and you can then reimplement the painting of elements that should be drawn differently.

所以原来 post 的答案基本上是 "What they said."