Tải bản đầy đủ (.pdf) (45 trang)

The book of qt 4 the art of building qt applications - phần 6 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (521.17 KB, 45 trang )

8 DisplayingDataUsing “Interview”
Q_UNUSED(parent);
returnaddressBook.count() -2;
}
To determine thenumber of columns,welook at thedataset from th efirstlineof
theCSV file. TheQStringList::cou nt() method is used to determine thenumber of
stringsinthe string listthatcontainsthe dataset corresponding to thefirstlineof
thefile,obtainedfromaddressBook byinvokingat(0):
// addressbook/addressbookmodel.cpp (continued)
intAddressbookModel::columnCount(const QModelIndex&parent)const
{
Q_UNUSED(parent);
returnaddressBook.at(0).count();
}
Viewsthatuse ouraddressbook modelcan discover thelabelingofthe rowsand
columns via theheaderData()method. To do so,theymustspecifythenumeric
positionofthe section of datafor which theheading is desired, where asection
is either arowor acolumn—whether thedesired heading is arowheading or
acolumn heading is decidedbythevalue given for theorientation.Thisisof
theenumeration type Qt::Orientation andhas thepossible valuesQt::Vertical or
Qt::Horizontal.
When it comestoroles,inthisexampleweare interested onlyin supporting the
DisplayRole,which is used whenthe viewneedsthe texttobedisplayed.Everything
else wepassontothe implementation of theoverclass. QAbstractTableModel does
more than just return emptyQVariants: If wehad notreimplemented headerData(),
it wou ld number allthe rowsand columns.Inorder to ensure that wecan use
themodellater withaQTableViewthat also queriesrowdescriptions,wecallthe
headerData()function of theoverclass, particularlyin casethe orientationis not
horizontal. This meansthatbydefa ult, alabel to theleftofthe datasets willdenote
thedataset number.For horizontalorientation weuse theentries from thefirst
dataset in thelist, which as weknowcontains thecolumn names:


// addressbook/addressbookmodel.cpp (continued)
QVariantAddressbookModel::headerData(intsection,
Qt::Orientation orientation, introle)const
{
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole) {
returnaddressBook.at(0).at(section);
}
}
returnQAbstractTableModel::headerData(section, orientation, role);
}
224
8.4 ImplementingYourOwn Models
Finally,the modeldeliversactualdatatoaviewvia thedata()method. As argu-
mentswepassaQModelIndex,which contains thepositionrequested bytheview,
andthe requested role:
// addressbook/addressbookmodel.cpp (continued)
QVariantAddressbookModel::data(const QModelIndex&index,
introle)const
{
if (!index.isValid()) returnQVariant();
QStringList addressRecord=addressBook.at(index.row()+1);
if (role == Qt::DisplayRole || role == Qt::EditRole) {
returnaddressRecord.at(index.column());
}
if (role == Qt::ToolTipRole) {
QString tip,key,value;
tip="<table>";
intmaxLines=addressRecord.count();
for(inti=0; i<maxLines; i++) {

key=headerData(i, Qt::Horizontal, Qt::DisplayRole)
.toString();
value=addressRecord.at(i);
if (!value.isEmpty())
tip+=QString("<tr><td><b>%1</b>: %2</td></tr>")
.arg(key,value);
}
tip+="</table>";
returntip;
}
returnQVariant();
}
Firstwecheck that theindexpassedisvalid—a good practice that prevents nasty
crashesinInterviewprogramming,since out-of-rangeindices can alwaysoccur.
Then weretrievethe desireddataset from thelist. In doing this weaccess the
dataset followingthe oneinthe rowspecified bytheindex—after all, thecolumn
headingsare in thefirstrow.
We deliver an address dataset ourselves if theviewasks fordatainthe DisplayRole.
To extract this ,weproceed in almost exactlythesamewayas beforewhenreading
outthe headings, withone difference: We localizethe dataset via therow() detail
of theindex.
Since themodelissupposed to be editable,wemustalsohandlethe situation
in which theviewasks fordat ainthe EditRole:Because thesam etextshould
appearlater on in theeditorasinthe DisplayRole,wehandlethe EditRole-and
Qt::DisplayRole queriesinone go.
To make theexamplealittlemoreinteresting,wewillalsoimplement theToolTi p-
Role at this point.Atooltip is ayellowboxthat containsadescriptionofaview
225
8 DisplayingDataUsing “Interview”
elementand appears if you lingerover it withthe mouse. In thetooltipfor alisten-

try,our address book viewwill showthecomponen ts of thedataset corresponding
to that entry,asdepicted in Figure 8.10.Itisour aimtodisplayonlythenonempty
components in thedataset.
Figure 8.10:
Thanks to the
treatment of th e
ToolTipRole ,the views
nowshowan
individual tooltip
when themouse
lingers over an entry.
HTML formatting can be used in tooltiptexts,and weconstruct thedescription of
theaddressbook entryusingthe <table>tag. Each rowof thetable consists of
twocells,one of which contains thenameofthe address book field (the key)and
theother,the matching value.Bothofthemwillbeshowninthe tooltiponlyif the
value is notthe emptystring.Weobtainthe keybycallingthe headerData()method
just implemented,and weobtainthe value byreading it outofthe currentdataset.
We formatthe cells usingthe <tr>and <td>tags andappend theresulting HTML
phrase to th estringthatwestarted previouslywiththe table tag. Finally,when
allofthe fieldsofthe dataset havebeen processed, wecomplete thetooltiptext
string byappending theclosing </table>tag, andreturn thefinished string.
We willnowwrite aprogram to testour model. This reads in theCSV file, allocates
amodel, andpassesthe file’scontents to themodelasaQString. We then bind
themodeltoalistview,atable view,and atreeview.The result is showninFigure
8.11.
// addressbook/main.cpp
#include <QtGui>
#include "addressbookmodel.h"
intmain(intargc, char
*

argv[] )
{
QApplication app( argc, argv);
// Open the addressbook file in the working directory
QFile file("addressbook.csv");
if (!file.open(QIODevice::ReadOnly|QIODevice::Text) )
return1;
// Readits contentintoastring
QString addresses=QString::fromUtf8(file.readAll());
226
8.4 ImplementingYourOwn Models
AddressbookModel model(addresses);
QListViewlistView;
listView.setModel(&model);
listView.setModelColumn(0);
listView.show();
QTreeViewtreeView;
treeView.setModel(&model);
treeView.show();
QTableViewtableView;
tableView.setModel(&model);
tableView.show();
returnapp.exec();
}
Thecolumn to be displayed bythelistviewcan be selected from themodelwitha
setModelColumn() instruction; for example, setModelColumn(2)would displayall
first namesinsteadofthe formatted name.
Figure 8.11:
Thesethree views use
ouraddressbook

