QGIM: documentation update

Document that we support reference wrappers and smart pointers for the
model. Turn the construction of the model into a dedicated section,
and add relevant snippets. Rename and reorganize sections a bit to
introduce basic concepts (rows, columns, multi-role items) first,
before the rather advanced topic of trees and value/pointer trade-offs
for rows.

Change-Id: Ia622783f9cd7aa345217186b438e5e883b9c25b0
Reviewed-by: Mate Barany <mate.barany@qt.io>
This commit is contained in:
Volker Hilsheimer 2025-04-07 15:49:31 +02:00
parent 2e5a881d34
commit d139d22f7e
2 changed files with 157 additions and 73 deletions

View File

@ -24,6 +24,36 @@ listView.setModel(&model);
//! [array]
}
void construct_by()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
{
//! [value]
QGenericItemModel model(numbers);
//! [value]
}
{
//! [pointer]
QGenericItemModel model(&numbers);
//! [pointer]
}
{
//! [reference_wrapper]
QGenericItemModel model(std::ref(numbers));
//! [reference_wrapper]
}
{
//! [smart_pointer]
auto shared_numbers = std::make_shared<std::vector<int>>(numbers);
QGenericItemModel model(shared_numbers);
//! [smart_pointer]
}
}
void const_array()
{
//! [const_array]
@ -40,6 +70,14 @@ std::array<const int, 5> numbers = {1, 2, 3, 4, 5};
QGenericItemModel model(numbers);
}
void const_ref()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
//! [const_ref]
QGenericItemModel model(std::cref(numbers));
//! [const_ref]
}
void list_of_int()
{
//! [list_of_int]

View File

@ -31,29 +31,67 @@ QT_BEGIN_NAMESPACE
operations will perform better if \c{std::size} is available, and if the
iterator satisfies \c{std::random_access_iterator}.
The range can be provided by pointer or by value, and has to be provided
when constructing the model. If the range is provided by pointer, then
QAbstractItemModel APIs that modify the model, such as setData() or
insertRows(), modify the range. The caller must make sure that the
range's lifetime exceeds the lifetime of the model. Methods that modify
the structure of the range, such as insertRows() or removeColumns(), use
standard C++ container APIs \c{resize()}, \c{insert()}, \c{erase()}, in
addition to dereferencing a mutating iterator to set or clear the data.
\section1 Constructing the model
There is no API to retrieve the range again, so constructing the model
from a range by value is mostly only useful for displaying data.
The range must be provided when constructing the model; there is no API to
set the range later, and there is no API to retrieve the range from the
model. The range can be provided by value, reference wrapper, or pointer.
How the model was constructed defines whether changes through the model API
will modify the original data.
When constructed by value, the model makes a copy of the range, and
QAbstractItemModel APIs that modify the model, such as setData() or
insertRows(), have no impact on the original range.
\snippet qgenericitemmodel/main.cpp value
As there is no API to retrieve the range again, constructing the model from
a range by value is mostly only useful for displaying read-only data.
Changes to the data can be monitored using the signals emitted by the
model, such as \l{QAbstractItemModel}{dataChanged()}.
However, if the range holds pointers to data, then the copy of the range
that the model operates on will typically point to the same data, so calls
to setData() will modify the original data, while calls to insertRows()
will not.
To make modifications of the model affect the original range, provide the
range either by reference wrapper or by pointer.
\snippet qgenericitemmodel/main.cpp pointer
\snippet qgenericitemmodel/main.cpp reference_wrapper
In this case, QAbstractItemModel APIs that modify the model also modify the
range. Methods that modify the structure of the range, such as insertRows()
or removeColumns(), use standard C++ container APIs \c{resize()},
\c{insert()}, \c{erase()}, in addition to dereferencing a mutating iterator
to set or clear the data.
\note Once the model has been constructed, the range the model operates on
must no longer be modified directly. Views on the model wouldn't be
informed about the changes, and structural changes are likely to corrupt
instances of QPersistentModelIndex that the model maintains.
The caller must make sure that the range's lifetime exceeds the lifetime of
the model.
Use smart pointers to make sure that the range is only deleted when all
clients are done with it.
\snippet qgenericitemmodel/main.cpp smart_pointer
QGenericItemModel supports both shared and unique pointers.
\section2 Read-only or mutable
For ranges that are const objects, for which access always yields
constant values, or where the required container APIs are not available,
For ranges that are const objects, for which access always yields constant
values, or where the required container APIs are not available,
QGenericItemModel implements write-access APIs to do nothing and return
\c{false}. In the example above, the model cannot add or remove rows, as
the number of entries in a C++ array is fixed. But the values can be
changed using setData(), and the user can trigger editing of the values in
the list view. By making the array const, the values also become read-only.
\c{false}. In the example using \c{std::array}, the model cannot add or
remove rows, as the number of entries in a C++ array is fixed. But the
values can be changed using setData(), and the user can trigger editing of
the values in the list view. By making the array const, the values also
become read-only.
\snippet qgenericitemmodel/main.cpp const_array
@ -61,31 +99,38 @@ QT_BEGIN_NAMESPACE
\snippet qgenericitemmodel/main.cpp const_values
In the above examples using \c{std::vector}, the model can add or remove
rows, and the data can be changed. Passing the range as a constant
reference will make the model read-only.
\snippet qgenericitemmodel/main.cpp const_ref
\note If the values in the range are const, then it's also not possible
to remove or insert columns and rows through the QAbstractItemModel API.
For more granular control, implement \l{the C++ tuple protocol}.
\section1 List, Table, or Tree
\section1 Rows and columns
The elements in the range are interpreted as rows of the model. Depending
on the type of these rows, QGenericItemModel exposes the range as a list,
a table, or a tree.
on the type of these row elements, QGenericItemModel exposes the range as a
list, a table, or a tree.
If the row type is not an iterable range, and does not implement the
C++ tuple protocol, then the range gets represented as a list.
If the row elements are simple values, then the range gets represented as a
list.
\snippet qgenericitemmodel/main.cpp list_of_int
If the row type is an iterable range, then the range gets represented as a
table.
If the type of the row elements is an iterable range, such as a vector,
list, or array, then the range gets represented as a table.
\snippet qgenericitemmodel/main.cpp grid_of_numbers
With such a row type, the number of columns can be changed via
insertColumns() and removeColumns(). However, all rows are expected to have
If the row type provides the standard C++ container APIs \c{resize()},
\c{insert()}, \c{erase()}, then columns can be added and removed via
insertColumns() and removeColumns(). All rows are required to have
the same number of columns.
\section2 Fixed-size rows
\section2 Structs and gadgets as rows
If the row type implements \l{the C++ tuple protocol}, then the range gets
represented as a table with a fixed number of columns.
@ -110,7 +155,48 @@ QT_BEGIN_NAMESPACE
implementing the tuple protocol for compile-time generation of the access
code.
\section2 Trees of data
\section2 Multi-role items
The type of the items that the implementations of data(), setData(),
clearItemData() etc. operate on can be the same across the entire model -
like in the \c{gridOfNumbers} example above. But the range can also have
different item types for different columns, like in the \c{numberNames}
case.
By default, the value gets used for the Qt::DisplayRole and Qt::EditRole
roles. Most views expect the value to be
\l{QVariant::canConvert}{convertible to and from a QString} (but a custom
delegate might provide more flexibility).
If the item is an associative container that uses \c{int},
\l{Qt::ItemDataRole}, or QString as the key type, and QVariant as the
mapped type, then QGenericItemModel interprets that container as the storage
of the data for multiple roles. The data() and setData() functions return
and modify the mapped value in the container, and setItemData() modifies all
provided values, itemData() returns all stored values, and clearItemData()
clears the entire container.
\snippet qgenericitemmodel/main.cpp color_map
The most efficient data type to use as the key is Qt::ItemDataRole or
\c{int}. When using \c{int}, itemData() returns the container as is, and
doesn't have to create a copy of the data.
Gadgets and QObject types are also represented at multi-role items if they
are the item type in a table. The names of the properties have to match the
names of the roles.
\snippet qgenericitemmodel/main.cpp color_gadget_0
When used in a list, these types are ambiguous: they can be represented as
multi-column rows, with each property represented as a separate column. Or
they can be single items with each property being a role. To disambiguate,
use the QGenericItemModel::SingleColumn and QGenericItemModel::MultiColumn
wrappers.
\snippet qgenericitemmodel/main.cpp color_gadget_1
\section1 Trees of data
QGenericItemModel can represent a data structure as a tree model. Such a
tree data structure needs to be homomorphic: on all levels of the tree, the
@ -241,55 +327,15 @@ QT_BEGIN_NAMESPACE
row type. QGenericItemModel will never allocate new rows in lists and tables
using operator new, and will never free any rows.
So, using pointers at rows comes with some memory allocation and management
Using pointers at rows comes with some memory allocation and management
overhead. However, when using rows through pointers the references to the
row items remain stable, even when they are moved around in the range,
or when the range reallocates. This can significantly reduce the cost
of making modifications to the model's structure when using insertRows(),
row items remain stable, even when they are moved around in the range, or
when the range reallocates. This can significantly reduce the cost of
making modifications to the model's structure when using insertRows(),
removeRows(), or moveRows().
So, each choice has different performance and memory overhead trade-offs.
The best option depends on the exact use case and data structure used.
\section2 Multi-role items
The type of the items that the implementations of data(), setData(),
clearItemData() etc. operate on can be the same across the entire model -
like in the \c{gridOfNumbers} example above. But the range can also have
different item types for different columns, like in the \c{numberNames}
case.
By default, the value gets used for the Qt::DisplayRole and Qt::EditRole
roles. Most views expect the value to be
\l{QVariant::canConvert}{convertible to and from a QString} (but a custom
delegate might provide more flexibility).
If the item is an associative container that uses \c{int},
\l{Qt::ItemDataRole}, or QString as the key type, and QVariant as the
mapped type, then QGenericItemModel interprets that container as the storage
of the data for multiple roles. The data() and setData() functions return
and modify the mapped value in the container, and setItemData() modifies all
provided values, itemData() returns all stored values, and clearItemData()
clears the entire container.
\snippet qgenericitemmodel/main.cpp color_map
The most efficient data type to use as the key is Qt::ItemDataRole or
\c{int}. When using \c{int}, itemData() returns the container as is, and
doesn't have to create a copy of the data.
Gadgets and QObject types are also represented at multi-role items if they
are the item type in a table. The names of the properties have to match the
names of the roles.
\snippet qgenericitemmodel/main.cpp color_gadget_0
When used in a list, these types are ambiguous: they can be represented as
multi-column rows, with each property represented as a separate column. Or
they can be single items with each property being a role. To disambiguate,
use the QGenericItemModel::SingleColumn wrapper.
\snippet qgenericitemmodel/main.cpp color_gadget_1
Each choice has different performance and memory overhead trade-offs. The
best option depends on the exact use case and data structure used.
\section2 The C++ tuple protocol