Model/view programming is a technique that involves separating data from its visual representation. It was first popularized as the MVC (model/view/ controller) paradigm used in the Smalltalk programming language.
A model is a class that provides a uniform interface through which data items can be accessed. A view is a class that can present the data items from a model to the user on-screen. A controller is a class that mediates between the user interface (e.g., mouse events and key presses) to provide a means by which users can manipulate data items.
The MVC approach offers several benefits. For example, huge datasets can be handled, because only the data that is actually displayed or edited is read or written from or to the data source. Different views of the same dataset access the same underlying data with no data duplication: This is useful for viewing the same data in different ways using two or more views, or for viewing different parts of a large dataset. Also, if we change how the dataset is stored, for example, from a binary file to a database, only the model needs to be adapted—all the logic in the view and the controller will continue to work because the model fully insulates them from the data.
PyQt uses a slight variation on MVC, called model/view/delegate, that provides all the same benefits as MVC. Both are depicted schematically in Figure 14.1. The key difference is that some of the functionality that classic MVC reserves
for the controller can be implemented either in the delegate or in the model with the model/view/delegate approach.
Conceptually, the model/view/delegate approach works like this: The data is read and written from and to the data source by the model. The view asks the model for the data items that the view wants to display, that is, those items that are visible in the user interface. For each item that the view displays, it gives the item and a painter to the delegate, and asks the delegate to paint the item. Similarly, if the user initiates editing, the view asks the delegate to provide a suitable editor, and if the user accepts their edit (i.e., if the user does not press Esc to cancel), the updated data is passed back to the model. An editor can be any widget, and it is drawn in-place exactly on top of the item, giving the application's user the illusion that the item has become editable.
Every data item in the model (and therefore implicitly every item of data in the dataset), can be identified by a unique QModelIndex. Each model index has three important attributes: a row, a column, and a parent.
1. For one-dimensional models (e.g., lists) only the row is used.
2. For two-dimensional models (e.g., tables, including database tables) only the row and column are used.
3. For hierarchical models (e.g., trees) all three attributes are used.
Although QModelIndexes can refer to any data item in any model, we need to bear in mind that conceptually there are really two kinds of model. The first kind are tables, which include lists since these are just tables with a single column. When we work with tables we work in terms of rows and columns. The second kind are trees. For trees we work in terms of parents and children. (It is possible to take a rows-and-columns approach with trees, but this goes against the grain and will lead to code that is slow and difficult to maintain.)
No matter what the underlying dataset, whether the data is in memory, databases, or files, PyQt models provide the same uniform interface for data access—in particular, the QAbstractItemModel.data() and QAbstractItem-Model.setData() methods. It is also possible to create custom models that contain the dataset inside themselves, such as a model that is a wrapper around a dictionary or a list.
All data held in a model is stored as QVariants. This does not mean that all the dataset's data must be QVariants—the model is an interface to a dataset and in any given session may only ever access a small portion of the entire dataset, so only those data items that are actually used will be stored as QVariants, and then only in the model. The model is responsible for converting from the underlying dataset's data types to and from the QVariants the model uses internally.
Some PyQt widgets, including QListWidget, QTableWidget, and QTreeWidget, are views with models and delegates aggregated inside them. These are the convenience item view widgets and they are especially useful for small and ad hoc datasets. We will see them in use in this chapter's first section.
PyQt also provides some pure view widgets, including QListView, QTableView, and QTreeView. These must be used in conjunction with external models, either ones we create ourselves or one of the built-in models such as QStringListModel, QDirModel, or QSqlTableModel. In the second section, we will see how to create a custom model that can be used with a view widget.
All the convenience views and pure views use a default delegate that controls how data items are presented and edited. In this chapter's last section, we will see how to create a custom delegate to exercise complete control over the editing and presentation of data items. Custom delegates can be used with any view, whether it is a convenience view or a pure view.
This chapter provides a foundation in using PyQt's model/view classes. In Chapter 15, we will see how to use the model/view classes to work with databases, and in Chapter 16, we will cover more advanced uses, including the creation of custom views, improving code reuse in delegates, and presenting tabular data in trees.
We use the same dataset for all the examples in this chapter to make it easier to compare and contrast the techniques used. The dataset's items are described in the first section.
Was this article helpful?
Do you already know the huge impact that video could have on your online business BUT have no idea where to begin with it? Discover Exactly How You Can Start Taking Advantage of Video Marketing In Your Online Business... Even If You're a Total Newbie... Starting Today.