model.
If amodelyou havewritten yourselfdoesnot turn outtoworkasyou wished,
you should first checkwhether theoverriddenconst methods havebeen declared
properly.Since theyshould merelyprovideinformation aboutthe model, Interview
doesnot givethemanywrite accesstothe class. If theconst keywordismissing,
thecorrect method willnot be available,because th einheritancemechanism of
C++makesadistinctionbetween constand non-constversionsofamethod.
8.4.2Making Your OwnModels Writable
Just outputting datainthe EditRole is notenoughifyou wanttomodifythedata
source via themodel. In ordertobeable do this,weneed to overwrite theflags()
andsetData()methods.
flags() returnsspecific properties of an indexentry,so-called flags (see Table 8.2).
Viewsuse this method to checkwhether operationsare allowed for aspecific item.
227
8 DisplayingDataUsing “Interview”
Table8.2:
ItemFlags formodels
Value Effect
Qt::ItemIsSelectable Elementcan be selected
Qt::ItemIsEditable Elementcan be modified
Qt::ItemIsDragEnabledElement can be used as astartingpoint for drag-
and-drop operations
Qt::ItemIsDropEnabledElement can be used as atargetfor drag-and-
drop operations
Qt::ItemIsUserCheckable Elementhas aselection status withtwostates
(selected,deselected); requires theimplementa-
tion of Qt::CheckStateRole in themodel
Qt::ItemIsEnabledElement reactstouserrequests
Qt::ItemIsTristate Elementhas aselection status withthree states
(selected,not selected,partiallyselected); use-

fulinhierarchical models where several child en-
triesare select ed andothersnot selected;requires
theimplementationofQt::CheckStateRole in the
modelinadvance
We willcomeback to theservices of this method on page 241, whenweequip our
modelwithdrag-and-drop capabilityfor data. Here wemustfirstmake allthe cells
editable:
// addressbook/addressbookmodel.cpp (continued)
Qt::ItemFlagsAddressbookModel::flags(const QModelIndex&index) const
{
if (!index.isValid())
return0;
returnQAbstractItemModel::flags(index) |Qt::ItemIsEditable;
}
Nowtheusercan edit everyposition. To do this,the viewsuse aQItemDelegateby
default, just as forthe display.Onceithas finishedits work, it calls thesetData()
method to storethe newdatainthe model. As soon as this hasstoredthe dat a
successfullyin themodel, it returnstrue.
setData() is thecounterpart to data(): Bothfunctionsmustworktogether.Since
thestandardimplementationofsetData()doesnothing more than return false,we
need to reimplementitasfollows:
// addressbook/addressbookmodel.cpp (continued)
228
8.4 ImplementingYourOwn Models
bool AddressbookModel::setData(const QModelIndex&index,
const QVariant&value, introle)
{
if (index.isValid() && (role == Qt::EditRole ||
role == Qt::DisplayRole)) {
// add 1tothe rowindextoskipoverthe header

addressBook[index.row()+1][index.column()]=value.toString();
emitdataChanged(index,index);
returntrue;
}
returnfalse;
}
Firstwecheck, as always, whether theindexis valid.Thistimewealsoensurethat
weare locatedinthe editingorinthe displayrole.Wedonot havetomake a
distinctionbetween thesetworoles for ouraddressbook model, because in both
cases thesamestringisinvolved.Other models mayneed to make adistinction
between thetworoles;for example, whendeliveringorupdatingimage data, a
modelmayneed to workwithactualpixmaps for theDisplayRole,but withpaths
to pixmaps wheninthe EditRole.
If theseconditionsapply,weset thenewvalue,passedvia value,atthe appropriate
point.Hereweuse theindexoperator[], insteadofat()asusual,inorder to avoid
thenormallydesirable behaviorofat(): Themethod provides onlyaconst reference
to thestring, whereas theindexoperatorprovides asimplereference.
5
Aftersuccessfullychanging thedata, it is important that thedataChanged() signal
is emitted so that theviewslinkedtothe modelupdatethe data. This demands
twoindices as parameters, of which therowandcolumn propertiesshouldform
arectangle.Because usuallyonlyonevalue is beingchanged at atime, wepass
thesameindextwiceinorder to indicate thepositionofthe corrected dataitem.
Finallywesignalthe successful completion of theprocess withretu rn true.Inall
othercases,inwhich wehavenot saved anything, weconsequentlyreturn false.
Insertingand RemovingRows
So that themodelwillbecompletelyflexible,weimplement theinsertion andre-
moval of rows. To do this weoverwrite theinsertRows()and removeRows()meth-
ods.The equivalentremoveColumns() andaddColumns() methods also existtore-
moveorinsertcolumns,but weare notconcerned withthese at this point.As

parameterswepassanindexto therowbeneathwhich wewishtoinsertempty
rows, as wellasthe number of rowstobeinserted.Wecan safelyignore theparent
argument.
To insert an emptyrow,wefirstneed to create an emptydataset. To do this wefill
astringlistwithasmanyemptystringsasthere arecolumns in themodel. Then we
5
On th is subject,see alsopage400 in AppendixB.
229
8 DisplayingDataUsing “Interview”
informthe model, withbeginInsertRows(), that wewanttoinsertrows. If wedo
notdothis, existing selections in this modelcould getmixed up.Next, weinsertthe
emptydataset—againincrementingthe rowby1because of theheader—andend
theinsertmode.Finally,wesignalthatthe datahavebeen successfullyinserted,by
returningtrue:
// addressbook/addressbookmodel.cpp (continued)
bool AddressbookModel::insertRows(introw,intcount,
const QModelIndex&parent)
{
Q_UNUSED(parent);
QStringList emptyRecord;
for(inti=0; i<columnCount(QModelIndex()); i++)
emptyRecord.append(QString());
beginInsertRows(QModelIndex(),row,row+count-1);
for(inti=0; i<count; i++)
addressBook.insert(row+1, emptyRecord);
endInsertRows();
returntrue;
}
We implementremoveRows()inthe same way,but withasafetycheckinthiscase:
If thereare more lines to be removed than thereare datasets in theaddressbook,

