tl;dr
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.
Long-form answer
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.
I. The default delegate is QStyledItemDelegate
The QStyledItemDelegate
is the default delegate for items in views. This is stated clearly in 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.
The docs for QStyledItemDelegate
provide a little more detail:
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.
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
.
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);
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.
II. QStyle: what gives the delegate its style?
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:
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.
In Qt, you will find the source code for style N in src/gui/styles/N.cpp
.
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.
III. QStyle.drawControl(): performing endoscopy on the delegate
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.
The documentation for QStyle.drawControl() is instructive:
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.
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
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(): size matters
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);
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
The subElementRect()
method contains all the cases in the subelement enumeration: our SE_ItemViewItemText
case starts on line 3015.
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()
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(): calculating the width and height of subelements
Starting on line 1003, viewItemLayout()
calls viewItemSize()
(defined on line 838), which calculates the height and width needed for a subelement.
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.PixelMetric
s are enumerated here:
http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#PixelMetric-enum
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.PixelMetric
s in the enumeration. In qcommonstyle.cpp
the implementation of pixelMetric()
starts on line 4367.
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));
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(): Rubik's Cube the subelements to get x/y positions
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 QRect
s 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.
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.
Once the QRect
s are calculated for the text, icon, and checkbox subelements, drawControl()
mov