Introduction to Design Patterns in C++ with Qt: Models and Views
A model-view design framework provides tools and techniques for separating the set of underlying data classes (the model) from the set of classes that present the user with a GUI (the view). Models typically organize the data, which can be tabular or hiearchical. In this chapter, we will show how to use the model classes in Qt to represent many different kinds of data.
In several earlier examples, you saw code that attempted to keep a clean separation between model classes that represent data and view code that presented a user interface. There are several important reasons for enforcing this separation.
First, separating model from view reduces the complexity of each. Model and view code have completely different maintenance imperatives—changes are driven by completely different factors—so it is much easier to maintain both when they are kept separate. Furthermore, the separation of model from view makes it possible to maintain several different, but consistent, views of the same data. The number of sophisticated view classes that can be reused with well-designed models is constantly growing.
Most GUI toolkits offer list, table, and tree view classes but require the developer to store data inside them. Qt has widget classes derived from corresponding view classes, as shown in Figure 13.1. For developers who have not used model-view frameworks, these widget classes may be easier to learn than their view counterparts. Storing data inside these widgets, however, leads to a strong dependency between the user interface and the underlying structure of the data. This dependency makes it difficult to reuse the widgets for other types of data or to reuse them in other applications. It also makes it difficult to maintain multiple consistent views of the same data. So, the price for the ease of use and convenience (especially in Qt Designer) is a decrease in flexibility and reusability.
Figure 13.1 Widgets and Views
13.1 Model-View-Controller (MVC)
Controller code manages the interactions among events, models, and views. Factory methods, delegates, and creation and destruction code in general fall into the realm of the controller. In the Qt framework, much of the controller mechanism can be found in delegates. Delegates control the rendering and editing of individual items in views. Views supply default delegates that are sufficient for most purposes, although you can, if necessary, refine the ways that the default delegates render items by deriving a custom model from QAbstractItemModel.
Data and Roles
When you get and set data, there is an optional role parameter that lets you specify values for particular roles from Qt::ItemDataRole, used by the view when it requires data from the model. Some roles specify general-purpose data values, such as Qt::DisplayRole (the default), Qt::EditRole (the data in a QVariant suitable for editing), or Qt::ToolTipRole (the data is a QString displayed in a tooltip). Other roles can describe appearance, such as Qt::FontRole, which enables the default delegate to specify a particular QFont, or Qt::TextAlignmentRole, which enables the default delegate to specify a particular Qt::AlignmentFlag. Qt::DecorationRole is used for icons that can decorate values in a view. Typically, you would use a QColor, QIcon, or QPixmap for this role type. Values of Qt::UserRole and above can be defined for your own purposes. Think of these as extra columns of data in the table model.
A model-view-controller framework, illustrated in Figure 13.2, uses a number of design patterns to make it possible to write applications that provide more than one view of the same data. It specifies that the model code (responsible for maintaining the data), the view code (responsible for displaying all or part of the data in various ways), and the controller code (responsible for handling events that impact both the data and the model, such as delegates) be kept in separate classes. This separation enables views and controllers to be added or removed without requiring changes in the model. It enables multiple views to be kept up to date and consistent with the model, even if the data is being interactively edited from more than one view. It maximizes code reuse by enabling subtitution of one model for another, or one view for another.
Figure 13.2 Model-View-Controller Classes
The primary purpose of a controller class is to encapsulate controller code. A complex application might have multiple controllers for different subcomponents, or layers, of the application.
In Qt, the base class for a variety of controller classes is QAbstractItemDelegate. Code that connects signals to slots can also be considered controller code. As you will see, keeping controller code out of model and view classes can yield additional design benefits.