wereturn false—otherwise, wewou ld runthe risk of theapplicationcrashing. We
also need to announcethe removal of lines andsignalthe endofthe action. If
everythingwas successful,wereturn true to thecallerasaconfirmation:
// addressbook/addressbookmodel.cpp (continued)
bool AddressbookModel::removeRows(introw,intcount,
const QModelIndex&parent)
{
Q_UNUSED(parent);
if (row-count-1 >addressBook.count()-1)returnfalse;
beginRemoveRows(QModelIndex(),row,row+count-1);
for(inti=0; i<count; i++)
addressBook.removeAt(row+1);
endRemoveRows();
returntrue;
}
With thesechanges,aprogram that accessesthe modelcan delete datasets by
callingremoveRows()oradd th em byinsertingemptydatasets withinsertRows()
andfillingthemvia setData()—themethod is notreserved just for delegates.
230
8.5 Sortingand FilteringDatawithProxyModels
Outputting theContents of theModel
In ordertoround off ourmodeland to complete ourtourthrough theworld of
writable models,weconstruct amethod calledtoString(), which converts thecon-
tents of themodelback into CSVformand outputsthisasastring.
To do this wegothrough allthe datasets anduse theQStringList method join to
combineeach string listintoasingle lineinwhich theindividualstrings aresep-
arated from oneanother withcommas(,).Weterminate each linewithanewline
characterbeforebeginning thenextline, which ensuresthatthe desiredemptyline
at theend of theCSV fileiscreated:
// addressbook/addressbookmodel.cpp (continued)

QString AddressbookModel::toString() const
{
QString ab;
foreach (QStringList record, addressBook) {
ab +=" \ "";
record.join("\ ", \ "");
ab +=" \ " \ n";
}
returnab;
}
To savethe currentstatusofthe model, you nowonlyneed to savethe return value
from toString().
8.5Sortingand Filtering Datawith ProxyModels
Ourmodelupuntil nowhaslacked th ecapabilityto return itsentries to aviewin
asorted form. This is because thereisnosorting criterioninthismodelfor anyof
thecolumns.Itisalsopracticallyimpossible to filter outspecific entriesfromthe
model.
To address this shortcoming, Interviewhasprovided,startingfromQtversi on 4.1,
theQSortFilterProxyModel class, after itspredecessorQProxyModel proved to be
toounwieldy.Itisbased on theQAbstractProxyModel base class, which represents
so-called proxymodels.These lie somewhere between amodeland view,obtain-
ingtheir datafromthe modeland returningittothe viewin modifiedform(see
Figure 8.14 on page 237).The proxy modelthereforebecomes thesourcemodelfor
theview.Onpage 237wewilllook in more detail at howproxy models function
andimplement ourownproxy model. Forthe moment,wewilljustlook at what
QSortFilterProxyModel can do: sort andfilter.
231
8 DisplayingDataUsing “Interview”
Duringfiltering, themodelreturnsthe modelindices for thoserowsinwhich the
textinacolumn matchesthe search filter.Duringsorting,the roworderisarranged

according to thevaluesinaspecified column, wherebyyou can sort in ascending
or descending order.
We willdemonstrate both capabilitiesofQSortFilterProxyModel withasmallex-
ample, theFilteringView.Thisconsistsofatree view,abovewhich is alineeditthat
accepts afilter term. Nexttothisweplace acombo boxcontaining allthe column
names. Figure 8.12 showshowyou can usethistoselectthe column that is to act
as thesearchcolumn.
Figure 8.12:
QSortFilterProxyModel
helps in sortingand
filteringmodels.
8.5.1Adjustments to theUserInterface
SimplifyingSorting
Since ourviewwill useaQSortFilterProxyModel instance,which can alreadysort,
weneed onlyto adjustthe viewer accordingly.The worknecessaryfor this is done
bytheconstructor of theclass:
// addressbook/filteringview.cpp
#include <QtGui>
#include "filteringview.h"
FilteringView::FilteringView(QAbstractItemModel
*
model, QWidget
*
parent)
:QWidget(parent)
{
setWindowTitle(tr("FilterView"));
proxyModel =newQSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
QVBoxLayout

*
lay=newQVBoxLayout(this);
QHBoxLayout
*
hlay=newQHBoxLayout;
QLineEdit
*
edit=newQLineEdit;
QComboBox
*
comboBox=newQComboBox;
intmodelIndex=model->columnCount(QModelIndex());
for(inti=0; i<modelIndex; i++)
232
8.5 Sortingand FilteringDatawithProxyModels
comboBox->addItem(model->headerData(i, Qt::Horizontal,
Qt::DisplayRole).toString());
hlay->addWidget(edit);
hlay->addWidget(comboBox);
QTreeView
*
view=newQTreeView;
view->setModel(proxyModel);
view->setAlternatingRowColors(true);
// Make the header"clickable"
view->header()->setClickable(true);
// Sort Indicatorfestlegen
view->header()->setSortIndicator(0,Qt::AscendingOrder);
// Sort Indicatoranzeigen
view->header()->setSortIndicatorShown(true);

// Initialsortieren
view->sortByColumn(0);
lay->addLayout(hlay);
lay->addWidget(view);
connect(edit,SIGNAL(textChanged(const QString&)),
proxyModel, SLOT(setFilterWildcard(const QString&)));
connect(comboBox,SIGNAL(activated(int)),
SLOT(setFilterKeyColumn(int)));
}
Firstwecreateaproxy modeland saveitinamember variable calledproxyModel.
Then wecreatebothavertical andahorizontallayoutusedlater to enclosethe
widgets:Wegroup thelineeditand thecombo boxtogether in onelinewiththe
horizontallayout. With thehelpofthe vertical layout, wepositionthisabovethe
viewto which wepassthe proxy modelasthe source.
To make reading easier,everyotherlineinthe tree viewis displayed withasecond
background color. We activatethisfeature withsetAlternatingRowColors(true).
To obtain thewidgetcontainingthe column headers in tree views, weuse header().
So that it can react to clicks,weset setClickable(true).Inaddition weprovide
it withaso rting indicator,usuallythis is atrianglethatchartswhether datais
shownsorted in ascendingordescendingorder.Inthiscasewesortcolumn 0in
ascendingorder (Qt::AscendingOrder) anddisplaytheindicator via setSortIndica-
torShown(true).Tomake sure that thelis tisalreadysorted beforethe user clicks
theheaderfor thefirsttime, weprearrangethe datasets sorted bythefirstcolumn,
withsortByColumn(0).
233
8 DisplayingDataUsing “Interview”
Restricting theView to SpecificDataset s
In ordertorestrictthe datashowninthe viewto datasets matching afilter string
specified in thelineeditwidget, twoconnect() instructions areneeded:The first
oneinforms theproxy modelassoon as thetextchanges in thelineedit. Theproxy

