Refreshing a Custom Model data (QList<UserDefinedClass>) and swapping the lists contained in a QMap
-
Hello everyone, what I'm trying to implement is the following QMainWindow made of the components:
-
left QListView do not exist for the moment and is simulated to simplify testing, so don't focus to much on it.
-
GroupEditor (righ Widget) uses a custom model (PathsInfoTableModel) that is set to the QTableView
2.1) This widget as a Custom Card ( visible under the QTableView on the image above) and is simply a QWidget with a
QDataWidgetMapper that is connected to the QTableView currentItem that is called MapperPathInfo.
The application's storage Structure:
// QMap< QString, QList<PathInfo> > "GroupName1" { [ Path_info_1, Path_info_2, ... , Path_info_12, ] }, "GroupName2" { [ Path_info_1, Path_info_2, ... , Path_info_7, ] }
Functionality Description:
In the main window constructor a customModel called PathsInfoTableModel( QList<PathInfo> data) is istanciated and then passed to the GroupEditor's constructor (right widget) for the QTableView and is forwarded to the Widget mapper MapperPathInfo that set also QDataWidgetMapper->setModel( m_model )
long story short, a customModel is istanciated in the main window and then shared to QTableView and a QDataWidgetMapper
When the user want to edit a group (that contains a QList of paths obj) he clicks the "Edit Group"
-> this will take the "GroupName" as a QString and use it as a key in the QMap to retrive the corresponding QList<PathInfo>
and set the model's internal data structure to that QListauto current_list = (*QMap)["GroupName2"]; //definition in the model: void setListData( QList<PathInfo>& new_paths_list ); customModel->setListData( current_list);
model definition:
class PathsInfoTableModel : public QAbstractTableModel { Q_OBJECT public: explicit PathsInfoTableModel( QList<PathInfo> group= QList<PathInfo>(), QObject *parent = nullptr ); // virtual override int rowCount( const QModelIndex &parent = QModelIndex() ) const override; int columnCount( const QModelIndex &parent = QModelIndex() ) const override; QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; bool setData( const QModelIndex& index, const QVariant& value, int role ) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QList<PathInfo>& getPathInfoList() { return m_model_data; } void setListData( QList<PathInfo>& new_paths_list ); private: QList<PathInfo> m_model_data; };
model implementation:
PathsInfoTableModel::PathsInfoTableModel(QList<PathInfo> group, QObject *parent) : QAbstractTableModel{parent}, m_model_data{ group } {} Qt::ItemFlags PathsInfoTableModel::flags( const QModelIndex &index ) const { if ( !index.isValid() ) return Qt::NoItemFlags; // sets path_col and IsOpenable_col editable if ( index.column() == 3 ) { return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int PathsInfoTableModel::rowCount( const QModelIndex &parent ) const { Q_UNUSED(parent) return m_model_data.size(); } int PathsInfoTableModel::columnCount( const QModelIndex &parent ) const { Q_UNUSED(parent) return 4; } QVariant PathsInfoTableModel::data( const QModelIndex &index, int role ) const { auto col = index.column(); auto row = index.row(); PathInfo p = m_model_data.at(row); // add pixmap to first and third column if ( role == Qt::DecorationRole && (col == 0 || col == 2) ) { auto is_path_valid = p[2].toBool(); // select the right icon for // the first column if ( col == 0 ) { // path doesn't exist if ( !is_path_valid ) return p.NOT_A_FILE_PIX; // exists and is a file if ( p[0].toBool() ) return p.FILE_PIX; // is a folder return p.FOLDER_PIX; } return ( is_path_valid ) ? p.VALID_PIX : p.INVALID_PIX; } if ( role == Qt::DisplayRole && col == 1 ) { return p[col]; } /**************************************************************************** * change it with a custom QStyledItem * to draw the chips instead ****************************************************************************/ // add text Valid-Invalid if ( role == Qt::DisplayRole && col == 2 ) { return ( p[col].toBool() ) ? QVariant( QString("Valid") ) : QVariant( QString("Invalid") ); } return QVariant(); } bool PathsInfoTableModel::setData( const QModelIndex& index, const QVariant& value, int role ) { auto row = index.row(), col = index.column(); QModelIndex fileTypeIdx = createIndex(row, 0); QModelIndex validityIdx = createIndex(row, 2); if ( !index.isValid() || row >= m_model_data.size() ) return false; if ( role == Qt::DisplayRole || role == Qt::EditRole ) { // get PathInfo at row index.row() PathInfo& pathInfo = m_model_data[row]; if ( col == 1 ) // edit the path_column { // not a path, return if ( !value.canConvert<QString>() ) return false; QString oldPath = pathInfo.path(); pathInfo.changePath( value.toString() ); if ( oldPath != pathInfo.path() ) { emit dataChanged( index, index, { Qt::DisplayRole, Qt::EditRole } ); emit dataChanged( fileTypeIdx, fileTypeIdx, { Qt::DisplayRole } ); emit dataChanged( validityIdx, validityIdx, { Qt::DisplayRole } ); return true; } return true; } else if ( col == 2 ) // edit the isValid chip { // check if data is of type bool if ( !value.canConvert<bool>() ) return false; bool old_state = pathInfo.pathValidity(); // checks if path is valid pathInfo.checkPathValidity(); if ( old_state != pathInfo.pathValidity() ) { // update FileType and ValidityChip col emit dataChanged( fileTypeIdx, fileTypeIdx, { Qt::DisplayRole } ); emit dataChanged( validityIdx, validityIdx, { Qt::DisplayRole } ); return true; } return true; } else if ( col == 3 ) // edit the isOpenable_col { if ( !value.canConvert<bool>() ) return false; pathInfo.setOpenable( value.toBool() ); emit dataChanged( index, index, { Qt::DisplayRole, Qt::EditRole } ); return true; } return false; } return false; } QVariant PathsInfoTableModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( orientation != Qt::Horizontal || role != Qt::DisplayRole ) return QVariant(); switch ( section ) { case 0: return "Type"; case 1: return "Full Path"; case 2: return "Validity State"; case 3: return "Openable"; default: return QVariant(); } } void PathsInfoTableModel::setListData( QList<PathInfo>& new_paths_list ) { beginResetModel(); m_model_data = new_paths_list; endResetModel(); }
-
-
I might have found a possible solution for what is happening.
1. QDebug() inside the model's data() method
I placed a qDebug() line to inspect the values of
index.row() ...................... // which is 0 in my case (the first row)
index.column() ............... // which is 1 ( 1 is the correct index where the path resides on the model
role.....................................// this is the int role given by the data()'s parameter indicating an Enum::ItemDataRole https://doc.qt.io/qt-6/qt.html#ItemDataRole-enumimportant
keeping only my widgetMapper I saw that the value for role was 2 ( meaning Qt::EditRole ) and I found that this is the default behavior when a QDataWidgetMapper maps a model value to a QWidget intended for editing the model
I was expecting a Qt::DisplayRole and I didn't account for EditRole, so my model was returning nothing to the mapper causing a blank widget. The reason why the Mapper uses the EditRole ( accordingly with DeepSeek ) is that, this role return a raw data rappresentation of the model, whereas the DisplayRole can be modified by the user who can add custom formatting before displaying it to the model view.
I will leave the DeepSeek response to my question Why would some QObject use a Qt::EditRole inside a model::data() ? Does it make sense only used in the model::setModel() for changing values?
Roles in Qt Models
In Qt's model-view architecture, roles are used to distinguish between different types of data associated with an item in a model. For example:
-
Qt::DisplayRole: Used for data that is displayed (e.g., text in a view).
-
Qt::EditRole: Used for data that is intended for editing.
-
Qt::ToolTipRole: Used for tooltips.
-
Qt::BackgroundRole: Used for background color.
Each role serves a specific purpose, and the model's data() method can return different values depending on the role requested.
Why Qt::EditRole is Used in data()
The Qt::EditRole is not just for setData(); it can also be used in data() for specific scenarios. Here's why:
-
Editing Widgets:
When a widget (like QLineEdit, QPlainTextEdit, etc.) is used for editing, it requests data from the model using Qt::EditRole. This ensures that the data it receives is in a format suitable for editing.
For example, a QLineEdit might request data with Qt::EditRole to populate its text field when the user starts editing. -
Consistency Between Display and Edit Data:
Sometimes, the data displayed (Qt::DisplayRole) and the data edited (Qt::EditRole) might differ. For instance:
A date might be displayed as "2023-10-25" (using Qt::DisplayRole) but edited as a QDateTime object (using Qt::EditRole).
By using Qt::EditRole in data(), the model can provide the data in a format that is optimized for editing. -
Custom Delegates:
Custom delegates might request data using Qt::EditRole to ensure they get the raw, editable data rather than a formatted version (which might be returned for Qt::DisplayRole). -
Default Behavior of QDataWidgetMapper:
As you've noticed, QDataWidgetMapper uses Qt::EditRole by default when fetching data from the model. This is because it assumes the widgets it maps to are editable, and it needs the data in a format suitable for editing.
When Does Qt::EditRole Make Sense in data()?
Here are some scenarios where Qt::EditRole is useful in data():
-
Editable Widgets:
If a widget is used for editing (e.g., QLineEdit, QComboBox), it will request data using Qt::EditRole. The model should return the data in a format that the widget can edit. -
Data Transformation:
If the data displayed (Qt::DisplayRole) is a transformed version of the raw data (e.g., a formatted string), the model should return the raw data for Qt::EditRole.
QVariant MyModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { return QString("Value: %1").format(rawData(index)); // Formatted for display } else if (role == Qt::EditRole) { return rawData(index); // Raw data for editing } return QVariant(); }
- Custom Delegate:
If you're using a custom delegate, it might request data using Qt::EditRole to ensure it gets the raw data for editing.
Hope this will help someone else too ;)
-
-
I have to make 2 different post because the first was getting to long.
the problems:
there is something in the model resetting when a change the old QList with the new QList retrived from the QMap that is causing segmentation fault. I Post the full code on gitHub (https://github.com/aVenturelli-qt/ArchWay-Project-Manager) because it will never fit to the forum.
How can I have a model pointing to QList stored in a QMap that can be directly modified by the model and when i'm done editing it and switch to a dfferent Qlist (also contained in the QMap but inside a different key) a can reset the Model with new Data ?
-
@Andrea_Venturelli said in Refreshing a Custom Model data (QList<UserDefinedClass>) and swapping the lists contained in a QMap:
beginResetModel();
m_model_data = new_paths_list;
endResetModel();I would have suggested exactly this now...
This doesn't work for you?!Reset the model when you switch to new list and assign the new one to update all views
-
commenting out the instanciation of the left widget will prevent the segmentation fault problem, so the problem mist be in some custom slot where i update the values after the “modelChanged()” signal.
it seems like the model exist for all the component that uses it but when accesing the have a reference to some non existing data and crush..
without a deep knowledge of QMap<> i think I’m missing some core concepts that is casuing the problem after the model resetted
-
Hello. After further investigation I found out why the app crash every time the MapperWidget custom class was used.
The problem was with a member_variable of type PathInfo* that was not initialized, so the program tryes to use a null pointer causing the crash.I solve it simply adding in the constructor the following piece:
MapperPathInfo::MapperPathInfo(PathsInfoTableModel* model, QWidget* parent) : QWidget(parent), ui(new Ui::MapperPathInfo), m_model{ model }, /* the following */ p_info{ new PathInfo } {
For the problem about passing a QList stored in a QMap for refreshing the model's data, I had to change the custom Model definition to accept a QList* (QList prt) instead of a QList&, and also change the member variable to
QList* m_data;
class PathsInfoTableModel : public QAbstractTableModel { Q_OBJECT public: explicit PathsInfoTableModel( QList<PathInfo>* group, QObject *parent = nullptr ); // virtual override int rowCount( const QModelIndex &parent = QModelIndex() ) const override; int columnCount( const QModelIndex &parent = QModelIndex() ) const override; QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; bool setData( const QModelIndex& index, const QVariant& value, int role ) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QList<PathInfo>* getPathInfoList() { return m_model_data; } void setListData( QList<PathInfo>* new_paths_list ); private: QList<PathInfo>* m_model_data; };
DataWidgetMapper don't read the value from the model
There is still a Big problem i'm trying to figure out, and that is about the QDataWidgetMapper which isn't able to read the correct value from the model, and set it to the mapped widget...
What I Tryed so far:
-
I isolate the MapperPathInfo (custom widget with a QDataWidgetMapper as member_variable) and tryed to test it alone, mapping a single value from the model instead of three. I choose to keep the second column (index is 1) which rappresent a path as a QString.
-
I setUp a basic QTableView which has the QItemSelectionModel::selectionChanged() signal connected to a slots that set the same new index selected, on the QDataWidgetMapper too.
-
I connected also the QDataWidget::currentIndexChanged() signal to a lambda that print the value of the new setted index to check that is working (and works)
-
I even tryed to map the mapper to a QLineEdit instead of a QPlainTextEdit becauseI supposed that a Multi-line text could potencially cause the issue, but the QLineEdit, also doesn't display nothing... The interesting part I saw, was if I write on the QLineEdit, is text is reflected on the QTableView ( and soon after erased when I change the selected row )
There something I can't see either in the DataWidgetMapper setup or in the model "setData() and data()" can someone help me figuring it out ?
MapperPathInfo::MapperPathInfo(PathsInfoTableModel* model, QWidget* parent) : QWidget(parent), ui(new Ui::MapperPathInfo), m_model{ model }, p_info{ new PathInfo } { ui->setupUi(this); ui->full_path_pte->setWordWrapMode(QTextOption::WrapAnywhere); m_mapper = new QDataWidgetMapper( this ); m_mapper->setModel( m_model ); // widgets mapping //m_mapper->addMapping( ui->full_path_pte, ColDescription::FilePath ); // pte -> PlainTextEdit (set as readOnly) m_mapper->addMapping( ui->my_le, 1 ); // le --> LineEdit //m_mapper->addMapping( validity_chip, ColDescription::IsValidChip ); //m_mapper->addMapping( toggle_sw, ColDescription::IsOpenable ); m_mapper->setCurrentIndex(0); // add custom widgets to UI toggle_sw->setMinimumWidth(80); ui->state_hlay->insertWidget(1, validity_chip); ui->buttons_vlay->addWidget(toggle_sw, 0); // connecting Toggle_sw toggled signal to slot openableToggled // sync the state with the model and with the view (as a side effect) connect( toggle_sw, &ViewToggleSw::toggled, this, &MapperPathInfo::openableToggled ); // could be ignored connect( ui->full_path_pte, &QPlainTextEdit::textChanged, this, &MapperPathInfo::updateFileNameLbl ); connect( ui->my_le, &QLineEdit::textChanged, this, &MapperPathInfo::updateFileNameLbl ); connect(m_mapper, &QDataWidgetMapper::currentIndexChanged, this, [](int index){ if (index == -1) { // -1 means no valid index qDebug() << "Mapper index is invalid!"; } else { qDebug() << "Mapper index changed to:" << index; } }); } MapperPathInfo::~MapperPathInfo() { delete p_info; delete ui; } void MapperPathInfo::updateFileNameLbl() { // when the the plain-text-edit is changed, // this method will update the file name Label // accordingly with the path qDebug() << "[DataWidgetMapper] plain text edit, textChanged()"; //auto new_path = ui->full_path_pte->toPlainText(); auto new_path = ui->my_le->text(); p_info->changePath( new_path ); qDebug() << "\nnew_path:" << new_path; qDebug() << ""; ui->path_name_lbl->setText( p_info->fileName() ); } void MapperPathInfo::setMapperIdx( int row ) { m_mapper->setCurrentIndex( row ); }
-
-
here the MainWindow created for the testing:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); ui->mapper_frame->setLayout( new QVBoxLayout ); auto mapper_layout = ui->mapper_frame->layout(); // create a member list to store the values auto path_info1 = PathInfo("C:\\Users\\test\\OneDrive\\Documenti"); auto path_info2 = PathInfo("C:\\Users\\test\\OneDrive\\Documenti\\Desktop\\someExcel.xlsx"); auto path_info3 = PathInfo("C:\\Users\\test\\OneDrive\\Documenti\\Desktop\\desk prj.blend"); m_data << path_info1 << path_info2 << path_info3; // create the CustomModel m_model = new PathsInfoTableModel( &m_data, this); ui->Tview->setModel( m_model ); ui->Tview->setSelectionMode(QAbstractItemView::SingleSelection); // create an instance of MapperPathInfo my_mapper = new MapperPathInfo( m_model ); mapper_layout->addWidget( my_mapper ); auto selectionModel = ui->Tview->selectionModel(); connect( selectionModel, &QItemSelectionModel::currentChanged, this, &MainWindow::updateMapperValues ); /* int_mapper = new QDataWidgetMapper(this); int_mapper->setModel( ui->Lwidget->model() ); int_mapper->addMapping( ui->my_spin, 0); int_mapper->setCurrentIndex(0); auto selectionModel = ui->Lwidget->selectionModel(); connect( selectionModel, &QItemSelectionModel::currentChanged, this, &MainWindow::updateMapperValues ); */ } void MainWindow::updateMapperValues(const QModelIndex& new_selection, const QModelIndex& previous_selection ) { Q_UNUSED( previous_selection ); qDebug() << "Selection changed:" ; int current_row = new_selection.row(); my_mapper->setMapperIdx( current_row ); //int_mapper->setCurrentIndex( current_row ); qDebug() << "row=" << current_row ; qDebug() << "" ; } MainWindow::~MainWindow() { delete m_model; delete ui; }
the mapper commented in the MainWindow Cunstructor was a test to see if a simple list filed with int will populate correctly the value of QSpinBox when the selectionModel of the ListWidget change and it does obviouslly
-
I might have found a possible solution for what is happening.
1. QDebug() inside the model's data() method
I placed a qDebug() line to inspect the values of
index.row() ...................... // which is 0 in my case (the first row)
index.column() ............... // which is 1 ( 1 is the correct index where the path resides on the model
role.....................................// this is the int role given by the data()'s parameter indicating an Enum::ItemDataRole https://doc.qt.io/qt-6/qt.html#ItemDataRole-enumimportant
keeping only my widgetMapper I saw that the value for role was 2 ( meaning Qt::EditRole ) and I found that this is the default behavior when a QDataWidgetMapper maps a model value to a QWidget intended for editing the model
I was expecting a Qt::DisplayRole and I didn't account for EditRole, so my model was returning nothing to the mapper causing a blank widget. The reason why the Mapper uses the EditRole ( accordingly with DeepSeek ) is that, this role return a raw data rappresentation of the model, whereas the DisplayRole can be modified by the user who can add custom formatting before displaying it to the model view.
I will leave the DeepSeek response to my question Why would some QObject use a Qt::EditRole inside a model::data() ? Does it make sense only used in the model::setModel() for changing values?
Roles in Qt Models
In Qt's model-view architecture, roles are used to distinguish between different types of data associated with an item in a model. For example:
-
Qt::DisplayRole: Used for data that is displayed (e.g., text in a view).
-
Qt::EditRole: Used for data that is intended for editing.
-
Qt::ToolTipRole: Used for tooltips.
-
Qt::BackgroundRole: Used for background color.
Each role serves a specific purpose, and the model's data() method can return different values depending on the role requested.
Why Qt::EditRole is Used in data()
The Qt::EditRole is not just for setData(); it can also be used in data() for specific scenarios. Here's why:
-
Editing Widgets:
When a widget (like QLineEdit, QPlainTextEdit, etc.) is used for editing, it requests data from the model using Qt::EditRole. This ensures that the data it receives is in a format suitable for editing.
For example, a QLineEdit might request data with Qt::EditRole to populate its text field when the user starts editing. -
Consistency Between Display and Edit Data:
Sometimes, the data displayed (Qt::DisplayRole) and the data edited (Qt::EditRole) might differ. For instance:
A date might be displayed as "2023-10-25" (using Qt::DisplayRole) but edited as a QDateTime object (using Qt::EditRole).
By using Qt::EditRole in data(), the model can provide the data in a format that is optimized for editing. -
Custom Delegates:
Custom delegates might request data using Qt::EditRole to ensure they get the raw, editable data rather than a formatted version (which might be returned for Qt::DisplayRole). -
Default Behavior of QDataWidgetMapper:
As you've noticed, QDataWidgetMapper uses Qt::EditRole by default when fetching data from the model. This is because it assumes the widgets it maps to are editable, and it needs the data in a format suitable for editing.
When Does Qt::EditRole Make Sense in data()?
Here are some scenarios where Qt::EditRole is useful in data():
-
Editable Widgets:
If a widget is used for editing (e.g., QLineEdit, QComboBox), it will request data using Qt::EditRole. The model should return the data in a format that the widget can edit. -
Data Transformation:
If the data displayed (Qt::DisplayRole) is a transformed version of the raw data (e.g., a formatted string), the model should return the raw data for Qt::EditRole.
QVariant MyModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { return QString("Value: %1").format(rawData(index)); // Formatted for display } else if (role == Qt::EditRole) { return rawData(index); // Raw data for editing } return QVariant(); }
- Custom Delegate:
If you're using a custom delegate, it might request data using Qt::EditRole to ensure it gets the raw data for editing.
Hope this will help someone else too ;)
-
-
@Andrea_Venturelli
In addition to your true statements about the use ofEditRole
vsDisplayRole
.If you ever intend to sort or order your data (including for a
QTableView
) using anything like aQSortFilterProxyModel
and you are doing something like makingDisplayRole
convert a number or a date to text, QSFPM uses sortRole to compare eachdata()
item. That defaults toDisplayRole
, which is bad for sorting/ordering this kind of data with its transformation to string. You should make yourdata()
also return the "raw" data, likeEditRole
, by adding aSortRole
case. -