Intended way of storing data in QAbstractTableModel derived class
-
Hi everyone!
In this thread I would like to ask you, what is the correct way to store data in custom model.
Let's say I have a struct of various types, some of which are to be displayed on table:struct Item{ int integer; std::string string; std::string another_string; enum_type some_enum; std::string time_stored_in_string; };
Then I sublass QAbstractTableModel and store that data in a vector:
class MyModel : public QAbstractTableModel{ ... std::vector<Item> m_items; };
And reimplement data() method:
QVariant MyModel::data(const QModelIndex& index, int role) const { switch (index.column()) { case 0: { return QDateTime::fromString(QString::fromStdString(m_items[index.row()].time_stored_in_string), "format"); } case 1: { return someEnumToQString(m_items[index.row()].some_enum); } case 2: { return QString::fromStdString(m_items[index.row()].string); } default: return QVariant(); } }
As you can see, there's a lot of construction in data() method, we're constructing QString and QDateTime from std::string, and calling some kind of switch-based someEnumToQString method. Also we're not using some of the fields (integer and another_string), but we have to store them for later use.
Here I am curious of how much of an overhead is this data method? Should I strive to store the data as QVariant for optimization purposes? :
class MyModel : public QAbstractTableModel{ ... std::vector<std::array<QVariant, 3>> m_items; ... void addItem(Item const& item){ // qvariant cast skipped for simplicity m_items.emplace_back({ QDateTime::fromString(QString::fromStdString(item.time_stored_in_string), "format"), someEnumToQString(item.some_enum), QString::fromStdString(item.string) }); ... QVariant data(const QModelIndex& index, int role) const final{ return m_items[idex.row][index.column]; } };
Feels like pseudocode listed above is closer to intended use, it's for sure faster, but here I lose some of struct members that are going to be needed later, and it is not possible to access members by name anymore, which makes code unreadable.
A half-measure would be adding some wrapper struct, that would store members used in view as QVariant and others as their original type:
struct ItemWrapper { ItemWrapper(Item const& item) { // cast to qvariant } // used in view QVariant string; QVariant some_enum; QVariant time_stored_in_string; // unused in view int integer; std::string another_string; };
In this case if I need to modify members stored as QVariant, I will have to perform ugly conversion every time
What are your thoughts on this problem? Should I use wrapper struct for speeding up data() method and losing readability? Or should I ignore overhead brought by first data() implementation as unrelevant?
-
Hi everyone!
In this thread I would like to ask you, what is the correct way to store data in custom model.
Let's say I have a struct of various types, some of which are to be displayed on table:struct Item{ int integer; std::string string; std::string another_string; enum_type some_enum; std::string time_stored_in_string; };
Then I sublass QAbstractTableModel and store that data in a vector:
class MyModel : public QAbstractTableModel{ ... std::vector<Item> m_items; };
And reimplement data() method:
QVariant MyModel::data(const QModelIndex& index, int role) const { switch (index.column()) { case 0: { return QDateTime::fromString(QString::fromStdString(m_items[index.row()].time_stored_in_string), "format"); } case 1: { return someEnumToQString(m_items[index.row()].some_enum); } case 2: { return QString::fromStdString(m_items[index.row()].string); } default: return QVariant(); } }
As you can see, there's a lot of construction in data() method, we're constructing QString and QDateTime from std::string, and calling some kind of switch-based someEnumToQString method. Also we're not using some of the fields (integer and another_string), but we have to store them for later use.
Here I am curious of how much of an overhead is this data method? Should I strive to store the data as QVariant for optimization purposes? :
class MyModel : public QAbstractTableModel{ ... std::vector<std::array<QVariant, 3>> m_items; ... void addItem(Item const& item){ // qvariant cast skipped for simplicity m_items.emplace_back({ QDateTime::fromString(QString::fromStdString(item.time_stored_in_string), "format"), someEnumToQString(item.some_enum), QString::fromStdString(item.string) }); ... QVariant data(const QModelIndex& index, int role) const final{ return m_items[idex.row][index.column]; } };
Feels like pseudocode listed above is closer to intended use, it's for sure faster, but here I lose some of struct members that are going to be needed later, and it is not possible to access members by name anymore, which makes code unreadable.
A half-measure would be adding some wrapper struct, that would store members used in view as QVariant and others as their original type:
struct ItemWrapper { ItemWrapper(Item const& item) { // cast to qvariant } // used in view QVariant string; QVariant some_enum; QVariant time_stored_in_string; // unused in view int integer; std::string another_string; };
In this case if I need to modify members stored as QVariant, I will have to perform ugly conversion every time
What are your thoughts on this problem? Should I use wrapper struct for speeding up data() method and losing readability? Or should I ignore overhead brought by first data() implementation as unrelevant?
@keesaev said in Intended way of storing data in QAbstractTableModel derived class:
QVariant MyModel::data(const QModelIndex& index, int role) const {
switch (index.column()) {You should also check for
role==Qt::DisplayRole
or you end up returning the main data even when the view is asking what font to useHere I am curious of how much of an overhead is this data method? Should I strive to store the data as QVariant for optimization purposes?
The overhead is minimal but I would store the
QDateTime
at construction/assignment time. just my preference thoughShould I strive to store the data as QVariant for optimization purposes?
Absolutely not, you shouldn't make the foot fit the shoe unless you have some impeding optimisation requirement
-
@keesaev said in Intended way of storing data in QAbstractTableModel derived class:
QVariant MyModel::data(const QModelIndex& index, int role) const {
switch (index.column()) {You should also check for
role==Qt::DisplayRole
or you end up returning the main data even when the view is asking what font to useHere I am curious of how much of an overhead is this data method? Should I strive to store the data as QVariant for optimization purposes?
The overhead is minimal but I would store the
QDateTime
at construction/assignment time. just my preference thoughShould I strive to store the data as QVariant for optimization purposes?
Absolutely not, you shouldn't make the foot fit the shoe unless you have some impeding optimisation requirement
@VRonin
Thanks for the reply!You should also check for role==Qt::DisplayRole or you end up returning the main data even when the view is asking what font to use
I omitted this detail for simplicity, of cource I use roles - for text and for color used by custom delegate
Absolutely not, you shouldn't make the foot fit the shoe unless you have some impeding optimisation requirement
Thanks for great advice!