modelthenusesthistextasthe newfilter.
Thereare threeslots in theproxy modeltowhich thetextChanged() signal can be
linked. setFilterFixedString() deliversall rowswhere thesearchcolumn contains the
specified filter string as asubstring,whereas setFilterWildcard()—which weuse in
theexam ple—also accepts *asawildc ardinthe filter string.For example, when
setFilterWildcard()isused, thesearchtermHel*ldwould matchadataset in the
modelwith“Hello world” in thesearchcolumn. Asearchfor Hel*ld usingsetFilter-
FixedString() willreturn onlydatasets that contain theexact six-character string
Hel*ld.
setFilterRegExp()acceptsfilter stringsthatare regularexpressions. Using Hel*ld as
asearchstring, it will return rowsofwhich thesearchcolumn contains oneofthe
followingsubstrings: Held,Helld,Hellld,Helllld andsoon.
Thesecondsignal/slot connectionisusedtoselectthe field which theproxy model
should compareagainstthe filter string.For this purposethe proxy modelhas the
setFilterKeyColumn() method, which expectsthe search column as theargument.
Since this method is unfortunatelynotimplemented as aslot, wemustimplement
ourownslotinthe view,which willcallthe function:
// addressbook/filteringview.cpp (continued)
void FilteringView::setFilterKeyColumn(intcol) {
proxyModel->setFilterKeyColumn(col);
}
Theslotisalsothe reason wecreated theproxyModel member variable:Werequire
accesstothe proxy modeloutside theconstructor.
8.6Making EntriesSelectablewith Checkboxes
If it is intendedthatthe user should make aselection from alist, Interviewplaces
aboxin frontofthe corresponding entriesthatcan be checkedvia theQItemDele-
gateusedbydefault, a checkbox.Wemake useofthispropertyin anewsubclass
of ouraddressbook model, theCheckableAddressbookModelsubclass.
To implementselectable entries, weneed to reimplementonlythreemethods,apart
from theconstructor:Inflags() weinformthe viewthat specificentries can be

selected.Inorder for thedelegatetodrawthecheckbox,weneed to useanewrole
in data()and setData()—theCheckStateRole.
234
8.6 Making EntriesSelectablewithCheckboxes
// addressbook/checkableaddressbookmodel.h
class CheckableAddressbookModel :public AddressbookModel
{
Q_OBJECT
public:
CheckableAddressbookModel(const QString&addresses,
QObject
*
parent=0);
virtualQVariantdata(const QModelIndex&index,
introle =Qt::DisplayRole )const;
virtualbool setData(const QModelIndex&index,
const QVariant&value, introle =Qt::EditRole);
virtualQt::ItemFlagsflags(const QModelIndex&index) const;
private:
QList<bool> checkedStates;
} ;
In theconstructor wefirstcallthe constructorofthe overclass, passing it thecom-
plete dataset as astring(addresses),aswellasthe parentwidget. We then haveto
findout howmanydatasets th epassedstringcontains. Equippedwiththisvalue,
wecan keep track of theselection status of therespectivelineinthe checkedStates
list.
We can findout thenumber of datasets throughthe number of newline characters.
Onlythosedatasets should be selectable that reallycontainaddressdata—so not
thefirstlinewiththe headers:
// addressbook/checkableaddressbookmodel.cpp

