More Related Content Similar to Model view-delegates-whitepaper Similar to Model view-delegates-whitepaper (20) Model view-delegates-whitepaper1. Qt 4’s Model/View
Delegates
A Whitepaper by Mark Summerfield
Phone: 617.621.0060
Integrated Computer
Solutions Incorporated Email: info@ics.com
The User Interface Company™ www.ics.com
2. Qt® 4's Model/View Delegates
A Whitepaper by Mark Summerfield
Table of Contents
Abstract ..................................................................................................................................3
Qt’s Model/View Architecture ...............................................................................................3
Controlling Item Appearance Using data() ............................................................................5
A Delegate for Numerically Coded Data ...............................................................................7
The Duplicate Problem.........................................................................................................11
The Generic Delegate Solution ............................................................................................11
The GenericDelegate Implementation..................................................................................13
The AbstractColumnDelegate Implementation....................................................................15
Two Example AbstractColumnDelegate Subclasses............................................................15
The DoubleColumnDelegate Implementation ............................................................15
The RichTextColumnDelegate Implementation .........................................................18
Conclusion............................................................................................................................20
Qt Training, Advanced Qt Components and Testing Tools .................................................21
QicsTable™ .................................................................................................................21
GraphPak™ ..................................................................................................................21
KD Executor™ .............................................................................................................21
KD Gantt™ ..................................................................................................................22
Motif to Qt Migration Assessment .............................................................................22
About ICS .............................................................................................................................22
About the Author ..................................................................................................................22
Note:
The source code associated with this whitepaper is Copyright © 2006 Qtrac Ltd. All rights reserved.
It is available for download at http://www.ics.com/developers/papers/.
This file is part of the Qtrac Ltd. set of training materials.
This file may be used under the terms of the GNU General Public License version 2.0 as published by
the Free Software Foundation or under the terms of the Qt Commercial License Agreement. The
respective license texts for these are provided with the open source and commercial editions of Qt.
This file is provided as is with no warranty of any kind. In particular, it has no warranty of design,
merchantability, or fitness for a particular purpose.
Copyright © 2006 Integrated Computer Solutions, Inc. All rights reserved. This document may not be
reproduced without written consent from Integrated Computer Solutions, Inc. Qt is a registered
trademark of Trolltech AS. QicsTable and GraphPak are trademarks of Integrated Computer Solutions,
Inc. All other trademarks are property of their owners.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 2 of 22
3. Abstract
This whitepaper introduces Qt 4's model/view/delegate architecture, with a brief look at models and
views. The main focus is on Qt 4's delegates—these are classes that provide complete control over the
presentation and editing of data items. The whitepaper shows how to customise the presentation of read-
only data purely through the data model. It also shows the implementation of a typical custom delegate to
control the presentation and editing of data in an editable model.
Unfortunately, the price to be paid for the power of Qt's delegates appears to be code duplication: it seems
that programmers must write a custom delegate for every single data model that is to be used with a view
and which requires custom editing behavior. Furthermore, these custom delegates will very often
duplicate each others code, with delegate after delegate having the same code for presenting and editing
integers, and for floating point values, for dates, and for text strings. This whitepaper presents a solution
to this code duplication problem. It shows a generic delegate class that can be used by any view and for
any model. The generic delegate allows programmers to achieve the same level of control over the
presentation and editing of fields as they enjoy with custom delegates, but without needing to duplicate
code, leading to smaller easier-to-maintain code bases.
Qt's Model/View Architecture
Data can be accessed from a variety of sources, for example, from files, over the network, and from
database connections. The presentation and editing of this data can often take many forms: for example, a
typical spreadsheet application will allow users to split a window so that two different parts of the
spreadsheet can be seen at the same time. Sometimes we want to provide different kinds of views of the
same data, for example a summary tabular view that just has a few key columns and a full view that
shows all of a dataset's columns. In all these cases the data comes from a single source (or at least from
what appears to be a single source from the program's point of view), and yet must be presented in many
different ways.
The model/view/controller architecture introduced by the Smalltalk programming language made a clear
separation between data and its presentation and editing. Data, no matter what the source, was abstracted
into a model—the model accessed the data by whatever means were appropriate, but always provided a
single uniform interface for other software components that wanted to access the data.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 3 of 22
4. This had many benefits. For example, since every model
provided the same interface, a model that wasn't ready
could easily be faked for testing purposes. And if the
underlying data source's interface changed, only the View
model would have to be updated, and all the client code
that used the model would continue to work without
requiring any changes. Another benefit was that views— Controller
typically widgets (“controls” in Windows-speak) that
presented data could interact with models; so no matter
what the underlying data and no matter what the model, Model
any view could be utilized because all views made use
of the uniform model interface.
Probably the biggest benefit offered by the model/view
approach is that the model only fetches or updates the
data that is actually required by the view—this makes it Dataset
feasible to operate on huge datasets, because even with
the largest available monitors, only a few hundred or
thousand items will be visible in the view at any one
time, and so only these will actually be fetched.
Users often need to interact with data, not just view it, and this is where the controller came in. It was a
software component that mediated between the user (mouse clicks, key presses) and the view, to request
the view to request the model to perform certain operations. For example, the controller might ask for the
data to be scrolled or might edit a value.
Qt 4 uses a model/view/delegate architecture that is very similar to the classic Smalltalk architecture. The
difference between a Qt delegate and a Smalltalk controller is that a delegate has access to both the view
and the model, and this makes it far more powerful and flexible than a controller.
Qt provides a set of abstract model classes that can be subclassed, and some concrete models that can be
used as-is. Similarly Qt provides a set of view classes that are suitable for subclassing, and a set of
concrete view widgets that contain built-in standard models
and which can be used directly.
Qt also provides a standard delegate with every view so in
many simple cases no delegate need be explicitly
instantiated. View
Every item in a Qt model has a unique model index
represented by the QModelIndex class. One-dimensional
Delegate
models (e.g., lists or vectors), only make use of the row
element; two-dimensional models (e.g., tables); make use
of the row and column elements; and hierarchical models
Model
(e.g., trees) make use of the row, column, and parent
elements.
The interface provided by Qt models includes many
methods, but the key ones are these:
Dataset
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 4 of 22
5. int rowCount(const QModelIndex &parent) const
int columnCount(const QModelIndex &parent) const
QVariant data(const QModelIndex &index, int role) const
QVariant headerData(int section, Qt::Orientation orient, int role) const
bool setData(const QModelIndex &index, const QVariant &value, int role)
The beauty of the model/view architecture concept is that no matter what the underlying dataset (in-
memory data structures, files, databases, etc.), Qt provides this simple uniform interface for accessing the
data items.
Any view can be used with any model, but not every combination makes sense. For example, presenting
hierarchical data in a QTableView will not provide the same convenience or clarity as presenting it in a
QTreeView. Any delegate can be used with any view, but in practice custom delegates are made for
particular models and only used with the views that are used with those models.
If we are working with a read-only model we can achieve a fair amount of control over the presentation of
items purely through our model's data() reimplementation. This approach allows us to control the font,
colors, and alignment of data items, without requiring a custom delegate at all, as we'll see in the next
section. But for total control we need a custom delegate, as we'll see in the later sections.
Controlling Item Appearance Using data()
The screenshot shows a QTableView presenting
data from a custom model. No custom delegate
is used. Instead the reimplementation of the
data() method colors rows according to their
category, colors weights according to their
magnitude, and aligns left or right according to
the column.
The custom model used in this example stores its
data items in a QMap<int, Part*> where a Part is
a class that has an ID, a category, a sub-
category, a description, and a weight, and which
is represented by one row in the model. Here's
the source code for the PartModel's data()
method:
QVariant PartTableModel::data(const QModelIndex &index, int role) const
{
// We ignore invalid indexes
if (!index.isValid() ||
index.row() < 0 || index.row() >= m_parts.count())
return QVariant();
// We extract the relevant Part object from our map
QMap<int, Part*>::const_iterator i = m_parts.constBegin() +
index.row();
if (i == m_parts.constEnd())
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 5 of 22
6. return QVariant();
Part *part = i.value();
// If the view wants data to display we provide it from the Part;
// we format floating point numbers and return them as strings
if (role == Qt::DisplayRole) {
switch (index.column()) {
case ID: return part->id();
case Category: return part->category();
case SubCategory: return part->subCategory();
case Description: return part->description();
case Weight: return QString("%1g")
.arg(part->weight(), 0, 'f', 1);
case Quantity: return part->quantity();
default: return QVariant();
}
}
// If the view wants to know how to align an item we return a
suitable
// alignment
else if (role == Qt::TextAlignmentRole) {
switch (index.column()) {
case ID: return int(Qt::AlignRight|Qt::AlignVCenter);
case Category: return int(Qt::AlignLeft|Qt::AlignVCenter);
case SubCategory: return int(Qt::AlignLeft|Qt::AlignVCenter);
case Description: return
int(Qt::AlignRight|Qt::AlignVCenter);
case Weight: return int(Qt::AlignRight|Qt::AlignVCenter);
case Quantity: return int(Qt::AlignRight|Qt::AlignVCenter);
case Created: return int(Qt::AlignRight|Qt::AlignVCenter);
case Updated: return int(Qt::AlignRight|Qt::AlignVCenter);
default: return QVariant();
}
}
// If the view wants to know what text color to use we return a value
// if it is asking about the Weight column. (Anything we don't handle
// can be safely ignored.)
else if (role == Qt::TextColorRole && index.column() == Weight)
return part->weight() < 100 ? QColor(Qt::darkMagenta) :
part->weight() < 1000 ? QColor(Qt::black) :
QColor(Qt::darkRed);
// We specify a background color according to the Part's category
else if (role == Qt::BackgroundColorRole) {
if (part->category() == QObject::tr("Kitchenware"))
return QColor(240, 220, 240);
else if (part->category() == QObject::tr("Stationary"))
return QColor(240, 240, 220);
else if (part->category() == QObject::tr("Tools"))
return QColor(220, 240, 240);
else
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 6 of 22
7. return QColor(200, 220, 220);
}
// If we haven't handled the request we simply return an
// invalid QVariant
return QVariant();
}
Using data() to provide control over item appearance is a perfectly good approach, but it does have two
disadvantages: If you're working with one of Qt's built-in concrete models, subclassing it just to
reimplement data() seems like overkill; and no matter how good the items look, this approach gives you
no control over editing at all. So now it is time to turn our attention to the creation of a delegate, and we'll
start by doing so in the standard way that the Qt documentation recommends.
A Delegate for Numerically Coded Data
There are many occasions
when we have a field that
can only hold a very
limited range of values.
Such fields are often
stored as chars, or shorts
to save space.
Unfortunately, in their
numeric form they aren't
much help to the user as
the screenshot shows.
In this example, columns, 1, 2, and 3 (with 3 not visible), must only have values from the set {-10, -5, 0,
5, 10}. In the next screenshot we've used a delegate to provide meaningful texts to represent those
numbers.
The delegate we've used
does two things. Firstly, it
presents the numbers as
meaningful texts, and
secondly, it provides a
means of editing the data
using a combobox
containing texts that map to
valid numbers. It also
ensures that column 0 is
read-only since we don't
want users to change the
survey questions.
Before we look at how the
delegate is implemented,
let's see how it is used.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 7 of 22
8. SurveyModel *model = new SurveyModel;
QTableView *view = new QTableView;
view->setModel(model);
view->setItemDelegate(new IntStringDelegate(model->valueToStringMap(),
QSet<int>() << 1 << 2 << 3));
view->resizeColumnsToContents();
The IntStringDelegate takes two arguments: a QMap<int, QString> that maps strings from integers, and a
set of column numbers to which the delegate should apply itself. In this example we've decided that the
model itself should provide the map: here's the method that returns it:
QMap<int, QString> SurveyModel::valueToStringMap() const
{
QMap<int, QString> map;
map[-10] = "Strongly disagree";
map[-5] = "Disagree";
map[0] = "Neither agree nor disagree";
map[5] = "Agree";
map[10] = "Strongly agree";
return map;
}
Our IntStringDelegate is a QItemDelegate subclass, and implements the same four methods that all
QItemDelegate subclasses implement, in addition to its own constructor. Here's the header:
class IntStringDelegate : public QItemDelegate
{
Q_OBJECT
public:
IntStringDelegate(const QMap<int, QString> &map,
const QSet<int> &columns, QObject *parent=0)
: QItemDelegate(parent), m_map(map), m_columns(columns) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private:
QMap<int, QString> m_map;
QSet<int> m_columns;
};
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 8 of 22
9. The constructor takes the map and the set of columns the delegate is to operate on, but passes all the work
on to the QItemDelegate base class. The signatures for the other methods are identical to those of any
other QItemDelegate; we'll now look at their implementations.
void IntStringDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index)
const
{
if (!m_columns.contains(index.column()))
return QItemDelegate::paint(painter, option, index);
int i = index.model()->data(index, Qt::DisplayRole).toInt();
drawDisplay(painter, option, option.rect, m_map.value(i));
drawFocus(painter, option, option.rect);
}
All the methods begin the same way: if the column isn't one the delegate has been told to work on, pass
the work on to the base class.
Here we extract the integer that corresponds to the item in the dataset, and instead of painting it we paint
the corresponding map value, i.e., the corresponding string, instead. Notice that we use the value()
method rather than operator[]; this is because if the integer isn't in the map value() will return a default-
constructed value (in this case an empty string), whereas operator[]'s behavior in such a case is undefined.
If the item does not have the focus, the drawFocus() call will do nothing.
QWidget *IntStringDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option, const QModelIndex &index)
const
{
if (!m_columns.contains(index.column()))
return QItemDelegate::createEditor(parent, option, index);
QComboBox *combobox = new QComboBox(parent);
QMapIterator<int, QString> i(m_map);
while (i.hasNext()) {
i.next();
combobox->addItem(i.value(), i.key());
}
combobox->installEventFilter(const_cast<IntStringDelegate*>(this));
return combobox;
}
If the user initiates editing (typically by pressing F2 or by double-clicking), the view calls createEditor().
We create a combobox and populate it with the strings from our map; the second argument for addItem()
is an optional “user value” in which we put the integer that corresponds to the string in our model. The
installEventFilter() call is used to ensure that the combobox responds properly to the mouse and
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 9 of 22
10. keyboard; in fact it is unnecessary in this case, and in many other cases, but we include the call because it
is recommended in the Qt documentation.
void IntStringDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
if (!m_columns.contains(index.column()))
return QItemDelegate::setEditorData(editor, index);
int i = index.model()->data(index, Qt::DisplayRole).toInt();
QComboBox *combobox = qobject_cast<QComboBox*>(editor);
combobox->setCurrentIndex(combobox->findData(i));
}
Once the combobox has been created, the view calls setEditorData(). We set the combobox's current
index to the index of the item that has “user data” corresponding to the item's data in the model. (We
could have used Qt::EditRole, but in many situations there's no practical difference between Qt::EditRole
and Qt::DisplayRole.)
void IntStringDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model, const QModelIndex &index)
const
{
if (!m_columns.contains(index.column()))
return QItemDelegate::setModelData(editor, model, index);
QComboBox *combobox = qobject_cast<QComboBox*>(editor);
QVariant i = combobox->itemData(combobox->currentIndex());
model->setData(index, i);
}
Once editing is complete, we extract the “user data” that corresponds to the selected combobox text. Since
we simply store the integer that corresponds with the text back in the model, and since setData() takes a
QVariant(), we don't waste time converting to and from an integer, but pass the integer wrapped as a
QVariant() just as the QComboBox:: itemData() returned it to us.
That completes the implementation of the IntStringDelegate. Like most delegates it isn't difficult to code.
And in addition to the example shown here, further examples are provided in the Qt documentation and in
the textbook, C++ GUI Programming with Qt 4.
The IntStringDelegate is also fairly versatile: it can be used with an arbitrary map of integer x string pairs,
and can be applied to an arbitrary set of columns. But what happens if we want to provide custom editing
of one of the columns that the IntStringDelegate is not asked to handle? We must either create a new
custom delegate from scratch or create a modified version of our IntStringDelegate. But let's look at a
more general and more common situation.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 10 of 22
11. The Duplicate Problem
Suppose we have a billing application that uses three different models. Here's the data held by each
model:
Customer Model Supplier Model Product Model
int id; int id; int id;
QString name; QString name; QString name;
QString address; QString address; QString description;
CreditRating rating; double creditLimit; double unitCost;
double creditLimit; double creditUsed; int quantityInStock;
double creditUsed;
We will need a custom delegate for each model so that we can control the range of numeric fields (for
example, not allow the quantityInStock field to be negative), and so on. These delegates will be quite
simple, probably 30-50 lines each (header and source included), the whole lot amounting to 100-150
lines. However, the code for handling the id field will be the same in each delegate; similarly the code for
handling the string fields. The code for handling the double fields will only differ in the range of values
allowed. So the majority of the code will be duplicated. And if we create other models for other
applications that also use integers, doubles, and strings, we'll have to create more custom delegates, and
duplicate more code.
Clearly there's a problem that needs to be solved.
The Generic Delegate Solution
What we'd really like to do is use the same delegate for every model's view, and to be able to write code
like this:
customerView->setModel(customerModel);
GenericDelegate *delegate = new GenericDelegate;
delegate->insertColumnDelegate(0, new ReadOnlyColumnDelegate(false));
delegate->insertColumnDelegate(1, new PlainTextColumnDelegate);
delegate->insertColumnDelegate(2, new PlainTextColumnDelegate);
delegate->insertColumnDelegate(3, new CreditRatingColumnDelegate);
delegate->insertColumnDelegate(4, new DoubleColumnDelegate(0.0,
10000.0));
delegate->insertColumnDelegate(5, new DoubleColumnDelegate(0.0,
10000.0));
customerView->setItemDelegate(delegate);
supplierView->setModel(supplierModel);
delegate = new GenericDelegate;
delegate->insertColumnDelegate(0, new ReadOnlyColumnDelegate(false));
delegate->insertColumnDelegate(1, new PlainTextColumnDelegate);
delegate->insertColumnDelegate(2, new PlainTextColumnDelegate);
delegate->insertColumnDelegate(3, new DoubleColumnDelegate(0.0,
10000.0));
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 11 of 22
12. delegate->insertColumnDelegate(4, new DoubleColumnDelegate(0.0,
10000.0));
supplierView->setItemDelegate(delegate);
productView->setModel(productModel);
delegate = new GenericDelegate;
delegate->insertColumnDelegate(0, new ReadOnlyColumnDelegate(false));
delegate->insertColumnDelegate(1, new PlainTextColumnDelegate);
delegate->insertColumnDelegate(2, new RichTextColumnDelegate);
delegate->insertColumnDelegate(3, new DoubleColumnDelegate(0.0, 1000.0));
delegate->insertColumnDelegate(4, new IntegerColumnDelegate(0, 1000));
productView->setItemDelegate(delegate);
In less than 30 lines we can achieve what would normally take 100-150 lines. And there is no code
duplication. We have one “ReadOnlyColumnDelegate” that we use for the id fields, we have one
“DoubleColumnDelegate” that we use for all the credit and cost fields, and so on. Once we've written the
common column delegates, i.e., for integers, doubles, plain text, rich text, string lists, dates, times, etc.,
we can reuse them again and again. In this particular example, the only column delegate that would need
to be written from scratch is the “CreditRatingColumnDelegate”.
Now we'll look at another example.
The screenshot shows a QTableView that's using a GenericDelegate. Column 0 uses a
ReadOnlyColumnDelegate with right-alignment; column 1 uses a PlainTextColumnDelegate, column 2
uses a RichTextColumnDelegate; column 3 uses a StringListColumnDelegate that provides a combobox
of valid strings to choose from; column 4 uses an IntegerColumnDelegate that provides a QSpinBox for
editing the quantities, and column 5 uses a DoubleColumnDelegate that provides a QDoubleSpinBox for
editing the weights. To get the presentation and editing of data we want for this model's view we used the
same GenericDelegate that we used for the three models shown earlier, and the same
AbstractColumnDelegate subclasses.
Now that we know what the problem is and what the solution looks like, let's see how to implement the
solution in practice.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 12 of 22
13. The GenericDelegate Implementation
The GenericDelegate class's implementation is surprisingly easy since it does no work itself. The header
is very similar to other QItemDelegate subclasses, except that we've added two new methods and some
private data.
class GenericDelegate : public QItemDelegate
{
Q_OBJECT
public:
GenericDelegate(QObject *parent=0) : QItemDelegate(parent) {}
~GenericDelegate();
void insertColumnDelegate(int column,
AbstractColumnDelegate *delegate);
void removeColumnDelegate(int column);
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private:
QMap<int, AbstractColumnDelegate*> m_delegates;
};
The purpose of the m_delegates map is to keep track of which columns have custom
AbstractColumnDelegates, as we'll see in a moment.
GenericDelegate::~GenericDelegate()
{
QMapIterator<int, AbstractColumnDelegate*> i(m_delegates);
while (i.hasNext()) {
AbstractColumnDelegate *delegate = i.next().value();
delete delegate;
}
}
The constructor was implemented in the header since all it did was call the base class constructor. But for
the destructor we need to do some work. In keeping with Qt programming style our GenericDelegate
class takes ownership of any AbstractColumnDelegate that is inserted into it. And for this reason we must
delete any AbstractColumnDelegates that are held by the GenericDelegate when it is destroyed.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 13 of 22
14. void GenericDelegate::insertColumnDelegate(int column,
AbstractColumnDelegate
*delegate)
{
AbstractColumnDelegate *olddelegate = m_delegates.value(column);
delete olddelegate;
m_delegates[column] = delegate;
}
When a new AbstractColumnDelegate is inserted it is possible that one was already defined for the
specified column. For this reason we attempt to retrieve the previously defined AbstractColumnDelegate.
The QMap::value() method returns the corresponding value from the map, unless the key isn't present, in
which case it returns a default constructed value. Since our values are pointers the default constructed
pointer value is 0, so if there was no previous AbstractColumnDelegate for the given column, the value()
call will return a 0 pointer. It is safe (and cheap) to call delete on a 0 pointer, so if there was a previous
value it is correctly deleted, and if there wasn't we've executed what amounts to a no-op. And at the end
we set the new AbstractColumnDelegate for the given column.
void GenericDelegate::removeColumnDelegate(int column)
{
if (m_delegates.contains(column)) {
AbstractColumnDelegate *olddelegate = m_delegates[column];
delete olddelegate;
m_delegates.remove(column);
}
}
This method is provided for completeness and is unlikely to be used in practice. If the user wants to stop
an AbstractColumnDelegate being used for a particular column, we delete it and remove it from the map.
From then on the GenericDelegate will call the QItemDelegate base class to handle the specified column's
data as we'll see next.
void GenericDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index)
const
{
AbstractColumnDelegate *delegate = m_delegates.value(index.column());
if (delegate)
delegate->paint(painter, option, index);
else
QItemDelegate::paint(painter, option, index);
}
In this method we first try to obtain an AbstractColumnDelegate for the given column. If we got the
delegate we pass on the painting to it; and if we didn't get it we pass on the painting to the base class. The
other GenericDelegate methods, createEditor(), setEditorData(), and setModelData(), all share the same
structure as this paint() method, (so we won't waste space showing them). This is why the
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 14 of 22
15. GenericDelegate is so simple: whenever there's work to be done it gives it to someone else. (Maybe we
should have called it the ShirkingDelegate.)
The AbstractColumnDelegate Implementation
The GenericDelegate relies on AbstractColumnDelegates. These are delegates that present and edit a
single datatype for a single column. The AbstractColumnDelegate is an abstract class completely defined
in the header file:
class AbstractColumnDelegate : public QItemDelegate
{
Q_OBJECT
public:
AbstractColumnDelegate(QObject *parent=0) : QItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{ QItemDelegate::paint(painter, option, index); }
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const = 0;
void setEditorData(QWidget *editor,
const QModelIndex &index) const = 0;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const = 0;
};
Just like the GenericDelegate, the AbstractColumnDelegate is a QItemDelegate subclass. We've chosen to
provide a default paint() implementation (that simply calls the base class), and to make the other methods
pure virtual. These design choices are debatable, and many other possibilities are just as valid.
Because the AbstractColumnDelegate class has pure virtual methods, AbstractColumnDelegate instances
cannot be instantiated, so we must provide suitable subclasses.
Two Example AbstractColumnDelegate Subclasses
Programmers who choose to use the GenericDelegate approach will probably create their own set of
common AbstractColumnDelegate subclasses. Here we present just two examples, the first,
DoubleColumnDelegate is typical, the second, RichTextColumnDelegate is a bit unusual.
The DoubleColumnDelegate Implementation
This AbstractColumnDelegate is used for presenting and editing floating point values. It can be given a
minimum and a maximum value, and the number of digits to display after the decimal point. A more
sophisticated version that allowed for prefix and suffix text to be shown (for example, units of weight, or
currency symbols), could easily be created based on the code shown here.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 15 of 22
16. class DoubleColumnDelegate : public AbstractColumnDelegate
{
public:
DoubleColumnDelegate(double minimum=0.0, double maximum=1.0e6,
int decimals=2)
: AbstractColumnDelegate(), m_minimum(minimum),
m_maximum(maximum),
m_decimals(decimals) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private:
double m_minimum;
double m_maximum;
int m_decimals;
};
The header contains no surprises; the constructor accepts the minimum, maximum, and number of
decimal digits, and the four standard delegate methods are reimplemented.
void DoubleColumnDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index)
const
{
QStyleOptionViewItem myOption = option;
myOption.displayAlignment = Qt::AlignRight|Qt::AlignVCenter;
double value = index.model()->data(index,
Qt::DisplayRole).toDouble();
drawDisplay(painter, myOption, myOption.rect,
QString("%1").arg(value, 0, 'f', m_decimals));
}
When painting we can either paint using the convenience drawDisplay() method, or directly using the
painter that's passed in. Here we use drawDisplay() and simply change the alignment to right-alignment
since we prefer that for numbers. Then we extract the value and paint it as a string in floating point 'f'
format, using the number of decimal digits we were given in the constructor. We haven't bothered to call
drawFocus() since focus is shown perfectly well without it.
QWidget *DoubleColumnDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &, const QModelIndex &)
const
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 16 of 22
17. {
QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
editor->setRange(m_minimum, m_maximum);
editor->installEventFilter(const_cast<DoubleColumnDelegate*>(this));
return editor;
}
When the user initiates editing, e.g., by pressing F2 or by double-clicking, we create an editor for the
data. We use a QDoubleSpinBox and set its range in accordance with the minimum and maximum the
DoubleColumnDelegate was constructed with. We set the event filtering because that's recommended in
the Qt documentation.
For completeness we'll show the two remaining methods, although their code is exactly what we'd expect.
void DoubleColumnDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
double value = index.model()->data(index,
Qt::DisplayRole).toDouble();
QDoubleSpinBox *spinbox = qobject_cast<QDoubleSpinBox*>(editor);
spinbox->setValue(value);
}
Once the editor has been created the view calls setEditorData() to initialise it. We simply extract the
relevant item from the model and set the QDoubleSpinBox accordingly.
void DoubleColumnDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model, const QModelIndex &index)
const
{
QDoubleSpinBox *spinbox = qobject_cast<QDoubleSpinBox*>(editor);
spinbox->interpretText();
model->setData(index, spinbox->value());
}
Once the user has finished editing (assuming they didn't cancel by pressing Esc), we set the
corresponding item in the model to the QDoubleSpinBox's value.
The code patterns used here are exactly the same as those used for any kind of delegate, the only
difference being that we don't check which column we're operating on or the validity of the model indexes
that are passed in. We don't care about the column because we know that the GenericDelegate will always
use the correct AbstractColumnDelegate for the item's column.
And if we are concerned about model index validity, the best place to check is in the GenericDelegate;
that will provide checking for all current and future AbstractColumnDelegates and will avoid duplicating
checking code in each AbstractColumnDelegate. For example we could recode GenericDelegate::paint()
like this:
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 17 of 22
18. void GenericDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index)
const
{
AbstractColumnDelegate *delegate = m_delegates.value(index.column());
if (delegate && index.isValid())
return delegate->paint(painter, option, index);
QItemDelegate::paint(painter, option, index);
}
And we could apply a similar approach for all the other GenericDelegate methods that receive model
indexes.
The RichTextColumnDelegate Implementation
This class is structurally similar to the DoubleColumnDelegate, or indeed to any other delegate, as the
header shows.
class RichTextColumnDelegate : public AbstractColumnDelegate
{
public:
RichTextColumnDelegate();
~RichTextColumnDelegate() { delete m_label; }
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private:
QLabel *m_label;
};
The only unusual thing here is that we hold a pointer to a private QLabel. This label is used for rendering
rich text and is never actually shown. (In Qt 4.2 we could use a QTextDocument instead, but we've
chosen to use a QLabel since that will work with both Qt 4.1 and Qt 4.2.) We will create the label in the
constructor, which is why we must delete it in the destructor.
RichTextColumnDelegate::RichTextColumnDelegate()
: AbstractColumnDelegate()
{
m_label = new QLabel;
m_label->setTextFormat(Qt::RichText);
}
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 18 of 22
19. In the constructor we create the label and tell it that any text it is given is “rich text”, i.e., simple HTML.
void RichTextColumnDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index)
const
{
QString text = index.model()->data(index,
Qt::DisplayRole).toString();
QPalette palette(m_label->palette());
palette.setColor(QPalette::Active, QPalette::Window,
option.state & QStyle::State_Selected ?
Qt::lightGray
: Qt::white);
m_label->setPalette(palette);
m_label->setFixedSize(option.rect.width(), option.rect.height());
m_label->setText(text);
painter->drawPixmap(option.rect, QPixmap::grabWidget(m_label));
}
The paint() method is more interesting than those we've seen before. We retrieve the relevant item's text
and then create a palette whose background color (QPalette::Window color) depends on whether the item
is selected or not. We set the label's palette and text and then use grabWidget() to get a pixmap image of
the label with the text rendered on it. The grabWidget() method tells the widget it is passed to paint itself,
and supplies a pixmap on which the painting takes place. We then simply paint the pixmap with the
rendered text in the space occupied by the item. (This means that we avoid having to parse the HTML and
render it ourselves.)
We won't show the remaining RichTextColumnDelegate methods, createEditor(), setEditorData(), and
setModelData(), since they follow the same pattern as those in the delegates we've already seen. The only
difference is that we use a RichTextLineEdit as our editor. Qt does not provide a single line
RichTextLineEdit widget, but it isn't difficult to create our own by subclassing QTextEdit. (The code for
an example RichTextLineEdit implementation is included in the source package that accompanies this
whitepaper.)
Unfortunately, there is a problem with our RichTextColumnDelegate. When the view asks for a rich text
item's size hint (by calling data() with role == SizeHintRole), the size hint that is returned is based upon
the current font and the length of the item's text. But with rich text the lengths are “wrong”. For example,
the rich text, “<font color='blue'>Blue Item</font>”, is 35 characters long, but only the 9 characters of
“Blue Text”, are actually displayed.
To solve this problem we must modify the data() method for any model that uses rich text to ensure that
the correct size hint is returned. If we have a model that stores “components” and has a “description” field
that holds rich text, we might add this code to the component model's data() method:
else if (role == Qt::SizeHintRole && index.column() == Description) {
Component *component = m_components[index.row()];
QFontMetrics fm(qApp->font());
QRegExp regex("<[^>]+>");
regex.setMinimal(true);
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 19 of 22
20. return QSize(fm.width(component->description()
.replace(regex, "") + "W"),
fm.height());
}
In this snippet we get the application's font, and get the font metrics based on the text with all HTML tags
removed. We don't have to worry about HTML entities like “©” because Qt uses Unicode so doesn't
have them (and doesn't need them). We actually pad out the width by the width of a single 'W' just to
give some extra spacing.
Windows users using Qt 4.1.4 or earlier may not get good results because there appears to be some
inaccuracy in Windows font metrics reporting. This will no doubt be solved in later Qt versions, but if
you need to use an unfixed version you could use an #ifdef #else #endif block to use a different algorithm
for calculating the Windows metric, for example:
#ifdef Q_WS_WIN
return QSize(int(fm.width(text.replace(regex, "")) * 2),
fm.height());
#else
return QSize(fm.width(text.replace(regex, "") + "W"),
fm.height());
#endif
Conclusion
Qt's model/view/delegate architecture is powerful, flexible, and easy-to-use. And the use of delegates
gives programmers complete control over the presentation and editing of data items. Unfortunately, the
standard approach to implementing delegates inevitably leads to code duplication and to the writing of far
more code than is strictly necessary.
As the two AbstractColumnDelegate subclasses presented above have shown, creating a family of
AbstractColumnDelegate subclasses for use with a GenericDelegate is not difficult. The code provided
with this whitepaper includes simple classes for presenting and editing integers, floating point numbers,
plain text strings, rich text strings, and string lists. These classes can easily be enhanced and made more
sophisticated, and they can also serve as models for the creation of similar classes for handling columns
of dates, times, date/times, and for custom datatypes.
The generic delegates/abstract column delegates approach shown in this whitepaper provides similar
control and versatility as the standard approach, but avoids code duplication, encourages code reuse,
reduces overall code size, and therefore eases code maintenance.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 20 of 22
21. Qt Training, Advanced Qt Components and Testing Tools
Training
As Trolltech’s preferred training partner for North America, ICS provides public and customized on-site
training on Qt. See details at http://www.ics.com/training/
ICS also provides a growing selection of advanced components and testing tools for Qt. Some of our
products include:
QicsTable™
Power to easily manipulate the largest data sets and with all of the display
and print functions you need to satisfy even the most demanding end-
users, this advanced Table component comes with a comprehensive API
that makes programming a snap. MVC architecture assures that your
application is easy to build, modify, and maintain.
Free trial at http://www.ics.com/download/evaluations/
GraphPak™
A collection of powerful charts and graphs that makes it easy to visually
present complex data.
Free trial at http://www.ics.com/download/evaluations/
KD Executor™
A true cross-platform testing harness that makes it easy to fully test your
Qt applications.
Free trial at http://www.ics.com/download/evaluations/
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 21 of 22
22. KD Gantt™
A graphics library add-on to Qt that eliminates the need to write custom
charting code by providing all of the graphics, layout, linkage, and end
user facilities needed to create Gantt charts.
Free trial at http://www.ics.com/download/evaluations/
Motif to Qt Migration Assessment
ICS is also the world’s foremost expert in Motif! Let our experts in Motif
and Qt guide you through the migration process.
Contact us at info@ics.com to discuss your needs.
About ICS
Driven by the belief that the success of any software application ultimately depends on the quality of the
user interface, Integrated Computer Solutions, Inc., The User Interface Company™, of Bedford, MA, is
the world’s leading provider of advanced user-interface development tools and services for professional
software engineers in the aerospace, petrochemical, transportation, military, communications,
entertainment, scientific, and financial industries. ICS' BX series of GUI builders has been long
recognized as the software of choice for visually developing mission-critical, high-performance Motif
applications. ICS is the largest independent supplier of add-on products to the Qt multi-platform
framework developed by Trolltech. Supporting the software development community since 1987, ICS
also provides custom UI development services, custom UI component development, training, consulting,
and project porting and implementation services. More information about ICS can be found at
http://www.ics.com
About the Author
Mark Summerfield is Director of Qtrac Ltd. (http://www.qtrac.eu/). Mark worked as Trolltech's
documentation manager for almost three years, and during that time he co-wrote the official Qt textbook,
C++ GUI Programming with Qt 3. Since leaving Mark has done consultancy work for Trolltech,
specifically to co-write C++ GUI Programming with Qt 4. Mark has been committed to Qt since joining
Trolltech in 2000, and is a strong advocate of Qt as the best tool for writing GUI applications.
Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 22 of 22