A common data model involves lists, tables and trees of more or
less uniform items. Several standard widgets provided by the
library use such a model. The abstract WAbstractItemModel
base class provides the interface which is
used by these view classes.
An item model is essentially a table of items, where each
item can be a parent to a nested table of items.
Since recursive statements like the one above can confuse even
a seasoned programmer, let's start from a simple model, and extend
it to more complex instances.
Lists
In its most simple and perhaps most common form, an item model
simply stores a list of items. Such a model has only one column.
Each item may holds different facets of the data, stored as
different Item Data Roles. If a table has two
dimensions (rows an columns), then data roles could be considered
as a third dimension.
The common use-case for item data roles is that for a single
item, there may be a textual representation, but also an icon, a
customized style class, a link, etc...
The built-in views will thus interpret
subsets of this data to render a single item. In particular, the
following data roles are commonly supported:
DisplayRole
Rendered as text
DecorationRole
Rendered as icons
CheckStateRole
Rendered as check box state
LinkRole
Rendered as links
ToolTipRole
Rendered as tool tips
StyleClassRole
Rendered as style classes
See the WItemDataRole enumeration for a more complete
list of standard roles, whose use may depend on the View.
Tables
As a straight forward generalization to lists, tables may include
more than one more column, and each item can be identified by a
(row, column) pair.
Trees
A tree model is - like a list - a model with a single column. In addition
each item may be parent to another list.
At this point it becomes necessary to introduce the concept of
a WModelIndex to uniquely identify an item. A model
index is a data structucre containing:
row
The row number
column
The column number
parent
The parent model index
The recursion is thus achieved by associating a parent index
with each item index. By convention, top-level items have an
Invalid index (which is a default constructed
WModelIndex). To make the recursive definition
consistent, one can also imagine an invisible root item
(corresponding to the "invalid" index) which is the parent of
the top-level items.
Tree Tables
Finally, a tree table extends the list model by allow additional
columns of data to be associated with each item row.
None of the standard Views render hierarchical data that is not
present in the first column ! While such data structures can
indeed be defined by item models, this will effectively be
ignored by the standard View classes.
To get you going, and more than sufficient for simple needs, the
library provides a number of standard and generic models, which
store the data in memory.
WStandardItemModel can implement any kind of
item model, being composed of a table of WStandardItem's, which each can contain itself a nested table
of WStandardItem items.
WStringListModel only supports lists with a
single column, and is thus optimized for list-oriented widgets
(combo-boxes and the like).
One particarly useful standard model is the Dbo::QueryModel which is a tabular model backed by database
data.
Custom models
Separating models from views would be not very useful, if it were
not of the ablity to implement customized models. These could be
models that compute some or all data on the fly, or fetch this
information from a underlying database or file system, or simply
display information from an existing data structure in a
tabular/tree-like way.
Table models
As a minimum, a custom table model should reimplement the
following methods from WAbstractTableModel:
int rowCount()
Returns the table row count (only for the invalid parent!)
int columnCount()
Returns the table column count (only for the invalid parent!)
boost::any data()
Returns the data for an item, and a particular role
boost::any headerData()
Returns the header data for a column
As an example of a custom table model, consider the following
(non-sensical) model that simply displays row/column information
for each item.
Tree models
A custom tree model involves considerably more work. Each
internal item (in the first column) which has children, needs to
be identified by a unique 64-bit value (which may thus be a
long long or a void * pointer). Depending on
the source data, a suitable choice must be made for this data.
The following methods must be implemented for a minimally
compliant hierarchical model:
int rowCount()
Returns the item children count
int columnCount()
Returns the column count (which is usually the same for
each item in the first column)
boost::any data()
Returns the data for an item, and a particular role
boost::any headerData()
Returns the header data for a column
WModelIndex child()
Creates a child index
WModelIndex parent()
Creates a parent index
As an example of a tree table model, consider the following
model that loads information from a git repository (in this
case, Wt's git). Only a minimum of information is kept in
memory: we allocate a data structure only for folders that are
being expanded, for use as internal pointer data in model
indexes.
Type
File
.editorconfig
.gitignore
Text
CMakeLists.txt
Changelog
Doxyfile
INSTALL
INSTALL.html
INSTALL.win32.html
LICENSE
Markdown
README.md
ReleaseNotes.html
WConfig.h.in
WtInstall.cmake
Folder
cmake/
Folder
doc/
Folder
examples/
Folder
jenkins/
Folder
migrate/
Folder
resources/
Folder
selenium/
1 of 2
#include<Wt/WAbstractItemModel.h>#include"Git.h"classGitModel:publicWt::WAbstractItemModel{public:/* * A custom role for the file contents of a Git BLOB object. */staticconstexprWt::ItemDataRoleContentsRole=Wt::ItemDataRole::User+1;GitModel(conststd::string&repository):WAbstractItemModel(){git_.setRepositoryPath(repository);loadRevision("master");}voidloadRevision(conststd::string&revName){Git::ObjectIdtreeRoot=git_.getCommitTree(revName);layoutAboutToBeChanged().emit();// Invalidates model indexestreeData_.clear();childPointer_.clear();/* * This stores the tree root as treeData_[0] */treeData_.push_back(Tree(-1,-1,treeRoot,git_.treeSize(treeRoot)));layoutChanged().emit();}virtualWt::WModelIndexparent(constWt::WModelIndex&index)const{if(!index.isValid()||index.internalId()==0){returnWt::WModelIndex();// treeData_[0] is the tree root}else{constTree&item=treeData_[index.internalId()];returncreateIndex(item.index(),0,item.parentId());}}virtualWt::WModelIndexindex(introw,intcolumn,constWt::WModelIndex&parent=Wt::WModelIndex())const{intparentId;if(!parent.isValid())parentId=0;else{intgrandParentId=parent.internalId();parentId=getTreeId(grandParentId,parent.row());}returncreateIndex(row,column,parentId);}virtualintcolumnCount(constWt::WModelIndex&parent=Wt::WModelIndex())const{return2;}virtualintrowCount(constWt::WModelIndex&parent=Wt::WModelIndex())const{inttreeId;if(parent.isValid()){if(parent.column()!=0)return0;Git::Objecto=getObject(parent);if(o.type==Git::Tree){// is a foldertreeId=getTreeId(parent.internalId(),parent.row());}else// is a filereturn0;}else{treeId=0;}returntreeData_[treeId].rowCount();}virtualWt::cpp17::anydata(constWt::WModelIndex&index,Wt::ItemDataRolerole=Wt::ItemDataRole::Display)const{if(!index.isValid())returnWt::cpp17::any();Git::Objectobject=getObject(index);switch(index.column()){case0:if(role==Wt::ItemDataRole::Display){if(object.type==Git::Tree)returnobject.name+'/';elsereturnobject.name;}elseif(role==Wt::ItemDataRole::Decoration){if(object.type==Git::Blob)returnstd::string("icons/git-blob.png");elseif(object.type==Git::Tree)returnstd::string("icons/git-tree.png");}elseif(role==ContentsRole){if(object.type==Git::Blob)returngit_.catFile(object.id);}break;case1:if(role==Wt::ItemDataRole::Display){if(object.type==Git::Tree)returnstd::string("Folder");else{std::stringsuffix=getSuffix(object.name);if(suffix=="C"||suffix=="cpp")returnstd::string("C++ Source");elseif(suffix=="h"||(suffix==""&&!topLevel(index)))returnstd::string("C++ Header");elseif(suffix=="css")returnstd::string("CSS Stylesheet");elseif(suffix=="js")returnstd::string("JavaScript Source");elseif(suffix=="md")returnstd::string("Markdown");elseif(suffix=="png"||suffix=="gif")returnstd::string("Image");elseif(suffix=="txt")returnstd::string("Text");elsereturnWt::cpp17::any();}}}returnWt::cpp17::any();}virtualWt::cpp17::anyheaderData(intsection,Wt::Orientationorientation=Wt::Orientation::Horizontal,Wt::ItemDataRolerole=Wt::ItemDataRole::Display)const{if(orientation==Wt::Orientation::Horizontal&&role==Wt::ItemDataRole::Display){switch(section){case0:returnstd::string("File");case1:returnstd::string("Type");default:returnWt::cpp17::any();}}elsereturnWt::cpp17::any();}private:Gitgit_;/* * Identifies a folder given parent and index */structChildIndex{intparentId;intindex;ChildIndex(intaParent,intanIndex):parentId(aParent),index(anIndex){}booloperator<(constChildIndex&other)const{if(parentId<other.parentId)returntrue;elseif(parentId>other.parentId)returnfalse;elsereturnindex<other.index;}boolequals(Wt::cpp17::anyo){ChildIndex*other=Wt::cpp17::any_cast<ChildIndex*>(o);returnparentId==other->parentId&&index==other->index;}inthashCode(){inthash=1;hash=hash*31+parentId;hash=hash*31+index;returnhash;}};/* * Data to be stored for an (expanded) folder */classTree{public:Tree(intparentId,intindex,constGit::ObjectId&object,introwCount):index_(parentId,index),treeObject_(object),rowCount_(rowCount){}intparentId()const{returnindex_.parentId;}intindex()const{returnindex_.index;}constGit::ObjectId&treeObject()const{returntreeObject_;}introwCount()const{returnrowCount_;}private:ChildIndexindex_;Git::ObjectIdtreeObject_;introwCount_;};typedefstd::map<ChildIndex,int>ChildPointerMap;/* * Expanded folder data */mutablestd::vector<Tree>treeData_;/* * Indexes into treeData_ */mutableChildPointerMapchildPointer_;/* * Gets or allocates an id for a folder. */intgetTreeId(intparentId,intchildIndex)const{ChildIndexindex(parentId,childIndex);ChildPointerMap::const_iteratori=childPointer_.find(index);if(i==childPointer_.end()){constTree&parentItem=treeData_[parentId];Git::Objecto=git_.treeGetObject(parentItem.treeObject(),childIndex);treeData_.push_back(Tree(parentId,childIndex,o.id,git_.treeSize(o.id)));intresult=treeData_.size()-1;childPointer_[index]=result;returnresult;}elsereturni->second;}/* * Gets the Git::Object that corresponds to an index. */Git::ObjectgetObject(constWt::WModelIndex&index)const{intparentId=index.internalId();constTree&parentItem=treeData_[parentId];returngit_.treeGetObject(parentItem.treeObject(),index.row());}staticstd::stringgetSuffix(conststd::string&fileName){std::size_tdot=fileName.rfind('.');if(dot==std::string::npos)return"";elsereturnfileName.substr(dot+1);}booltopLevel(constWt::WModelIndex&index)const{return!parent(index).isValid();}};constexprWt::ItemDataRoleGitModel::ContentsRole;
Note The instantiation of
this model with a Tree View is discussed in MVC Tree Views.
A model may support sorting by one of its columns. This sorting
can be implemented within the model itself.
void sort()
Sorts the model according to one of its columns.
Sorting may be bolted onto a source model using the WSortFilterProxyModel,
which is one of the standard proxy models.
Model Changes
A model does not necessarily need to be a static data source, but
its data can also change, and data (rows/columns) can be added or
removed. A model needs to generate events to inform Views of these
modifications (for the events to which a View is subscribed). When
implementing a custom model which is dynamic in nature, it is
therefore important to emit these signals when making the
modifications.
Editing
The model API also provides a standard interface to perform
editing of the data, and some Views (such as the Tree View and
Table Views) can be configured to allow editing of the data.
If a custom wants to support this editing API, it needs to reimplement the
following methods from WAbstractTableModel:
bool setData()
Updates data. Views typically use the EditRole
for the data used in editing