CheckableAddressbookModel::CheckableAddressbookModel(
const QString&addresses,QObject
*
parent)
:AddressbookModel(addresses,parent)
{
// Contrary towhatwe’vedone in the AddressbookModel,
// wedon’t add 1tothe indexhere
// since the headers can’t bechecked bythe user
introws =addresses.count(’\ n’);
for(inti=0; i<rows; i++) {
checkedStates.append(false);
}
}
In thereimplementationofflags() wefirstcatch invalid indicesagain. So that
thereisnot acheckboxin frontofeverysingle column entry,onlytheentries in
thefirstcolumn areselectable,standingfor thewhole lin e. Forthisreasonwe
checkthe indexandallowtheadditional status onlyin column 0.Itisimportant
here to consultthe base implementation,AddressbookModel::flags(index), because
this,among otherthings, ensuresthatitiseditable.Provided that weare in the
first column, weapplyabitwiselogical OR operation withQt::ItemIsUserCheckable
to theexisting flags.Thisoperation combines theflags witheach other:
235
8 DisplayingDataUsing “Interview”
// addressbook/checkableaddressbookmodel.cpp (continued)
Qt::ItemFlagsCheckableAddressbookModel::flags
(const QModelIndex&index) const {
if (!index.isValid())
return0;
if (index.column() == 0)

returnAddressbookModel::flags(index)|Qt::ItemIsUserCheckable;
else
returnAddressbookModel::flags(index);
}
Then weimplement data(). If thecallerislocated in thefirstcolumn andqueries
themodelwhile in theCheckStateRole,welook up thestatusofthe currentrow
(index.row()) in thecheckedStates list. If thecheckboxis selected,the correspond-
ingelement is true,which wesignalbyreturningQt::Checked; otherwise, if it is
false,wereturn Qt::Unchecked. In allother cases weretrievethe return value from
theoverclass:
// addressbook/checkableaddressbookmodel.cpp (continued)
QVariantCheckableAddressbookModel::data(
const QModelIndex&index,introle)const
{
if (!index.isValid()) returnQVariant();
if (role == Qt::CheckStateRole && index.column() == 0) {
if (checkedStates[index.row()]== true)
returnQt::Checked;
else
returnQt::Unchecked;
}
returnAddressbookModel::data(index,role);
}
AlthoughQItemDelegatenowdrawsacheckboxfor everyrow,the user can still not
change itsstatus. This onlyworks if setData() hasbeen implemented accordingly:
// addressbook/checkableaddressbookmodel.cpp (continued)
bool CheckableAddressbookModel::setData(const QModelIndex&index,
const QVariant&value, introle)
{
if (!index.isValid()) returnfalse;

if (role == Qt::CheckStateRole && index.column() == 0) {
checkedStates[index.row()]=!checkedStates[index.row()];
emitdataChanged(index,index);
236
8.7 DesigningYourOwn ProxyModels
returntrue;
}
returnAddressbookModel::setData(index,value, role);
}
Here wealsocheck whether theroleiscorrect andwhether weare locatedinthe
first column. If everythingisall right, wenegatethe status of thelistelement
at therelevantposition. In orderfor theviewstodisplaythechanged value,we
triggerthe dataChanged()signalfor theindexwhenweare finished, just as wedid
in theoverclass. We forwardall othercalls to theoverclass, as wedid for theother
twomethods.
To tryoutthe modelwehavejustcompleted,wechangethe main() program so
that it instantiates ournewmodelinsteadofthe AddressbookModeloverclass, and
adjustthe #include compilerdirectiveaccordingly.The resultscan be seen in Figure
8.13.
Figure 8.13:
CheckableAddress-
bookModel insertsa
checkbox foreach
row.
8.7DesigningYourOwn ProxyModels
With QSortFilterProxyModel wehavealreadygotten to knowoneclass that inherits
from QAbstractProxyModel.But proxy models can also displaytheoriginalmodels
in verydifferent ways. To demonstratethiswewillwrite ourownproxy modelthat
swaps thecolumns androwsofthe original modelinawaysimilartothe matrix
transposeoperation in mathematics.

Figure 8.14:
Proxymodels lie
between theoriginal
modeland theview.
Data source
(Directory, Database, XML, )
Proxy
model
Views
Delegate
Model
237
8 DisplayingDataUsing “Interview”
Firstwewillstart withthe constructor: Since weare notusing additionaldata
structures of ourown, it remainsemptyandmerelyinitializes theoverclass:
// addressbook/transposeproxymodel.cpp
TransposeProxyModel::TransposeProxyModel(QObject
*
parent)
:QAbstractProxyModel(parent)
{
}
Thetwomethods that followdefinehowthedat afromthe source modelare ar-
ranged in theproxy model: mapFromSource()converts an indexfrom thesource
modeltoanindexfor theproxy model, while mapToSource()converts an inde x
from theproxy modeltoanindexfor thesourcemodel. In th emapFromSource()
implementation wefetch th eindexusingthe method of thesamename, butpass
column() as therownumber androw() as thecolumn number.mapToSource()
works in exactlythesameway,but calls theindex() method of thesourcemodel,
in casethe source modelindexhasbeen manipulated:

// addressbook/transposeproxymodel.cpp (continued)
QModelIndexTransposeProxyModel::mapFromSource(
const QModelIndex&sourceIndex) const
{
returnindex(sourceIndex.column(),sourceIndex.row());
}
QModelIndexTransposeProxyModel::mapToSource(
const QModelIndex&proxyIndex) const
{
returnsourceModel()->index(proxyIndex.column(),proxyIndex.row());
}
Butweare still notfinished,because QAbstractProxyModel inherits directlyfrom
QAbstract ItemModel.Thismeans that wemustals oimplement allofits methods.
Take index(), for example. Since weare planning anormal, two-dimensionalmodel,
weuse thecreateIndex() function to generate theindex.Columns androwsmust
notbeswapped here,since thethiswould undothe mappingstoand from the
source:
// addressbook/transposeproxymodel.cpp (continued)
QModelIndexTransposeProxyModel::index(introw,intcolumn,
const QModelIndex&parent) const
{
Q_UNUSED(parent);
returncreateIndex(row,column);
}
238
8.7 DesigningYourOwn ProxyModels
We also havetoimplement theparent()method. Butsince ourproxy modelonly
supports two-dimensionalmodels,and thereforedoesnot supportparentrelations,
wereturn an invalid indexhere:
// addressbook/transposeproxymodel.cpp (continued)

QModelIndexTransposeProxyModel::parent(
const QModelIndex&index) const
{
Q_UNUSED(index);
returnQModelIndex();
}
Nextwereimplement rowCount() andcolumnCount(). Theviewuses thesefunc-
tionstodetermine which indicesitshouldquery.For ourpurposes,rowCount()
should callthe source model’scolumnCount(), andviceversa.
// addressbook/transposeproxymodel.cpp (continued)
intTransposeProxyModel::rowCount(const QModelIndex&parent) const
{
returnsourceModel()->columnCount(parent);
}
intTransposeProxyModel::columnCount(const QModelIndex&parent) const
{
returnsourceModel()->rowCount(parent);
}
In addition,data()mustdeliver thecorrect data. We also fetchthisdirectlyfrom
thesourcemodel. It is important here that you convertthe indexcorrectly,using
thepreviouslycreated mapping methods.Since thepassedindexoriginates from
theproxy model, weuse mapToSource():
// addressbook/transposeproxymodel.cpp (continued)
QVariantTransposeProxyModel::data(const QModelIndex&index,
introle)const
{
if (!index.isValid()) returnQVariant();
returnsourceModel()->data(mapToSource(index),role);
}
Even if notrequired(theheaderData()method is notcompletelyvirtual), it is rec-

