Custom QStyledItemDelegate to customize apperence of QListView with a SQLModel under the hood
-
exactly ! you get it right ahah
I printed the flags and the first give me “isEditable” with all the rest usual thinks (selectable, enabled, nochild)
but all the others don’t have it.. this looks strange to me. I’ll investigate deeper soon and I’ll post if I find something interesting -
@Andrea_Venturelli
That comes from the model'sflags()
implementation. Either you have done something there for row #0 but not others, or if you are saying this is directly theQSqlTableModel
then I don't see why it would return different values per row. -
@JonB , @SGaist after a little more debug this is what I noticed:
-
- the first item I DOUBLE-CLICK (editor trigger for modification) no matter if it was the first ListStyledItemDelegate rappresented in the ListView, the second, the third or the n_th element, became editable and remain editable. After that, all the others items flags are as default ( Selectable and Editable)
as you can see, the fourth item in the list is editable, because it was the first I double clicked.
If I close the application and run it again and double-clicking any other item first, it will be editable and the fourth is only selectable..
-
- after the body_text being changed, and the key Ctrl+Return being pressed (used for trigger CommitAndClose editor) the method "QStyledItemDelegate::setModelData( param1, param2, param3) doesn't set the new body text correctly as shown here:
void CommentListItem::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const { if ( index.siblingAtColumn(2).data().canConvert<QString>() ) { qDebug() << "\n\t[::setModelData()] being called.."; \\ print_1 CommentEditor* cm_editor = qobject_cast<CommentEditor*>(editor); auto cmm_wdg = cm_editor->commentWidget(); auto body_txt = cmm_wdg.bodyText(); if ( cm_editor->bodyText() != body_txt ) { qDebug() << "\t[::setModelData()--> body_text different detected] inside if call.."; \\ print_2 /* generate the edited_mess_date */ QDateTime time_now = QDateTime::currentDateTime(); auto edited_mess_date = QString("Last Modification: "); edited_mess_date += time_now.toString("ddd, MMM dd HH:mm"); qDebug() << "\t - 'edited_mess_date' = " << edited_mess_date; \\ print_3 /* update the model */ qDebug() << "index.siblingAtColumn(4):\t" << index.siblingAtColumn(4); \\ print_4 qDebug() << "index.siblingAtColumn(5):\t" << index.siblingAtColumn(5); \\ print_5 model->setData( index.siblingAtColumn(4), QVariant::fromValue( body_txt ) ); model->setData( index.siblingAtColumn(5), QVariant::fromValue( edited_mess_date ) ); } } }
the prints are:
Ctrl + Return keys detected
committing editor...
[::setModelData()] being called.. <------- print_1[::setModelData()--> body_text different detected] inside if call.. <------- print_2 - 'edited_mess_date' = "Last Modification: Tue, Nov 05 09:30" <------- print_3
index.siblingAtColumn(4): QModelIndex(2,4,0x0,QSqlTableModel(0x23a1bceac10)) <------- print_4
index.siblingAtColumn(5): QModelIndex(2,5,0x0,QSqlTableModel(0x23a1bceac10)) <------- print_5 -
-
MainWindow code where the SetUp of the model occur.
private:
Ui::MainWindow ui;
QSqlDatabase _m_db;
QSqlQuery _m_query;
QSqlTableModel _m_model;MainWidow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); this->setUpDb(); // set the model to QList and QTable // _m_model = new QSqlQueryModel; _m_model = new QSqlTableModel; _m_model->setQuery("SELECT * FROM veh_comments;"); ui->tb_view->setModel(_m_model); auto header = ui->tb_view->horizontalHeader(); header->setSectionResizeMode(QHeaderView::ResizeToContents); ui->tb_view->show(); ui->lst_vw->setItemDelegate( new CommentListItem ); ui->lst_vw->setEditTriggers(QAbstractItemView::EditTrigger::DoubleClicked); ui->lst_vw->setModel(_m_model); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_serach_le_returnPressed() { // read the id from the lineEdit auto id = ui->serach_le->text(); if (id == "") { QSqlQuery query("SELECT * FROM veh_comments"); _m_model->setQuery(std::move(query)); } else { // UPDATE VIEW auto query = commentsPerChassisId(id); _m_model->setQuery(std::move(query)); } qDebug() << "Line Edit \"Return_Key\" pressed..\n\n"; } QSqlQuery MainWindow::commentsPerChassisId(const QString& id) { QSqlQuery my_query(_m_db); my_query.prepare( "SELECT " "veh.*, us.user_name " "FROM veh_comments AS veh " "INNER JOIN " "users AS us " "ON us.id = veh.user_id " "WHERE " "veh_serial = ? " "ORDER BY " "comment_datetime DESC " ); my_query.addBindValue(id); my_query.exec(); return my_query; } void MainWindow::setUpDb() { _m_db = QSqlDatabase::addDatabase("QPSQL"); _m_db.setHostName("localhost"); _m_db.setDatabaseName("test"); _m_db.setUserName("postgres"); _m_db.setPassword("MyPostGresPSW0!"); if (!_m_db.open()) { qDebug() << "Error: Could not open database"; } }
After the "QStyledItemDelegate::setEditorData()", should I update the model, refresh it or something similar to save permanently the data to the SQL ?
-
@Andrea_Venturelli
Your posts are large and contain several questions. It's hard to keep up!I would start by sorting out the editability. Before you double-click to edit, print out the
flags()
of each item in the model. Do they all containItemIsEditable
? Then after double-clicking to edit, both then and after the edit is finished, print them all out again. Are you saying some/all of them lose theItemIsEditable
?doesn't set the new body text correctly as shown
I can't read the screenshot to see what you are saying. Is it just some sort of refresh issue, where the data has changed (automatically shown in the
QTableView
onto the SQL model) while it remains as-was on theQListView
item? Your view needs to know that the model data has changed and refresh accordingly, if it does not already do so automatically (as aQTableView
would).After the "QStyledItemDelegate::setEditorData()", should I update the model, refresh it or something similar to save permanently the data to the SQL ?
You have to start by deciding which QSqlTableModel::EditStrategy you are using/wish to use.
-
@JonB I didn't change nothing special beside adding the qDebug() line to print the flags and now all the items are editable and the EditorWidget show up correctly for all ... that's new.
I guess the "editing part is fixed now, but for simplicity I posted all the code on github here: https://github.com/aVenturelli-qt/CommentsListView?tab=readme-ov-file
-
You may have missed it but the way to use QSqlTableModel is to set the table you want to use on it rather than the query.
-
@SGaist I knew it could potentially become an issue, but I decided to use that model temporarily to create and test the UI.
I guess this is also the cause why I can't subscribe the changes to the model.Now I'm refactoring the code to make it cleaner, after that I will try to use a QSqlRelationalModel or give up and create a customModel to filter and sort the data without a QFilterSortProxyModel..
this is how my database is Structed at the moment:
-
Okay, thanks to everyone who helped me, to resolve the varius bugs and expand my knowlage.
here the recap of how to create and customize the view:step 1)
Create a normal c++ Class containing the Data you want to display (not inheriting from QWidget)
step 2)
on the Custom Class implement a method "paint(QPainter *painter, const QRect &rect, const QPalette &palette)" that will be used inside the QStyledItemDelegate::paint() method of the custom QStyledItemList for delegating the drawing part to the normal c++ class
step 3)
create a widgetEditor (derived from QWidget) that implement some default Widgets (like QComboBox, QLineEdit, QSpinBox ecc..) to let the user change the data.
When the user have finished to edit, you can provide a "button" to emit a custom signal like "commitAndCloseEditor()" so you can trigger the QStyledItemDelegate::commitData() and closeEditor() signal needed for destroy the Editor once finished the editing.
step 4)
for the custom QStyledItemDelegate to work, you must re-implement the following methods:
- void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
- QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
- void setEditorData( QWidget* editor, const QModelIndex &index ) const
- void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
- QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index )
step 5)
use the method from the QIndexModel to detect if the model have the correct data that can be converted correctly to your vanilla c++ class from the point (1)
NOTE: see the StarDelegate Example provided by Qt. If the underling model have contains a int, using QModelIndex::.data().canConvert<StarRating>() checks if passing a int to the constructor of StarRating will be enough to initialize it.
remember to declare the vanilla c++ class with the Qt's macro like so: Q_DECLARE_METATYPE(StarRating)you can found the complete code (with notes and comments) on github at: https://github.com/aVenturelli-qt/CommentsListView/tree/main
I still need to fix the data subscription to the model, but this is a separate topic that I will ask on another question if needed. You can find the problems highlighted with a comment like:
/* WARNING HERE */ -
-
for the one interested in this topic, I updated the code on gitHub and now is possible to edit the model and save the changes within the listView with the help of a specialized QStyledItemDelegate.
link: https://github.com/aVenturelli-qt/CommentsListViewIn the README there are all the information needed for the setUp and run