• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Model view-delegates-whitepaper
 

Model view-delegates-whitepaper

on

  • 1,093 views

 

Statistics

Views

Total Views
1,093
Views on SlideShare
1,092
Embed Views
1

Actions

Likes
0
Downloads
67
Comments
0

1 Embed 1

http://www.slideshare.net 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Model view-delegates-whitepaper Model view-delegates-whitepaper Document Transcript

    • Qt 4’s Model/View Delegates A Whitepaper by Mark Summerfield Phone: 617.621.0060 Integrated Computer Solutions Incorporated Email: info@ics.comThe User Interface Company™ www.ics.com
    • Qt® 4s Model/View DelegatesA Whitepaper by Mark SummerfieldTable 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 ..................................................................................................................22Note: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 bereproduced without written consent from Integrated Computer Solutions, Inc. Qt is a registeredtrademark 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
    • AbstractThis whitepaper introduces Qt 4s model/view/delegate architecture, with a brief look at models andviews. The main focus is on Qt 4s delegates—these are classes that provide complete control over thepresentation 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 tocontrol the presentation and editing of data in an editable model.Unfortunately, the price to be paid for the power of Qts delegates appears to be code duplication: it seemsthat programmers must write a custom delegate for every single data model that is to be used with a viewand which requires custom editing behavior. Furthermore, these custom delegates will very oftenduplicate each others code, with delegate after delegate having the same code for presenting and editingintegers, and for floating point values, for dates, and for text strings. This whitepaper presents a solutionto this code duplication problem. It shows a generic delegate class that can be used by any view and forany model. The generic delegate allows programmers to achieve the same level of control over thepresentation and editing of fields as they enjoy with custom delegates, but without needing to duplicatecode, leading to smaller easier-to-maintain code bases.Qts Model/View ArchitectureData can be accessed from a variety of sources, for example, from files, over the network, and fromdatabase connections. The presentation and editing of this data can often take many forms: for example, atypical spreadsheet application will allow users to split a window so that two different parts of thespreadsheet can be seen at the same time. Sometimes we want to provide different kinds of views of thesame data, for example a summary tabular view that just has a few key columns and a full view thatshows all of a datasets columns. In all these cases the data comes from a single source (or at least fromwhat appears to be a single source from the programs point of view), and yet must be presented in manydifferent ways.The model/view/controller architecture introduced by the Smalltalk programming language made a clearseparation between data and its presentation and editing. Data, no matter what the source, was abstractedinto a model—the model accessed the data by whatever means were appropriate, but always provided asingle 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 modelprovided the same interface, a model that wasnt readycould easily be faked for testing purposes. And if theunderlying data sources interface changed, only the Viewmodel would have to be updated, and all the client codethat used the model would continue to work withoutrequiring any changes. Another benefit was that views— Controllertypically widgets (“controls” in Windows-speak) thatpresented data could interact with models; so no matterwhat the underlying data and no matter what the model, Modelany view could be utilized because all views made useof the uniform model interface.Probably the biggest benefit offered by the model/viewapproach is that the model only fetches or updates thedata that is actually required by the view—this makes it Datasetfeasible to operate on huge datasets, because even withthe largest available monitors, only a few hundred orthousand items will be visible in the view at any onetime, 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 asoftware component that mediated between the user (mouse clicks, key presses) and the view, to requestthe view to request the model to perform certain operations. For example, the controller might ask for thedata 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. Thedifference between a Qt delegate and a Smalltalk controller is that a delegate has access to both the viewand 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 beused as-is. Similarly Qt provides a set of view classes that are suitable for subclassing, and a set ofconcrete view widgets that contain built-in standard modelsand which can be used directly.Qt also provides a standard delegate with every view so inmany simple cases no delegate need be explicitlyinstantiated. ViewEvery item in a Qt model has a unique model indexrepresented by the QModelIndex class. One-dimensional Delegatemodels (e.g., lists or vectors), only make use of the rowelement; two-dimensional models (e.g., tables); make useof the row and column elements; and hierarchical models Model(e.g., trees) make use of the row, column, and parentelements.The interface provided by Qt models includes manymethods, but the key ones are these: DatasetCopyright© 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 thedata items.Any view can be used with any model, but not every combination makes sense. For example, presentinghierarchical data in a QTableView will not provide the same convenience or clarity as presenting it in aQTreeView. Any delegate can be used with any view, but in practice custom delegates are made forparticular 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 ofitems purely through our models data() reimplementation. This approach allows us to control the font,colors, and alignment of data items, without requiring a custom delegate at all, as well see in the nextsection. But for total control we need a custom delegate, as well see in the later sections.Controlling Item Appearance Using data()The screenshot shows a QTableView presentingdata from a custom model. No custom delegateis used. Instead the reimplementation of thedata() method colors rows according to theircategory, colors weights according to theirmagnitude, and aligns left or right according tothe column.The custom model used in this example stores itsdata items in a QMap<int, Part*> where a Part isa class that has an ID, a category, a sub-category, a description, and a weight, and whichis represented by one row in the model. Heresthe source code for the PartModels 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 dont 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 Parts 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); elseCopyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 6 of 22
    • return QColor(200, 220, 220); } // If we havent 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 twodisadvantages: If youre working with one of Qts built-in concrete models, subclassing it just toreimplement data() seems like overkill; and no matter how good the items look, this approach gives youno control over editing at all. So now it is time to turn our attention to the creation of a delegate, and wellstart by doing so in the standard way that the Qt documentation recommends.A Delegate for Numerically Coded DataThere are many occasionswhen we have a field thatcan only hold a verylimited range of values.Such fields are oftenstored as chars, or shortsto save space.Unfortunately, in theirnumeric form they arentmuch help to the user asthe 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 weve used a delegate to provide meaningful texts to represent thosenumbers.The delegate weve useddoes two things. Firstly, itpresents the numbers asmeaningful texts, andsecondly, it provides ameans of editing the datausing a comboboxcontaining texts that map tovalid numbers. It alsoensures that column 0 isread-only since we dontwant users to change thesurvey questions.Before we look at how thedelegate is implemented,lets 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 aset of column numbers to which the delegate should apply itself. In this example weve decided that themodel itself should provide the map: heres 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 allQItemDelegate subclasses implement, in addition to its own constructor. Heres 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 workon to the QItemDelegate base class. The signatures for the other methods are identical to those of anyother QItemDelegate; well 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 isnt one the delegate has been told to work on, passthe 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 paintthe 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 isnt 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. TheinstallEventFilter() call is used to ensure that the combobox responds properly to the mouse andCopyright© 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 itis 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 comboboxs currentindex to the index of the item that has “user data” corresponding to the items data in the model. (Wecould have used Qt::EditRole, but in many situations theres no practical difference between Qt::EditRoleand 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. Sincewe simply store the integer that corresponds with the text back in the model, and since setData() takes aQVariant(), we dont waste time converting to and from an integer, but pass the integer wrapped as aQVariant() just as the QComboBox:: itemData() returned it to us.That completes the implementation of the IntStringDelegate. Like most delegates it isnt difficult to code.And in addition to the example shown here, further examples are provided in the Qt documentation and inthe 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 editingof one of the columns that the IntStringDelegate is not asked to handle? We must either create a newcustom delegate from scratch or create a modified version of our IntStringDelegate. But lets look at amore general and more common situation.Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 10 of 22
    • The Duplicate ProblemSuppose we have a billing application that uses three different models. Heres the data held by eachmodel: 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 (forexample, not allow the quantityInStock field to be negative), and so on. These delegates will be quitesimple, probably 30-50 lines each (header and source included), the whole lot amounting to 100-150lines. However, the code for handling the id field will be the same in each delegate; similarly the code forhandling the string fields. The code for handling the double fields will only differ in the range of valuesallowed. So the majority of the code will be duplicated. And if we create other models for otherapplications that also use integers, doubles, and strings, well have to create more custom delegates, andduplicate more code.Clearly theres a problem that needs to be solved.The Generic Delegate SolutionWhat wed really like to do is use the same delegate for every models view, and to be able to write codelike 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 codeduplication. 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 weve written thecommon 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 needto be written from scratch is the “CreditRatingColumnDelegate”.Now well look at another example.The screenshot shows a QTableView thats using a GenericDelegate. Column 0 uses aReadOnlyColumnDelegate with right-alignment; column 1 uses a PlainTextColumnDelegate, column 2uses a RichTextColumnDelegate; column 3 uses a StringListColumnDelegate that provides a comboboxof valid strings to choose from; column 4 uses an IntegerColumnDelegate that provides a QSpinBox forediting the quantities, and column 5 uses a DoubleColumnDelegate that provides a QDoubleSpinBox forediting the weights. To get the presentation and editing of data we want for this models view we used thesame GenericDelegate that we used for the three models shown earlier, and the sameAbstractColumnDelegate subclasses.Now that we know what the problem is and what the solution looks like, lets see how to implement thesolution in practice.Copyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 12 of 22
    • The GenericDelegate ImplementationThe GenericDelegate classs implementation is surprisingly easy since it does no work itself. The headeris very similar to other QItemDelegate subclasses, except that weve added two new methods and someprivate 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 customAbstractColumnDelegates, as well 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 forthe destructor we need to do some work. In keeping with Qt programming style our GenericDelegateclass takes ownership of any AbstractColumnDelegate that is inserted into it. And for this reason we mustdelete 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 thespecified 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 isnt present, inwhich case it returns a default constructed value. Since our values are pointers the default constructedpointer 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 previousvalue it is correctly deleted, and if there wasnt weve executed what amounts to a no-op. And at the endwe 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 stopan 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 columnsdata as well 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 thedelegate we pass on the painting to it; and if we didnt get it we pass on the painting to the base class. Theother GenericDelegate methods, createEditor(), setEditorData(), and setModelData(), all share the samestructure as this paint() method, (so we wont waste space showing them). This is why theCopyright© 2006 Integrated Computer Solutions, Inc. www.ics.com Page 14 of 22
    • GenericDelegate is so simple: whenever theres work to be done it gives it to someone else. (Maybe weshould have called it the ShirkingDelegate.)The AbstractColumnDelegate ImplementationThe GenericDelegate relies on AbstractColumnDelegates. These are delegates that present and edit asingle datatype for a single column. The AbstractColumnDelegate is an abstract class completely definedin 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. Weve chosen toprovide a default paint() implementation (that simply calls the base class), and to make the other methodspure virtual. These design choices are debatable, and many other possibilities are just as valid.Because the AbstractColumnDelegate class has pure virtual methods, AbstractColumnDelegate instancescannot be instantiated, so we must provide suitable subclasses.Two Example AbstractColumnDelegate SubclassesProgrammers who choose to use the GenericDelegate approach will probably create their own set ofcommon AbstractColumnDelegate subclasses. Here we present just two examples, the first,DoubleColumnDelegate is typical, the second, RichTextColumnDelegate is a bit unusual.The DoubleColumnDelegate ImplementationThis AbstractColumnDelegate is used for presenting and editing floating point values. It can be given aminimum and a maximum value, and the number of digits to display after the decimal point. A moresophisticated version that allowed for prefix and suffix text to be shown (for example, units of weight, orcurrency 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 ofdecimal 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 thepainter thats passed in. Here we use drawDisplay() and simply change the alignment to right-alignmentsince we prefer that for numbers. Then we extract the value and paint it as a string in floating point fformat, using the number of decimal digits we were given in the constructor. We havent bothered to calldrawFocus() since focus is shown perfectly well without it. QWidget *DoubleColumnDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) constCopyright© 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 thedata. We use a QDoubleSpinBox and set its range in accordance with the minimum and maximum theDoubleColumnDelegate was constructed with. We set the event filtering because thats recommended inthe Qt documentation.For completeness well show the two remaining methods, although their code is exactly what wed 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 therelevant 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 didnt cancel by pressing Esc), we set thecorresponding item in the model to the QDoubleSpinBoxs value.The code patterns used here are exactly the same as those used for any kind of delegate, the onlydifference being that we dont check which column were operating on or the validity of the model indexesthat are passed in. We dont care about the column because we know that the GenericDelegate will alwaysuse the correct AbstractColumnDelegate for the items 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 duplicatingchecking 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 modelindexes.The RichTextColumnDelegate ImplementationThis class is structurally similar to the DoubleColumnDelegate, or indeed to any other delegate, as theheader 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 renderingrich text and is never actually shown. (In Qt 4.2 we could use a QTextDocument instead, but wevechosen to use a QLabel since that will work with both Qt 4.1 and Qt 4.2.) We will create the label in theconstructor, 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 weve seen before. We retrieve the relevant items textand then create a palette whose background color (QPalette::Window color) depends on whether the itemis selected or not. We set the labels palette and text and then use grabWidget() to get a pixmap image ofthe 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 therendered text in the space occupied by the item. (This means that we avoid having to parse the HTML andrender it ourselves.)We wont show the remaining RichTextColumnDelegate methods, createEditor(), setEditorData(), andsetModelData(), since they follow the same pattern as those in the delegates weve already seen. The onlydifference is that we use a RichTextLineEdit as our editor. Qt does not provide a single lineRichTextLineEdit widget, but it isnt difficult to create our own by subclassing QTextEdit. (The code foran example RichTextLineEdit implementation is included in the source package that accompanies thiswhitepaper.)Unfortunately, there is a problem with our RichTextColumnDelegate. When the view asks for a rich textitems size hint (by calling data() with role == SizeHintRole), the size hint that is returned is based uponthe current font and the length of the items 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 thatthe correct size hint is returned. If we have a model that stores “components” and has a “description” fieldthat holds rich text, we might add this code to the component models 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 applications font, and get the font metrics based on the text with all HTML tagsremoved. We dont have to worry about HTML entities like “&copy;” because Qt uses Unicode so doesnthave them (and doesnt need them). We actually pad out the width by the width of a single W just togive some extra spacing.Windows users using Qt 4.1.4 or earlier may not get good results because there appears to be someinaccuracy in Windows font metrics reporting. This will no doubt be solved in later Qt versions, but ifyou need to use an unfixed version you could use an #ifdef #else #endif block to use a different algorithmfor 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()); #endifConclusionQts model/view/delegate architecture is powerful, flexible, and easy-to-use. And the use of delegatesgives programmers complete control over the presentation and editing of data items. Unfortunately, thestandard approach to implementing delegates inevitably leads to code duplication and to the writing of farmore code than is strictly necessary.As the two AbstractColumnDelegate subclasses presented above have shown, creating a family ofAbstractColumnDelegate subclasses for use with a GenericDelegate is not difficult. The code providedwith 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 moresophisticated, and they can also serve as models for the creation of similar classes for handling columnsof dates, times, date/times, and for custom datatypes.The generic delegates/abstract column delegates approach shown in this whitepaper provides similarcontrol 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 ToolsTrainingAs Trolltech’s preferred training partner for North America, ICS provides public and customized on-sitetraining 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 ourproducts include:QicsTable™Power to easily manipulate the largest data sets and with all of the displayand print functions you need to satisfy even the most demanding end-users, this advanced Table component comes with a comprehensive APIthat makes programming a snap. MVC architecture assures that yourapplication 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 visuallypresent 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 yourQt 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 customcharting code by providing all of the graphics, layout, linkage, and enduser facilities needed to create Gantt charts.Free trial at http://www.ics.com/download/evaluations/Motif to Qt Migration AssessmentICS is also the world’s foremost expert in Motif! Let our experts in Motifand Qt guide you through the migration process.Contact us at info@ics.com to discuss your needs.About ICSDriven by the belief that the success of any software application ultimately depends on the quality of theuser interface, Integrated Computer Solutions, Inc., The User Interface Company™, of Bedford, MA, isthe world’s leading provider of advanced user-interface development tools and services for professionalsoftware engineers in the aerospace, petrochemical, transportation, military, communications,entertainment, scientific, and financial industries. ICS BX series of GUI builders has been longrecognized as the software of choice for visually developing mission-critical, high-performance Motifapplications. ICS is the largest independent supplier of add-on products to the Qt multi-platformframework developed by Trolltech. Supporting the software development community since 1987, ICSalso provides custom UI development services, custom UI component development, training, consulting,and project porting and implementation services. More information about ICS can be found athttp://www.ics.comAbout the AuthorMark Summerfield is Director of Qtrac Ltd. (http://www.qtrac.eu/). Mark worked as Trolltechsdocumentation 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 joiningTrolltech 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