Qt 中项目视图的默认委托的机制是什么? [英] What are the mechanics of the default delegate for item views in Qt?

查看:39
本文介绍了Qt 中项目视图的默认委托的机制是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

短版

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

What is the default delegate used by QTreeView? In particular I am trying to find its paint() method?

更长的版本

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

I am a Python user (Pyside/PyQt), and am using a custom delegate to recreate some of the functionality of QTreeView. Hence, I am trying to find the default delegate, and paint method, that is used in a QTreeView. Even better would be an explanation of how it works.

交叉发布

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

I posted the same question at Qt Centre (http://www.qtcentre.org/threads/64458-Finding-default-delegate-for-QTreeView?).

推荐答案

tl;dr

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

The default delegate for all item views is the QStyledItemDelegate. Its paint() method invokes drawControl(), defined in qcommonstyle.cpp, to draw each item. Hence, peruse qcommonstyle.cpp for the nitty-gritty details about how each item is drawn.

长篇回答

喜欢简洁的人应该阅读上面的 tl;dr有关项目视图中样式的文档.如果您仍然被卡住(许多 Python 用户可能会卡住),那么这个答案的其余部分应该会有所帮助.

Those who prefer brevity should read the tl;dr above and the documentation on Styles in Item Views. If you are still stuck (and many Python users probably will be), the rest of this answer should help.

我.默认委托是 QStyledItemDelegate

QStyledItemDelegate 是视图中项目的默认委托.这在 Qt 的模型/视图编程概述中有明确说明:

The QStyledItemDelegate is the default delegate for items in views. This is stated clearly in Qt's Model/View Programming overview:

从 Qt 4.4 开始,默认的委托实现是由QStyledItemDelegate,这被 Qt 用作默认委托标准视图.

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.

QStyledItemDelegate 的文档提供再详细一点:

The docs for QStyledItemDelegate provide a little more detail:

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

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.

In sum, if you want to understand the underlying mechanics of any of the item views (not just a tree view, but tables and lists too), study QStyledItemDelegate.

qstyleditemdleegate.cpp 中的 paint() 方法定义为 在 doxygenated 代码库的第 419 行.让我们看看最后两行:

The paint() method in qstyleditemdleegate.cpp is defined on line 419 of the doxygenated code base. Let's look at the last two lines:

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

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

Two things are happening here. First, the style is set -- typically to QApplication.style(). Second, that style's drawControl() method is invoked to draw the item being painted. And that's it. That's literally the final line of QStyledItemDelegate.paint()!

因此,如果你真的想弄清楚默认委托是如何绘制东西的,我们实际上必须研究风格,它正在做所有真正的工作.这就是我们将在本文档的其余部分进行的工作.

Hence, if you really want to figure out how the default delegate is painting things, we've actually got to study the style, which is doing all the real work. That's what we'll do in the rest of this document.

二.QStyle:是什么赋予了委托的风格?

当使用 Qt 显示任何内容时,它会根据您在实例化 QApplication 时以特定于系统的方式选择的某种样式绘制.来自 QStyle 的文档:

When anything is displayed using Qt, it is drawn according to some style that was chosen, in a system-specific way, when you instantiated QApplication. From the docs for QStyle:

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

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 的源代码.

In Qt, you will find the source code for style N in src/gui/styles/N.cpp.

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

Each style contains the implementation of the elementary operations used for drawing everything in a GUI, from tree views to dropdown menus. The standard styles, such as QWindowsStyle, inherit most of their methods from QCommonStyle. Each particular style typically includes just minor deviations from that common base. Hence, a close study of qcommonstyle.cpp will reveal the base functionality that Qt developers found useful for painting all the parts of a GUI. Its importance is hard to overstate.

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

In what follows, we'll examine the parts relevant for drawing view items.

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

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

As mentioned above, understanding the basic mechanics of drawing a view requires examining the implementation of drawControl() in qcommonstyle.cpp, an implementation that starts on line 1197. Note in what follows, when I refer to a line number without mentioning a file name, by convention I am referring to qcommonstyle.cpp in the doxygenated code base.

QStyle.drawControl() 文档 具有指导意义:

QStyle.drawControl(element, option,painter)

参数:

  • element – QStyle.ControlElement

  • element – QStyle.ControlElement

option – QtGui.QStyleOption

option – QtGui.QStyleOption

画家 – PySide.QtGui.QPainter

painter – PySide.QtGui.QPainter

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

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.

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

The caller tells drawControl() what type of element it is trying to draw by passing it a QStyle.ControlElement flag. Control elements are higher-level components of a window that display information to the user: things like checkboxes, pushbuttons, and menu items. All of the control elements are enumerated here:

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

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

Recall the control element sent in the call to QStyledItemDelegate.paint() was CE_ItemViewItem, which is simply an item to be displayed inside an item view. Within QCommonStyle.drawControl(), the CE_ItemViewItem case starts on line 2153. Let's dig in.

A.subElementRect():尺寸很重要

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

It is key to get the size and layout of each item right. This is the first thing drawControl() calculates. To get this information, it invokes subElementRect() (defined on line 2313, and first called on line 2158). For example, we have:

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

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

The first argument is a QStyle.SubElement flag, in this case SE_ItemViewItemText. Style subelements represent constituent parts of control elements. Each item in a view has three possible subelements: the checkbox, the icon, and text; obviously, the SE_ItemViewItemText subelement represents the text. All possible subelements are enumerated here:

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

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

The subElementRect() method contains all the cases in the subelement enumeration: our SE_ItemViewItemText case starts on line 3015.

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

Note that subElementRect() returns a QRect, which defines a rectangle in the plane using integer precision, and can be constructed with four integers (left, top, width, height). For instance r1 = QRect(100, 200, 11, 16). This specifies, for a subelement, its size as well as the x,y position where it will be painted in the viewport.

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

subElementRect() actually calls viewItemLayout() (defined on line 999) to do the real work, which is a two-step process. First, viewItemLayout() calculates the height and width of subelements using viewItemSize(). Second, it calculates the x and y position of the subelement. Let's consider each of these operations in turn.

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

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

Starting on line 1003, viewItemLayout() calls viewItemSize() (defined on line 838), which calculates the height and width needed for a subelement.

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

Where does viewItemSize() get the default numbers for things like the height of the title bar? This is the province of the pixel metric. A pixel metric is a style-dependent size represented by a single pixel value. For instance, Style.PM_IndicatorWidth returns the width of a check box indicator, and QStyle.PM_TitleBarHeight returns the title bar height for your application's style. All the different QStyle.PixelMetrics are enumerated here:

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

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

You can retrieve the value of a given pixel metric using QStyle.pixelMetric(), which is used a lot in viewItemSize(). The first input of pixelMetric() is one of the QStyle.PixelMetrics in the enumeration. In qcommonstyle.cpp the implementation of pixelMetric() starts on line 4367.

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

For instance, viewItemSize() calculates the width and height of the checkbox (if needed) using the following:

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

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

Note that pixelMetric() is not just used in viewItemSize(), but is ubiquitous. It is used to calculate metric properties of many GUI elements, from window borders to icons, and is peppered throughout qcommonstyle.cpp. Basically, whenever you need to know how many pixels your style uses for some graphical element whose size doesn't change (like a checkbox), the style will call on pixel metrics.

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

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

The second part of viewItemLayout() is devoted to organizing the layout of the subelements whose width and height were just calculated. That is, it needs to find their x and y positions to finish filling the values into QRects such as textRect. The subelements will be organized differently depending on the general setup of the view (e.g., whether it is right-left or left-right oriented). Hence, viewItemLayout() calculates the final resting position of each subelement depending on such factors.

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

If you ever need clues about how to reimplement sizeHint() in a custom delegate, subElementRect() could be a useful source of tips and tricks. In particular, viewItemSize() is likely to contain useful tidbits and relevant pixel metrics you might want to query when you want a custom view to closely match the default.

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

Once the QRects are calculated for the text, icon, and checkbox subelements, drawControl() moves on, and finally starts to draw.

B.给背景上色:变得原始

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

First the background gets filled in for the item (line 2163):

drawPrimitive(PE_PanelItemViewItem, option, painter);

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

This is a lot like the call to QStyle.drawControl(), but the first argument is not a control element, but a primitive element (PE). Just as drawControl() is a large sprawling method that controls the drawing of all higher-level control elements, QStyle.drawPrimitive() draws most of the lower-level primitive graphical elements in a GUI (such as the rectangular background in an item view). These lower-level units, the primitive elements, are enumerated here:

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

文档说,原始元素是常见的 GUI 元素,例如复选框指示器或按钮斜角."例如,PE_PanelItemViewItem 基本元素是[T]he 项视图中项的背景."

The docs say, "A primitive element is a common GUI element, such as a checkbox indicator or button bevel." For instance, the PE_PanelItemViewItem primitive element is "[T]he background for an item in an item view."

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

Each style must have an implementation of drawPrimitive(), and ours starts on line 140. Therein, you can discover in great detail how it carries out its primitive paint operations. This is a useful source of hints for how to use the paint() command in practice in your custom delegates.

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

The reimplementation of QStyle.drawPrimitive() for the PE_PanelItemViewItem case begins on line 773. It first selects the appropriate background color based on the state of the item. It does this by querying the item's QStyle.StateFlag. The option.state contains the state flags that describe the state of the item at the time (is it enabled, selected, being edited, etc.?). These states are not just used in the back end, but you will likely need to use it when reimplementing QStyledItemDelegate.paint() in your custom delegates. You can find an enumeration of the QStyle.StateFlags here:

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

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

After picking the right color, drawPrimitive() then uses QPainter.fillRect() to fill the appropriate region with that color (line 786):

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

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

QPainter.fillRect() is a very useful method when implementing custom delegates for yourself.

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

After taking care of the background, drawControl() then proceeds to finish drawing the item, starting with the checkbox (line 2165), then the icon (line 2185), and finally the text (line 2194). We won't go over this in detail, but I will finish by briefly discussing how it draws the text.

C.绘制文字:叛逆

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

First, the state of the item is used to specify the text's color. This uses the QStyle.StateFlags just discussed. Then drawControl() kicks text drawing responsibilities out to a custom method viewItemDrawText() (defined at line 921):

viewItemDrawText(painter, vopt, textRect);

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

This method takes in the painter, option, and the text rectangle described above (part A) as parameters. Note the option parameter is very important: it is a QStyleOption class by which the content is passed around within a style (it includes icon, checkstate, and text properties).

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

After the text is pulled from option, it is incorporated into a QtGui.QTextLine that is added to a QtGui.QTextLayout. The final, elided (i.e., with ellipses, depending on word wrap settings) text is drawn by QPainter.drawText() (see line 983), which is one of Qt's primitive painting operations.

坦率地说,很多 viewItemDrawText() 用于处理自动换行.这就是我们开始了解 Qt 的一些内容的地方,Python 用户从来没有想过要看到这些内容,更不用说修补了.例如,它使用 QStackTextEngine 类.我鼓励您使用 Google 'qstacktextengine pyqt' 来了解 Python 用户很少出现这种情况.如果您对此感兴趣,那就来吧!

Frankly a good deal of viewItemDrawText() is spent dealing with word wrapping. This is where we start to get into some of the guts of Qt that no Python user was ever meant to see, much less tinker with. For instance, it uses the QStackTextEngine class. I encourage you to Google 'qstacktextengine pyqt' to see just how infrequently this has come up for Python users. If this interests you, have at it!

四.总结

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

If you want to access the underlying painting mechanics for the default delegate, you will end up studying the implementation of QStyle.drawControl() in qcommonstyle.cpp, a 6000-line beast of a file. This exercise can be very helpful for figuring out the exact procedures used to calculate sizes and draw the primitive graphical elements that items contain. Sometimes, however, this beast can be downright scary and unhelpful, like when it comes to dealing with word wrap. In those cases, you will probably just have to figure out a custom implementation of the desired functionality for your delegate.

最后,既然我们已经看到了事物如何运作的全局视图,我们可以更好地理解 QStyle 的文档是多么有用,特别是 项目视图中的样式.在那里,我们发现了以下具有启发性的爱情金块:

Finally, now that we've seen a big-picture view of how things work, we can better appreciate how helpful the documentation for QStyle is, in particular the section Styles in Item Views. There, we find the following revelatory love nugget:

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

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.

所以基本上原始帖子的答案是,他们说了什么."

So basically the answer to the original post is, "What they said."

这篇关于Qt 中项目视图的默认委托的机制是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