ommended that you swap thecolumn androwheaders. To do this wesimplypass
theother value of theOrientation enumeratorineach case:
239
8 DisplayingDataUsing “Interview”
// addressbook/transposeproxymodel.cpp (continued)
QVariantTransposeProxyModel::headerData(intsection,
Qt::Orientation orientation, introle)const
{
if (orientation == Qt::Horizontal)
returnsourceModel()->headerData(section, Qt::Vertical, role);
else
returnsourceModel()->headerData(section, Qt::Horizontal, role);
}
We can nowplacethismodelasaproxy modelbetween atable viewandour
address book model, for example. To do this wefirstmodifythemain() program
from th eoriginaladdressbook exampleonpage 226byincludingthe headerfile
transposeproxymodel.h,displayed below:
// addressbook/transposeproxymodel.h
#ifndef TRANSPOSEPROXYMODEL_H
#define TRANSPOSEPROXYMODEL_H
#include <QAbstractProxyModel>
class TransposeProxyModel :public QAbstractProxyModel {
Q_OBJECT
public:
TransposeProxyModel(QObject
*
parent=0);
virtualQModelIndexmapFromSource(
const QModelIndex&sourceIndex) const;
virtualQModelIndexmapToSource(

const QModelIndex&proxyIndex) const;
virtualQModelIndexindex(int,int,
const QModelIndex&parent=QModelIndex()) const;
virtualQModelIndexparent(const QModelIndex&index) const;
virtualintrowCount(const QModelIndex&parent) const;
virtualintcolumnCount(const QModelIndex&parent) const;
virtualQVariantdata(const QModelIndex&index,
introle =Qt::DisplayRole)const;
virtualQVariantheaderData(intsection,
Qt::Orientation orientation,
introle =Qt::DisplayRole)const;
} ;
#endif // TRANSPOSEPROXYMODEL_H
240
8.8 ImplementingDragand Drop in Models
Figure 8.15:
Ourproxy model
transposes the
original model.
Then weinstantiate theproxy,invokeits setSourceModel()method, passing this a
pointer to theoriginalmodel, andmake theproxy modelbethe source for theview.
Figure 8.15 showsthe result.
8.8ImplementingDragand Drop in Models
So far ourmodelisnot able to moveorcopyindividualrowsvia drag anddrop.
In Section7.4 wegot to knowhowdrag anddropcan be implemented for any
widgets you like, andInterviewalso offers thepossibilityof usingelementsfrom
viewsasdragobjects. Butincontrasttothe previous examples,itis not necessary
here to useinheritanceand adjustone of theviewclassesinthismanner. It is
sufficienttomodifythemodel.
To demonstratethiswewilluse asubclassofthe already-implemented Address-

bookModelclass,calledDndAddressbookModel.
6
To provideitwithdrag-and-drop
capability,wemustnowoverwrite thefollowingmethods:
// addressbook/dndaddressbookmodel.h
#ifndef DNDADDRESSBOOKMODEL_H
#define DNDADDRESSBOOKMODEL_H
#include "addressbookmodel.h"
class DndAddressbookModel :public AddressbookModel
{
public:
DndAddressbookModel(const QString&addresses,QObject
*
parent=0);
virtualQt::ItemFlagsflags(const QModelIndex&index) const;
QStringList mimeTypes() const;
QMimeData
*
mimeData(const QModelIndexList &indexes)const;
bool dropMimeData(const QMimeData
*
data,Qt::DropAction action,
introw,intcolumn, const QModelIndex&parent);
6
Of course,wecould also inheritfromCheckableAddressbookModeland extend itsfunctionality,
butthatwould be more complex,and is thereforelesssuitable fordidacticpurposes.
241
8 DisplayingDataUsing “Interview”
} ;
#endif //ADDRESSBOOKMODEL_H

In ourexampleimplementation, if theusertouches anyelementfromarowwith
themouse,the entire rowwill alwaysbecopied,sothatthe dataset remainsintact .
Forthisreasonthe DndAddressbookModelisuseless for table views. (Although with
abit more work, it is possible to copyonlyindividualelements, wewil lnot go into
this here,toavoidthingsbecomingtoo complicated.)
In theconstructor wedonothing more than forwardthe argumentstothe over-
class. Calls such as setDropEnabled()are notnecessaryhere:
// addressbook/dndaddressbookmodel.cpp
#include <QtGui>
#include "dndaddressbookmodel.h"
DndAddressbookModel::DndAddressbookModel(const QString&addresses,
QObject
*
parent)
:AddressbookModel(addresses,parent)
{
}
Qt::ItemFlagsDndAddressbookModel::flags(const QModelIndex&index) const
{
Qt::ItemFlagsdefaultFlags=AddressbookModel::flags( index);
if (index.isValid())
returnQt::ItemIsDragEnabled |Qt::ItemIsDropEnabled |defaultFlags;
else
returnQt::ItemIsDropEnabled |defaultFlags;
}
If wewanttoallowdrops,wemustsignalthisfor each modelindexin theflags()
method. Whereasweonlyallowdrags from valid modelindices,dropping is also
possible on invalid ones:Ifthe user releases adragafter thelastentryin alist, this
positionisinvalid as amodelindex,althoughitcan be used to signifythat anew
elementshouldbeappended to thelist.

