qt - Qt中项目 View 的默认委托(delegate)的机制是什么?

标签 qt pyqt pyside

精简版

QTreeView使用的默认委托是什么?特别是我正在尝试找到其paint()方法?

较长的版本

我是Python用户(Pyside / PyQt),并且正在使用自定义委托来重新创建QTreeView的某些功能。因此,我试图找到在QTreeView中使用的默认委托和绘画方法。更好的办法是对其进行解释。

交叉岗位

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

最佳答案

tl; dr

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



长格式答案

那些喜欢简洁的人应该阅读上面的tl; dr和documentation 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

paint()中的qstyleditemdleegate.cpp方法在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():对委托执行内窥镜检查

如上所述,了解绘制视图的基本机制要求检查drawControl()qcommonstyle.cpp的实现,该实现始于第1197行。在以下提到的内容中,当我指代行号而未提及文件名时,按照惯例,我指的是qcommonstyle.cpp in the doxygenated code base

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


QStyle.drawControl(element,option,painter)

参数:


元素– QStyle.ControlElement
选项– QtGui.QStyleOption
画家– PySide.QtGui.QPainter


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


调用方通过向其传递drawControl()标志来告知QStyle.ControlElement尝试绘制哪种元素。控件是窗口的高层组件,向用户显示信息:复选框,按钮和菜单项之类的信息。这里列举了所有控制元素:

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.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元素(从窗口边框到图标)的度量属性,并且在整个qcommonstyle.cpp中使用。基本上,每当您需要知道样式用于大小不变的某些图形元素(例如复选框)使用多少像素时,样式就会调用像素度量。

2. viewItemLayout():魔方将子元素多维数据集获取x / y位置

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

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

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

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.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);


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

从选项中提取文本后,将其合并到添加到QtGui.QTextLineQtGui.QTextLayout中。最后的被省略的文本(即,用椭圆表示,取决于自动换行设置)由QPainter.drawText()绘制(请参阅第983行),这是Qt的原始绘画操作之一。

坦白说,很多viewItemDrawText()用于处理自动换行。这是我们开始了解一些Qt胆量的地方,这是Python用户从未想过的,更不用说修补了。例如,它使用QStackTextEngine类。我鼓励您使用Google'qstacktextengine pyqt',以了解Python用户很少遇到这种情况。如果您对此感兴趣,请尝试一下!

IV。摘要

如果要访问默认委托的基础绘画技巧,您将最终研究文件中的6000行野兽QStyle.drawControl()qcommonstyle.cpp的实现。此练习对于弄清楚用于计算尺寸和绘制项目所包含的原始图形元素的确切过程非常有帮助。但是,有时候,这头野兽可能会变得非常恐怖和无助,例如在处理自动换行时。在这种情况下,您可能只需要为委托人找出所需功能的自定义实现即可。

最后,既然我们已经对事物的工作原理有了一个大的了解,我们可以更好地理解QStyle的文档,特别是Styles in Item Views部分的帮助。在那里,我们发现了以下启示性的爱情金块:


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


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

关于qt - Qt中项目 View 的默认委托(delegate)的机制是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33990029/

相关文章:

python - PyQt5:使用事件绘制多个矩形

python - 未能在qt快速应用程序中构建图形管道状态

python - SQLite 准备好的查询与未准备的查询 - 不同的结果(PySide,python)

用于 JavaScript 的 QT 5.4 WebEngine 开发工具

c++ - windows下如何设置Qt程序的图标?

c++ - 如何在Windows中使用Valgrind和vera++

qt - 动画高度

events - Pyqt:如何获得 QLineEdit 处于焦点的信号?

python - QStyledItemDelegate 在QTableView中显示QComboBox

python - pySide:ExtensionLoader_Pyside_QtGUI.py找不到指定的模块