PixelDelegate Class Implementation
The PixelDelegate constructor is used to set up a default value for the size of each "pixel" that it renders. The base class constructor is also called to ensure that the delegate is set up with a parent object, if one is supplied:
PixelDelegate::PixelDelegate(QObject *parent)
: QAbstractItemDelegate(parent)
{
pixelSize = 12;
}
Each item is rendered by the delegate's paint() function. The view calls this function with a ready-to-use QPainter object, style information that the delegate should use to correctly draw the item, and an index to the item in the model:
void PixelDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
The first task the delegate has to perform is to draw the item's background correctly. Usually, selected items appear differently to non-selected items, so we begin by testing the state passed in the style option and filling the background if necessary.
The radius of each circle is calculated in the following lines of code:
int size = qMin(option.rect.width(), option.rect.height());
int brightness = index.model()->data(index, Qt::DisplayRole).toInt();
double radius = (size/2.0) - (brightness/255.0 * size/2.0);
if (radius == 0.0)
return;
First, the largest possible radius of the circle is determined by taking the smallest dimension of the style option's rect attribute. Using the model index supplied, we obtain a value for the brightness of the relevant pixel in the image. The radius of the circle is calculated by scaling the brightness to fit within the item and subtracting it from the largest possible radius.
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(Qt::NoPen);
We save the painter's state, turn on antialiasing (to obtain smoother curves), and turn off the pen.
if (option.state & QStyle::State_Selected)
painter->setBrush(option.palette.highlightedText());
else
painter->setBrush(QBrush(Qt::black));
The foreground of the item (the circle representing a pixel) must be rendered using an appropriate brush. For unselected items, we will use a solid black brush; selected items are drawn using a predefined brush from the style option's palette.
painter->drawEllipse(QRectF(option.rect.x() + option.rect.width()/2 - radius,
option.rect.y() + option.rect.height()/2 - radius,
2*radius, 2*radius));
painter->restore();
}
Finally, we paint the circle within the rectangle specified by the style option and we call restore() on the painter.
The paint() function does not have to be particularly complicated; it is only necessary to ensure that the state of the painter when the function returns is the same as it was when it was called. This usually means that any transformations applied to the painter must be preceded by a call to QPainter::save() and followed by a call to QPainter::restore().
The delegate's sizeHint() function returns a size for the item based on the predefined pixel size, initially set up in the constructor:
QSize PixelDelegate::sizeHint(const QStyleOptionViewItem & ,
const QModelIndex & ) const
{
return QSize(pixelSize, pixelSize);
}
The delegate's size is updated whenever the pixel size is changed. We provide a custom slot to do this:
void PixelDelegate::setPixelSize(int size)
{
pixelSize = size;
}
Using The Custom Delegate
In this example, we use a main window to display a table of data, using the custom delegate to render each cell in a particular way. Much of the MainWindow class performs tasks that are not related to item views. Here, we only quote the parts that are relevant. You can look at the rest of the implementation by following the links to the code at the top of this document.
In the constructor, we set up a table view, turn off its grid, and hide its headers:
MainWindow::MainWindow()
{
...
view = new QTableView;
view->setShowGrid(false);
view->horizontalHeader()->hide();
view->verticalHeader()->hide();
view->horizontalHeader()->setMinimumSectionSize(1);
view->verticalHeader()->setMinimumSectionSize(1);
view->setModel(model);
This enables the items to be drawn without any gaps between them. Removing the headers also prevents the user from adjusting the sizes of individual rows and columns.
We also set the minimum section size to 1 on the headers. If we didn't, the headers would default to a larger size, preventing us from displaying really small items (which can be specified using the Pixel size combobox).
The custom delegate is constructed with the main window as its parent, so that it will be deleted correctly later, and we set it on the table view.
PixelDelegate *delegate = new PixelDelegate(this);
view->setItemDelegate(delegate);
Each item in the table view will be rendered by the PixelDelegate instance.
We construct a spin box to allow the user to change the size of each "pixel" drawn by the delegate:
QLabel *pixelSizeLabel = new QLabel(tr("Pixel size:"));
QSpinBox *pixelSizeSpinBox = new QSpinBox;
pixelSizeSpinBox->setMinimum(4);
pixelSizeSpinBox->setMaximum(32);
pixelSizeSpinBox->setValue(12);
This spin box is connected to the custom slot we implemented in the PixelDelegate class. This ensures that the delegate always draws each pixel at the currently specified size:
connect(pixelSizeSpinBox, SIGNAL(valueChanged(int)),
delegate, SLOT(setPixelSize(int)));
connect(pixelSizeSpinBox, SIGNAL(valueChanged(int)),
this, SLOT(updateView()));
...
}
We also connect the spin box to a slot in the MainWindow class. This forces the view to take into account the new size hints for each item; these are provided by the delegate in its sizeHint() function.
void MainWindow::updateView()
{
view->resizeColumnsToContents();
view->resizeRowsToContents();
}
We explicitly resize the columns and rows to match the Pixel size combobox.