In thenextstep wedefine which MIME typescan be handledbyourmodel. Here we
useour ownformatcalledapplication/x-osp.text.csv,which willsaveussomework
on thenextpage whencopyingthe entriesbetween twomodel/viewinstan ce s:
// addressbook/dndaddressbookmodel.cpp (continued)
QStringList DndAddressbookModel::mimeTypes() const
{
QStringList types;
types<< "application/x-osp.text.csv";
242
8.8 ImplementingDragand Drop in Models
returntypes;
}
ThemimeData()method comesintoplayif theuserpulls aselection awayfrom the
view,thus initiating adrag. We aregiven alistwiththe modelindices involved.
Themethod should packthemintoaQMimeDataobject,and theinstantiation of a
QDrag object is takenover byInterview:
// addressbook/dndaddressbookmodel.cpp (continued)
QMimeData
*
DndAddressbookModel::mimeData(
const QModelIndexList &indexes) const
{
QMimeData
*
mimeData=newQMimeData();
QList<int>rows;
foreach (QModelIndexindex,indexes)
if (index.isValid())
if (!rows.contains(index.row()))
rows +=index.row();

QByteArrayencodedData;
QDataStreamstream(&encodedData,QIODevice::WriteOnly);
foreach(introw,rows)
stream<<addressBook.at(row+1);
mimeData->setData("application/x-osp.text.csv",encodedData);
returnmimeData;
}
Since weare interested onlyin complete rows, weextract therespectiverownum-
bers from th emodelindices passedand savetheminalist.
In thesecondstep wemustfind asuitable wayof storingour datasets in aQByteAr-
ray.Herethe QDataStream classisofhelp, which wewillget to kn owbetter in
Chapter 11 on page 317. It can serializeall primitivedatatypesinQtvia the<<
operator, includingQStringList objects. Thebyte arrayencodedDataisusedhereas
an output medium, because although QDataStream is intendedfor output into files
andfor real ou tput devices,thankstoanoverloadedconstructor theclass can also
write to QByteArrayobjectsorreadfromthem. Corresponding to thefile seman-
tics,the second parameter QIODevice::WriteOnlyindicatesthatthe QDataStream
instance stream mayonlywrite to thebyte array.
Nowwegothrough th ejust-created rowslistand accessthe corresponding en try
of theaddressBook structure. To gettothe positionwereallywant, wemustagain
accessone entrybeyondthatposition.
Each entryfound in this wayis read via aQDataStream into thebyte arrayen-
codedData. We passthe finishedbyte arrayto themimeDataobject.The factthat
243
8 DisplayingDataUsing “Interview”
thecontents no longer havetobepureASCII textafter transformation through
QDataStream is anot herreasonwecannotuse text/plainasMIMEtypes, in addi-
tion to theissueofdistinguishabilityduring thedropprocedure.
Theother side of th edrag-and-drop procedureishandled bythedropMimeData()
method. Apartfromthe MIME data,italsocontainsthe type of drop: Should the

databecopied (CopyAction), moved (MoveAction), linked(LinkAction), or ignored
(IgnoreAction)?
Furthermore, weare given both therowandcolumn in which theuserreleased
themouse,thus triggering thedrop. Viaparentwelearn whether thecurrent item
is achild of anotheritem. Since this cannotbethe caseinour childless model,
wecan ignore parentaswellascolumn, sinceweare onlydragging anddropping
entire rowsatatime.The method returnstrueifthe drop procedureissuccessful,
otherwisefalse:
// addressbook/dndaddressbookmodel.cpp (continued)
bool DndAddressbookModel::dropMimeData(const QMimeData
*
data,
Qt::DropAction action, introw,
intcolumn, const QModelIndex&parent)
{
Q_UNUSED(column);
Q_UNUSED(parent);
if (action == Qt::IgnoreAction)
returntrue;
if (!data->hasFormat("application/x-osp.text.csv"))
returnfalse;
// workaround forQt4.1.2 bug
if (row== -1)
row=rowCount();
QByteArrayencodedData=data->data("application/x-osp.text.csv");
QDataStreamstream(&encodedData,QIODevice::ReadOnly);
QList<QStringList>lines;
while (!stream.atEnd())
stream>>lines;
introws =lines.count();

insertRows(row,rows,QModelIndex());
foreach(QStringList line, lines) {
addressBook.replace(row+1, line);
row++;
}
returntrue;
}
244
8.9 Your OwnDelegates
We react to allactions,but to be on thesafe side wecatch IgnoreAction.We
shouldn’treallyacceptthisaction. If this happens,though, weannounceasuccess-
fulcompletion of th edropoperation—after all, wesuccessfullyignoredthe drop.
In addition wemustensurethatour drag contains theapplication/x-osp.text.csv
MIME type,otherwiseweterminate withreturn false,since thedropactionwas
notsuccessful.
In se veral Qt versions, including4.1.2,the problemoccurs that rowreturnsthe
value -1 if thedroptargetliesbeneath thelastentryin alistortreeview.For this
reason wewillintercept this caseand return thenumber of columns in themodel
so that thenewdataset(s) can be inserted after thelastrow,asintended.
Nowwereadout theQByteArrayfor ourMIMEtypes. This is thedataweobtained
bycombiningthe various string listentries.Wenowreverse that process byreading
outthe lines listfromencodedData, string listbystring list, butthistimemarked
as read-only.The useofthe atEnd()meth od demonstrates that wehavetreated
thebyte array,through QDataStream,likeafile.
Nowwecan calculate thenumber of rowstobeinserted withcount andadd them
to themodelwithinsertRows(). rowprovides us withthe offset here.Finallywe
replacethe emptystring lists created byinsertRows()withthe real contents.The
drag operation is nowcompleted,which wewillannouncetothe callerofthe
method withreturn true.
To testour modifiedmodel, wereplace AddressbookModelwithDndAddressb ook-

Model in themain() function of theaddressprogram on page 226 andstart two
instancesofthe application. Drag anddropisnowpossible between them,and also
withinthe same view.
8.9YourOwn Delegates
Untilnowwehaveaccepted that viewsdisplaytheirentries themselves.Wewill
nowrevealthe secret of the delegates,which areresponsible for thedisplayof
individualelementsand for providing an editor for writable models.Each model
hasexactlyonedelegate.
Alldelegates inheritfromQAbstractItemDelegate, in themannerofFigure8.16.
Bydefault, allviewsuse theQItemDelegateclass derived directlyfrom this,which
provides astandardeditorand contains thecharacter logicfor theentries.Wewill
gettoknowasimilarSQL-specificclass calledQSqlRelationalDelegateinChapter
9.
Belowwewillwrite adelegatethatnot onlyprovides an editor likeQItemDelegate
butalsohas tabcompletion andanoverviewof allexisting entries. Thecurrent
column serves as thedatasource. In thecaseofour address book model, this can
savethe user agreat deal of typing work, for example, for frequentlyoccurring first
namesand familynames.
245
8 DisplayingDataUsing “Interview”
Figure 8.16:
Theinheritance
pattern of delegates
in In te rview
QSqlRelationalDelegate
QItemDelegate
QAbstractItemDelegate
Firstweshall look at theconstructor:All this hastodoiscallthe constructorof
theoverclass, because for this modelwedon’t need anymember variablesthatwe
would havetoinitialize:

