SlideShare a Scribd company logo
1 of 22
Download to read offline
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
{
             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
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
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
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 “&copy;” 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
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
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

More Related Content

Viewers also liked

Best Practices in Qt Quick/QML - Part III
Best Practices in Qt Quick/QML - Part IIIBest Practices in Qt Quick/QML - Part III
Best Practices in Qt Quick/QML - Part IIIICS
 
Best Practices in Qt Quick/QML - Part I
Best Practices in Qt Quick/QML - Part IBest Practices in Qt Quick/QML - Part I
Best Practices in Qt Quick/QML - Part IICS
 
Optimizing Performance in Qt-Based Applications
Optimizing Performance in Qt-Based ApplicationsOptimizing Performance in Qt-Based Applications
Optimizing Performance in Qt-Based Applicationsaccount inactive
 
Informal stakeholder meeting_malawi-rashid
Informal stakeholder meeting_malawi-rashidInformal stakeholder meeting_malawi-rashid
Informal stakeholder meeting_malawi-rashidJulien Grollier
 
Arany kezek(11)+ani (nx power lite)
Arany kezek(11)+ani (nx power lite)Arany kezek(11)+ani (nx power lite)
Arany kezek(11)+ani (nx power lite)VarganeAnny
 
Arany kezek(24)+ani (nx power lite)
Arany kezek(24)+ani (nx power lite)Arany kezek(24)+ani (nx power lite)
Arany kezek(24)+ani (nx power lite)VarganeAnny
 
стартовая со скриншотами по ид
стартовая со скриншотами по идстартовая со скриншотами по ид
стартовая со скриншотами по идБирюкова Н.И.
 
Javascript testing
Javascript testingJavascript testing
Javascript testingTCS bank
 
Arany kezek(31)+ani (nx power lite)
Arany kezek(31)+ani (nx power lite)Arany kezek(31)+ani (nx power lite)
Arany kezek(31)+ani (nx power lite)VarganeAnny
 
Arany kezek(40)+ani (nx power lite)
Arany kezek(40)+ani (nx power lite)Arany kezek(40)+ani (nx power lite)
Arany kezek(40)+ani (nx power lite)VarganeAnny
 
Responsible Travel: We have to learn before we can help!
Responsible Travel: We have to learn before we can help!Responsible Travel: We have to learn before we can help!
Responsible Travel: We have to learn before we can help!Daniela Papi
 
Projekty na lekcji języka obcego.
Projekty na lekcji języka obcego.Projekty na lekcji języka obcego.
Projekty na lekcji języka obcego.Monika Wisła
 
Peningkatan profesionalisme guru melalui reflektif teaching
Peningkatan profesionalisme guru melalui reflektif teachingPeningkatan profesionalisme guru melalui reflektif teaching
Peningkatan profesionalisme guru melalui reflektif teachingVivi Vey
 
El Sistema Solar
El Sistema SolarEl Sistema Solar
El Sistema Solarcrafols
 
Discussion 2 tools
Discussion 2 toolsDiscussion 2 tools
Discussion 2 toolschadjans
 
Quentin Leiper 2006-07 Weblog
Quentin Leiper 2006-07 WeblogQuentin Leiper 2006-07 Weblog
Quentin Leiper 2006-07 Weblogkatfyt
 
Japan social conversation in the customer journey
Japan social conversation in the customer journeyJapan social conversation in the customer journey
Japan social conversation in the customer journeyAkihisa Kamishiro
 

Viewers also liked (20)

Best Practices in Qt Quick/QML - Part III
Best Practices in Qt Quick/QML - Part IIIBest Practices in Qt Quick/QML - Part III
Best Practices in Qt Quick/QML - Part III
 
Best Practices in Qt Quick/QML - Part I
Best Practices in Qt Quick/QML - Part IBest Practices in Qt Quick/QML - Part I
Best Practices in Qt Quick/QML - Part I
 
Optimizing Performance in Qt-Based Applications
Optimizing Performance in Qt-Based ApplicationsOptimizing Performance in Qt-Based Applications
Optimizing Performance in Qt-Based Applications
 
La india
La indiaLa india
La india
 
Informal stakeholder meeting_malawi-rashid
Informal stakeholder meeting_malawi-rashidInformal stakeholder meeting_malawi-rashid
Informal stakeholder meeting_malawi-rashid
 
Rafa 2
Rafa 2Rafa 2
Rafa 2
 
Happy new year
Happy new yearHappy new year
Happy new year
 
Arany kezek(11)+ani (nx power lite)
Arany kezek(11)+ani (nx power lite)Arany kezek(11)+ani (nx power lite)
Arany kezek(11)+ani (nx power lite)
 
Arany kezek(24)+ani (nx power lite)
Arany kezek(24)+ani (nx power lite)Arany kezek(24)+ani (nx power lite)
Arany kezek(24)+ani (nx power lite)
 
стартовая со скриншотами по ид
стартовая со скриншотами по идстартовая со скриншотами по ид
стартовая со скриншотами по ид
 
Javascript testing
Javascript testingJavascript testing
Javascript testing
 
Arany kezek(31)+ani (nx power lite)
Arany kezek(31)+ani (nx power lite)Arany kezek(31)+ani (nx power lite)
Arany kezek(31)+ani (nx power lite)
 
Arany kezek(40)+ani (nx power lite)
Arany kezek(40)+ani (nx power lite)Arany kezek(40)+ani (nx power lite)
Arany kezek(40)+ani (nx power lite)
 
Responsible Travel: We have to learn before we can help!
Responsible Travel: We have to learn before we can help!Responsible Travel: We have to learn before we can help!
Responsible Travel: We have to learn before we can help!
 
Projekty na lekcji języka obcego.
Projekty na lekcji języka obcego.Projekty na lekcji języka obcego.
Projekty na lekcji języka obcego.
 
Peningkatan profesionalisme guru melalui reflektif teaching
Peningkatan profesionalisme guru melalui reflektif teachingPeningkatan profesionalisme guru melalui reflektif teaching
Peningkatan profesionalisme guru melalui reflektif teaching
 
El Sistema Solar
El Sistema SolarEl Sistema Solar
El Sistema Solar
 
Discussion 2 tools
Discussion 2 toolsDiscussion 2 tools
Discussion 2 tools
 
Quentin Leiper 2006-07 Weblog
Quentin Leiper 2006-07 WeblogQuentin Leiper 2006-07 Weblog
Quentin Leiper 2006-07 Weblog
 
Japan social conversation in the customer journey
Japan social conversation in the customer journeyJapan social conversation in the customer journey
Japan social conversation in the customer journey
 

Similar to Model view-delegates-whitepaper

Lecture 18 - Model-Driven Service Development
Lecture 18 - Model-Driven Service DevelopmentLecture 18 - Model-Driven Service Development
Lecture 18 - Model-Driven Service Developmentphanleson
 
Software development effort reduction with Co-op
Software development effort reduction with Co-opSoftware development effort reduction with Co-op
Software development effort reduction with Co-oplbergmans
 
IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...
IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...
IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...IRJET Journal
 
Software architecture-patterns
Software architecture-patternsSoftware architecture-patterns
Software architecture-patternspedro
 
Software arquitectura patron diseño
Software arquitectura patron diseñoSoftware arquitectura patron diseño
Software arquitectura patron diseñopedro
 
software-architecture-patterns
software-architecture-patternssoftware-architecture-patterns
software-architecture-patternsPallav Kumar
 
Dot Net Fundamentals
Dot Net FundamentalsDot Net Fundamentals
Dot Net FundamentalsLiquidHub
 
Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...
Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...
Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...IRJET Journal
 
Software Architecture and Project Management module III : PATTERN OF ENTERPRISE
Software Architecture and Project Management module III : PATTERN OF ENTERPRISESoftware Architecture and Project Management module III : PATTERN OF ENTERPRISE
Software Architecture and Project Management module III : PATTERN OF ENTERPRISEsreeja_rajesh
 
System verilog important
System verilog importantSystem verilog important
System verilog importantelumalai7
 
Actively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net DeveloperActively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net DeveloperKarthik Reddy
 
Actively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net DeveloperActively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net DeveloperKarthik Reddy
 
MAD Model Aggregator eDitor (EMF)
MAD Model Aggregator eDitor (EMF)MAD Model Aggregator eDitor (EMF)
MAD Model Aggregator eDitor (EMF)Sysord
 
ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010
ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010
ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010vchircu
 

Similar to Model view-delegates-whitepaper (20)

Lecture 18 - Model-Driven Service Development
Lecture 18 - Model-Driven Service DevelopmentLecture 18 - Model-Driven Service Development
Lecture 18 - Model-Driven Service Development
 
The caQtDM powerpoint (ICALEPCS 2013)
The caQtDM powerpoint (ICALEPCS 2013)The caQtDM powerpoint (ICALEPCS 2013)
The caQtDM powerpoint (ICALEPCS 2013)
 
The caQtDm paper (ICALEPCS 2013)
The caQtDm paper (ICALEPCS 2013)The caQtDm paper (ICALEPCS 2013)
The caQtDm paper (ICALEPCS 2013)
 
MVC
MVCMVC
MVC
 
Software development effort reduction with Co-op
Software development effort reduction with Co-opSoftware development effort reduction with Co-op
Software development effort reduction with Co-op
 
IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...
IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...
IRJET- Generation of HTML Code using Machine Learning Techniques from Mock-Up...
 
Microservices.pdf
Microservices.pdfMicroservices.pdf
Microservices.pdf
 
Software architecture-patterns
Software architecture-patternsSoftware architecture-patterns
Software architecture-patterns
 
Software arquitectura patron diseño
Software arquitectura patron diseñoSoftware arquitectura patron diseño
Software arquitectura patron diseño
 
software-architecture-patterns
software-architecture-patternssoftware-architecture-patterns
software-architecture-patterns
 
Dot Net Fundamentals
Dot Net FundamentalsDot Net Fundamentals
Dot Net Fundamentals
 
Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...
Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...
Limited Budget but Effective End to End MLOps Practices (Machine Learning Mod...
 
Software Architecture and Project Management module III : PATTERN OF ENTERPRISE
Software Architecture and Project Management module III : PATTERN OF ENTERPRISESoftware Architecture and Project Management module III : PATTERN OF ENTERPRISE
Software Architecture and Project Management module III : PATTERN OF ENTERPRISE
 
System verilog important
System verilog importantSystem verilog important
System verilog important
 
Actively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net DeveloperActively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net Developer
 
Actively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net DeveloperActively looking for an opportunity to work as a challenging Dot Net Developer
Actively looking for an opportunity to work as a challenging Dot Net Developer
 
MAD Model Aggregator eDitor (EMF)
MAD Model Aggregator eDitor (EMF)MAD Model Aggregator eDitor (EMF)
MAD Model Aggregator eDitor (EMF)
 
Orchard
OrchardOrchard
Orchard
 
ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010
ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010
ChircuVictor StefircaMadalin rad_aspmvc3_wcf_vs2010
 
ASP.NET MVC3 RAD
ASP.NET MVC3 RADASP.NET MVC3 RAD
ASP.NET MVC3 RAD
 

Model view-delegates-whitepaper

  • 1. 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 “&copy;” 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