// addressbook/completiondelegate.cpp
#include <QtGui>
#include "completiondelegate.h"
CompletionDelegate::CompletionDelegate(QObject
*
parent)
:QItemDelegate(parent)
{
}
Theviewcalls thecreateEditor()method whenthe user launchesaneditorfor the
first time from anyindexpositionatall, bydouble-clicking or pressing




F2 :
// addressbook/completiondelegate.cpp (continued)
QWidget
*
CompletionDelegate::createEditor(QWidget
*
parent,
const QStyleOptionViewItem&option,
const QModelIndex&index)const
{
const QAbstractItemModel
*
model =index.model();
if (!model)
returnQItemDelegate::createEditor(parent,option, index);

QComboBox
*
box=newQComboBox(parent);
box->setEditable(true);
box->setAutoCompletion(true);
box->setModel(const_cast<QAbstractItemModel
*
>(model));
box->setModelColumn(index.column());
box->installEventFilter(const_cast<CompletionDelegate
*
>(this));
returnbox;
}
246
8.9 Your OwnDelegates
As theprocessing widgetwedisplayacombo box,which can be edited likealine
edit,and in additionitshouldbeable to performtab completion.Weuse theparent
pointer passedasthe fatherfor theconstructor;asaresult,the view,and notthe
delegate, controls thewidget.
To fillthe comboboxwithdata, it is sufficienttopassthe comboboxthecurrent
model, because QComboBox,althoughnot an offic ialviewclass, can handle the
QAbstract ItemModel-based models as asource. Just as withQListView,herewe
mustalsospecifythecolumn from themodelfor which theselection boxshould
look,using setModelColumn().
Theconst_castisnecessaryhere because weare in aconst method andour model
pointer is aconst pointer.Thismeans that wemustpreventanywrite operations
to themodel. In addition wemustensurethatcertain keys, such as





Enter or




Esc ,
closethe editor andsignaltothe delegatethatitshouldwrite thedat aback to the
model.
This is donebytheeventfilter that weinstall on thecombo box.Itdiverts all
thekeystrokestothe delegate. Nowwewould havetooverwrite theeventFilter()
method to intercept thekeystrokes. In practice,however,QComboBoxhassuchan
eventfilter in theprivate(that is,internallyhidden) QComboBoxPrivateContainer
class,
7
which is undocumented,however.
This meansthatintheory,the filter maydisappear in each newQt release. If you
wanttobecompletelysure,you should write yourowneventfilter,based on the
implementation of this Qt class.
Figure 8.17:
Our
CompletionDelegate
completesthe entry
on thebasis of other
entriesinthe same
column of th esource
model.
Theeditorisnowavailable,but it is possible that theusermaywanttouse it again
later on at thesameindexposition. To supplyit withcurrent dataeverytime it is

used,the setEditorData()method exists.Our comboboxserves as an editor widget,
which is whyweconvertthe object via qobject_cast:
// addressbook/completiondelegate.cpp (continued)
void CompletionDelegate::setEditorData(QWidget
*
editor,
const QModelIndex&index)const
{
QComboBox
*
box=qobject_cast<QComboBox
*
>(editor);
7
Seeqcombobox_p.h/qcombobox.cpp in theQtsourcecode.
247
8 DisplayingDataUsing “Interview”
const QAbstractItemModel
*
model =index.model();
if (!box|| !model)
QItemDelegate::setEditorData(editor,index);
box->setCurrentIndex(index.row());
}
qobject_castfunctionslikeadynamic_castbut doesnot requir eanyRTTI support,
8
which manypeopleliketodisable for reasonsofspace, particularlyon embedded
platforms.Inaddition it works beyondthe bordersofdynamiclibraries—normally
dynamic_castwillnot workhere. Theonlyconsolation: It can onlybe used for
QObject-based classes.

Exactlylikeadynamic_cast, qobject_castalsoreturnsazeropointer if theconver-
sion fails.Althoughthe conversion should never fail, wewill intercept th is scenario
andbusyourselves insteadwiththe base implementation.Ifeverythinggoesac-
cordingtoplan, wepassthe rowcoordinates of ourcurrent positioninthe model
to th ecombo box.Itusesthisinformation at this point as thestandardtext. Ourfi-
naltasknowconsists of implementing setModelData(). After afewsecuritychecks,
wesimplysetthe currentcontents of thecombo boxas thenewvalue for the
currentmodelindex.Ifyou wanttobereallysure,you should do this both for the
DisplayRole andfor theEditRole:
// addressbook/completiondelegate.cpp (continued)
void CompletionDelegate::setModelData(QWidget
*
editor,
QAbstractItemModel
*
model,
const QModelIndex&index) const
{
if (!index.isValid())
return;
QComboBox
*
box=qobject_cast<QComboBox
*
>(editor);
if (!box)
returnQItemDelegate::setModelData(editor,model, index);
model->setData(index,box->currentText(),Qt::DisplayRole);
model->setData(index,box->currentText(),Qt::EditRole);
}

We can nowinsert ourowndelegates into whatever viewswewant(into ourad-
dressbook as well, or course)using setItemDelegate(). What this lookslikeisshown
in Figure 8.17.
8
RTTI standsfor RuntimeTypeInformation.Itallowsthe type of amethodtobedeterminedin
C++. Since RTTI supportheavilyinflatesthe sizeofthe object filecreated,and mocobtains the
corresponding informationanywayat compile time,you canmanagewithout RTTI supportin
Qt programs.
248

×