Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole

How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole

Scheduled Pinned Locked Moved Unsolved General and Desktop
10 Posts 3 Posters 293 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    Mark81
    wrote on last edited by Mark81
    #1

    I wrote this delegate in Qt 6.8.2:

    #ifndef DELEGATEEVENTS_H
    #define DELEGATEEVENTS_H
    
    #include <QSqlRelationalDelegate>
    #include <QComboBox>
    #include <QSqlDatabase>
    #include <QSqlTableModel>
    #include <QSqlQuery>
    #include <QSqlError>
    #include <QDebug>
    
    #include "mainwindow.h"
    #include "formprotocols.h"
    
    class DelegateEvents : public QSqlRelationalDelegate
    {
        Q_OBJECT
    
    public:
        DelegateEvents(QObject *parent = nullptr) : QSqlRelationalDelegate(parent)
        {
        }
    
        QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
        {
            const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model());
    
            QModelIndex idx = sqlModel->index(index.row(), static_cast<int>(FormProtocols::Columns::MODEL_COL_TYPE));
            QString text = sqlModel->data(idx, Qt::EditRole).toString();
            if (text.isEmpty()) return nullptr;
    
            MainWindow::EventTypes eventType;
            bool ok;
            int i = text.toInt(&ok);
            if (ok) eventType = static_cast<MainWindow::EventTypes>(i);
            else
            {
                if (text == "Alarm") eventType = MainWindow::EventTypes::Alarm;
                else if (text == "Session") eventType = MainWindow::EventTypes::Session;
                else return nullptr;
            }
    
            QComboBox *editor = new QComboBox(parent);
            editor->setFrame(false);
    
            QSqlDatabase db = QSqlDatabase::database("mydb");
            QSqlQuery query(db);
    
            switch (eventType)
            {
            case MainWindow::EventTypes::Alarm:
                query.exec("SELECT id, gb FROM alarms;");
    
                while (query.next())
                {
                    int id = query.value("id").toInt();
                    QString name = query.value("gb").toString();
                    editor->addItem(name, id);
                }
                break;
    
            case MainWindow::EventTypes::Session:
                query.exec("SELECT id, gb FROM sessions;");
    
                while (query.next())
                {
                    int id = query.value("id").toInt();
                    QString name = query.value("gb").toString();
                    editor->addItem(name, id);
                }
                break;
            }
    
            return editor;
        }
    
        void setEditorData(QWidget *editor, const QModelIndex &index) const override
        {
            int value = index.model()->data(index, Qt::EditRole).toInt();
    
            QComboBox *w = static_cast<QComboBox*>(editor);
            w->setCurrentIndex(w->findData(value));
        }
    
        void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
        {
            QComboBox *w = static_cast<QComboBox*>(editor);
            int value = w->currentData().toInt();
            model->setData(index, "foo", Qt::DisplayRole); // <-- this is not shown
            model->setData(index, value, Qt::EditRole);
        }
    };
    
    #endif // DELEGATEEVENTS_H
    

    The goal is to fit a specific request: basically the column where the delegate is installed will show the values from another table according to a specific value of the current FormProtocols::Columns::MODEL_COL_TYPE text.

    When I create the editor I add the items in the QComboBox using the name as text and id as user-data. In this way when I have to set the editor value I can retrieve the correct item using the data property.

    On the other side, when I need to write back the values to the model I set both the EditRole (the actual integer value that is committed to the database) and the DisplayRole that should printed in the cell of the QTableView. My current test implementation above should always print "foo". Instead it shows the EditRole, i.e. the id value.

    QSqlRelationalDelegate does not reimplement the paint function, hence I checked the source code for the QStyledItemDelegate and it seems it should print the DisplayRole:

        value = modelRoleDataSpan.dataForRole(Qt::DisplayRole);
        if (value->isValid() && !value->isNull()) {
            option->features |= QStyleOptionViewItem::HasDisplay;
            option->text = displayText(*value, option->locale);
        }
    

    In order to debug what's happening I added a dummy paint function:

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        QSqlRelationalDelegate::paint(painter, option, index);
    }
    

    so I was able to place a breakpoint and go deeper to reach the QStyledItemDelegate level. But the debugger told me the actual value for the DisplayRole was 3 (the id in my test case).

    Hence I tried to understand why and I changed the function like this:

    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
    {
        QComboBox *w = static_cast<QComboBox*>(editor);
        int value = w->currentData().toInt();
        model->setData(index, "foo", Qt::DisplayRole);
        model->setData(index, value, Qt::EditRole);
        qDebug() << model->data(index, Qt::EditRole) << model->data(index, Qt::DisplayRole);
    }
    

    and the output was:

    QVariant(qlonglong, 3) QVariant(qlonglong, 3)

    so it does not store the DisplayRole value. Why?

    Unfortunately I cannot debug the setData function: placing a breakpoint there will hang the whole screen, nothing reacts anymore. I had to kill gdb from SSH. Tried few times.

    I checked the return value of the first setData() and it was false, but in the documentation I cannot find how to find out the reasons (like the lastError for queries).

    JonBJ 1 Reply Last reply
    0
    • M Mark81

      I wrote this delegate in Qt 6.8.2:

      #ifndef DELEGATEEVENTS_H
      #define DELEGATEEVENTS_H
      
      #include <QSqlRelationalDelegate>
      #include <QComboBox>
      #include <QSqlDatabase>
      #include <QSqlTableModel>
      #include <QSqlQuery>
      #include <QSqlError>
      #include <QDebug>
      
      #include "mainwindow.h"
      #include "formprotocols.h"
      
      class DelegateEvents : public QSqlRelationalDelegate
      {
          Q_OBJECT
      
      public:
          DelegateEvents(QObject *parent = nullptr) : QSqlRelationalDelegate(parent)
          {
          }
      
          QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
          {
              const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model());
      
              QModelIndex idx = sqlModel->index(index.row(), static_cast<int>(FormProtocols::Columns::MODEL_COL_TYPE));
              QString text = sqlModel->data(idx, Qt::EditRole).toString();
              if (text.isEmpty()) return nullptr;
      
              MainWindow::EventTypes eventType;
              bool ok;
              int i = text.toInt(&ok);
              if (ok) eventType = static_cast<MainWindow::EventTypes>(i);
              else
              {
                  if (text == "Alarm") eventType = MainWindow::EventTypes::Alarm;
                  else if (text == "Session") eventType = MainWindow::EventTypes::Session;
                  else return nullptr;
              }
      
              QComboBox *editor = new QComboBox(parent);
              editor->setFrame(false);
      
              QSqlDatabase db = QSqlDatabase::database("mydb");
              QSqlQuery query(db);
      
              switch (eventType)
              {
              case MainWindow::EventTypes::Alarm:
                  query.exec("SELECT id, gb FROM alarms;");
      
                  while (query.next())
                  {
                      int id = query.value("id").toInt();
                      QString name = query.value("gb").toString();
                      editor->addItem(name, id);
                  }
                  break;
      
              case MainWindow::EventTypes::Session:
                  query.exec("SELECT id, gb FROM sessions;");
      
                  while (query.next())
                  {
                      int id = query.value("id").toInt();
                      QString name = query.value("gb").toString();
                      editor->addItem(name, id);
                  }
                  break;
              }
      
              return editor;
          }
      
          void setEditorData(QWidget *editor, const QModelIndex &index) const override
          {
              int value = index.model()->data(index, Qt::EditRole).toInt();
      
              QComboBox *w = static_cast<QComboBox*>(editor);
              w->setCurrentIndex(w->findData(value));
          }
      
          void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
          {
              QComboBox *w = static_cast<QComboBox*>(editor);
              int value = w->currentData().toInt();
              model->setData(index, "foo", Qt::DisplayRole); // <-- this is not shown
              model->setData(index, value, Qt::EditRole);
          }
      };
      
      #endif // DELEGATEEVENTS_H
      

      The goal is to fit a specific request: basically the column where the delegate is installed will show the values from another table according to a specific value of the current FormProtocols::Columns::MODEL_COL_TYPE text.

      When I create the editor I add the items in the QComboBox using the name as text and id as user-data. In this way when I have to set the editor value I can retrieve the correct item using the data property.

      On the other side, when I need to write back the values to the model I set both the EditRole (the actual integer value that is committed to the database) and the DisplayRole that should printed in the cell of the QTableView. My current test implementation above should always print "foo". Instead it shows the EditRole, i.e. the id value.

      QSqlRelationalDelegate does not reimplement the paint function, hence I checked the source code for the QStyledItemDelegate and it seems it should print the DisplayRole:

          value = modelRoleDataSpan.dataForRole(Qt::DisplayRole);
          if (value->isValid() && !value->isNull()) {
              option->features |= QStyleOptionViewItem::HasDisplay;
              option->text = displayText(*value, option->locale);
          }
      

      In order to debug what's happening I added a dummy paint function:

      void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
      {
          QSqlRelationalDelegate::paint(painter, option, index);
      }
      

      so I was able to place a breakpoint and go deeper to reach the QStyledItemDelegate level. But the debugger told me the actual value for the DisplayRole was 3 (the id in my test case).

      Hence I tried to understand why and I changed the function like this:

      void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
      {
          QComboBox *w = static_cast<QComboBox*>(editor);
          int value = w->currentData().toInt();
          model->setData(index, "foo", Qt::DisplayRole);
          model->setData(index, value, Qt::EditRole);
          qDebug() << model->data(index, Qt::EditRole) << model->data(index, Qt::DisplayRole);
      }
      

      and the output was:

      QVariant(qlonglong, 3) QVariant(qlonglong, 3)

      so it does not store the DisplayRole value. Why?

      Unfortunately I cannot debug the setData function: placing a breakpoint there will hang the whole screen, nothing reacts anymore. I had to kill gdb from SSH. Tried few times.

      I checked the return value of the first setData() and it was false, but in the documentation I cannot find how to find out the reasons (like the lastError for queries).

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #2

      @Mark81
      I am not sure I follow all that you have done/said. However, the way things work with a QSqlRelation is that the data table stores the desired value (like an integer id or similar) as its EditRole and the delegate looks that up in the foreign table to find the display value from there. Presumably ignoring any DisplayRole it would normally use for a data table and doing its own look up instead.

      M 1 Reply Last reply
      0
      • JonBJ JonB

        @Mark81
        I am not sure I follow all that you have done/said. However, the way things work with a QSqlRelation is that the data table stores the desired value (like an integer id or similar) as its EditRole and the delegate looks that up in the foreign table to find the display value from there. Presumably ignoring any DisplayRole it would normally use for a data table and doing its own look up instead.

        M Offline
        M Offline
        Mark81
        wrote on last edited by Mark81
        #3

        @JonB ok, I changed my class so now it is a subclass of QStyledItemDelegate but nothing has changed. The behavior is the same.

        By the way, I tried to reimplement displayText, but since it does not provide an index (just the text to be printed) I cannot retrieve the associated data from the model.

        JonBJ 1 Reply Last reply
        0
        • M Mark81

          @JonB ok, I changed my class so now it is a subclass of QStyledItemDelegate but nothing has changed. The behavior is the same.

          By the way, I tried to reimplement displayText, but since it does not provide an index (just the text to be printed) I cannot retrieve the associated data from the model.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by
          #4

          @Mark81
          I have to confess I don't know what you are trying to "fix". I don't know what you expect to "change". It may (well) be that I don't understand what you are saying, but I don't think you can change the behaviour of DisplayRole/display text.

          Nor do I understand why you are trying to do so. The whole point of QSqlRelationalTableModel is just to allow columns of a source table to hold a foreign key into a different table, so that the value (typically an integer "id", and stored in EditRole) is looked up in a foreign table and another column there (typically some text) is used as the DisplayRole value in the source table instead of its own. And QSqlRelationalDelegate is just for editing the source table in this situation, so that e.g. a combobox of the text values from the foreign table can be offered to the user when editing the source table instead of dealing with the underlying id number.

          M 1 Reply Last reply
          1
          • JonBJ JonB

            @Mark81
            I have to confess I don't know what you are trying to "fix". I don't know what you expect to "change". It may (well) be that I don't understand what you are saying, but I don't think you can change the behaviour of DisplayRole/display text.

            Nor do I understand why you are trying to do so. The whole point of QSqlRelationalTableModel is just to allow columns of a source table to hold a foreign key into a different table, so that the value (typically an integer "id", and stored in EditRole) is looked up in a foreign table and another column there (typically some text) is used as the DisplayRole value in the source table instead of its own. And QSqlRelationalDelegate is just for editing the source table in this situation, so that e.g. a combobox of the text values from the foreign table can be offered to the user when editing the source table instead of dealing with the underlying id number.

            M Offline
            M Offline
            Mark81
            wrote on last edited by
            #5

            @JonB thanks for your effort and patience, surely my English is not helping here.
            My post was quite long because I provided a MRE, my attempts and my researches.

            Anyway I try to explain it in another way. Please, feel free to ask what is not clear.

            In table protocols there is a column (idEvent) that is the foreign key towards another table (say alarms). If it was so simple, the standard QSqlRelationalTableModel behavior was just fine.

            My problem is the protocols table has another column (eventType) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into the alarms table, if the value is 1 it must lookup into the sessions table and so on.

            Of course this very specific (and awkward...) behavior is not provided by the QSqlRelationalTableModel and QSqlRelationalDelegate.

            My attempt was to manually select the right table in function of the eventType value and add the id and the name of the rows to the QComboBox (as text and user-data). And this is done and working in the createEditor() function.

            When the user open the editor I select the correct text finding out the current user-data (that is the id stored in the database as foreign key in the protocols table) and also this seems to work.

            My issue is when the user close the editor and I need to write to the model the new value: the Qt::EditRole should be the id of the related row (and this is ok) but since I would show in the QTableView the name of the linked row (exactly as the standard lookup would do) my approach was to set this string to the Qt::DisplayRole hoping it will be painted to the cell. But I cannot set the Qt::DisplayRole to the model: the setData() fails.

            If you might suggest another approach (that does not require to change the db structure, since its not mine) I will be glad to try it.

            Christian EhrlicherC JonBJ 2 Replies Last reply
            0
            • M Mark81

              @JonB thanks for your effort and patience, surely my English is not helping here.
              My post was quite long because I provided a MRE, my attempts and my researches.

              Anyway I try to explain it in another way. Please, feel free to ask what is not clear.

              In table protocols there is a column (idEvent) that is the foreign key towards another table (say alarms). If it was so simple, the standard QSqlRelationalTableModel behavior was just fine.

              My problem is the protocols table has another column (eventType) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into the alarms table, if the value is 1 it must lookup into the sessions table and so on.

              Of course this very specific (and awkward...) behavior is not provided by the QSqlRelationalTableModel and QSqlRelationalDelegate.

              My attempt was to manually select the right table in function of the eventType value and add the id and the name of the rows to the QComboBox (as text and user-data). And this is done and working in the createEditor() function.

              When the user open the editor I select the correct text finding out the current user-data (that is the id stored in the database as foreign key in the protocols table) and also this seems to work.

              My issue is when the user close the editor and I need to write to the model the new value: the Qt::EditRole should be the id of the related row (and this is ok) but since I would show in the QTableView the name of the linked row (exactly as the standard lookup would do) my approach was to set this string to the Qt::DisplayRole hoping it will be painted to the cell. But I cannot set the Qt::DisplayRole to the model: the setData() fails.

              If you might suggest another approach (that does not require to change the db structure, since its not mine) I will be glad to try it.

              Christian EhrlicherC Offline
              Christian EhrlicherC Offline
              Christian Ehrlicher
              Lifetime Qt Champion
              wrote on last edited by
              #6

              @Mark81 said in How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole:

              But I cannot set the Qt::DisplayRole to the model: the setData() fails.

              How should those work? The model is not made for this as already explained. You have to implement this by yourself if you want and really need it (which I doubt).

              Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
              Visit the Qt Academy at https://academy.qt.io/catalog

              M 1 Reply Last reply
              0
              • Christian EhrlicherC Christian Ehrlicher

                @Mark81 said in How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole:

                But I cannot set the Qt::DisplayRole to the model: the setData() fails.

                How should those work? The model is not made for this as already explained. You have to implement this by yourself if you want and really need it (which I doubt).

                M Offline
                M Offline
                Mark81
                wrote on last edited by
                #7

                @Christian-Ehrlicher ok, I didn't find an explanation in the docs why I cannot set the Qt::DisplayRole, but here I learnt I can't. So, can you kindly propose another approach to reach my goal please?

                1 Reply Last reply
                0
                • M Mark81

                  @JonB thanks for your effort and patience, surely my English is not helping here.
                  My post was quite long because I provided a MRE, my attempts and my researches.

                  Anyway I try to explain it in another way. Please, feel free to ask what is not clear.

                  In table protocols there is a column (idEvent) that is the foreign key towards another table (say alarms). If it was so simple, the standard QSqlRelationalTableModel behavior was just fine.

                  My problem is the protocols table has another column (eventType) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into the alarms table, if the value is 1 it must lookup into the sessions table and so on.

                  Of course this very specific (and awkward...) behavior is not provided by the QSqlRelationalTableModel and QSqlRelationalDelegate.

                  My attempt was to manually select the right table in function of the eventType value and add the id and the name of the rows to the QComboBox (as text and user-data). And this is done and working in the createEditor() function.

                  When the user open the editor I select the correct text finding out the current user-data (that is the id stored in the database as foreign key in the protocols table) and also this seems to work.

                  My issue is when the user close the editor and I need to write to the model the new value: the Qt::EditRole should be the id of the related row (and this is ok) but since I would show in the QTableView the name of the linked row (exactly as the standard lookup would do) my approach was to set this string to the Qt::DisplayRole hoping it will be painted to the cell. But I cannot set the Qt::DisplayRole to the model: the setData() fails.

                  If you might suggest another approach (that does not require to change the db structure, since its not mine) I will be glad to try it.

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by
                  #8

                  @Mark81 said in How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole:

                  My problem is the protocols table has another column (eventType) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into the alarms table, if the value is 1 it must lookup into the sessions table and so on.

                  I would just confirm that this means you cannot use what SQL offers for a "foreign key", and hence should mean that a QSqlRelationalTableModel is not applicable here and presumably should not be used. I think you would need to implement what you want from this entirely in your own code. And may well be problematic.

                  M 1 Reply Last reply
                  0
                  • JonBJ JonB

                    @Mark81 said in How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole:

                    My problem is the protocols table has another column (eventType) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into the alarms table, if the value is 1 it must lookup into the sessions table and so on.

                    I would just confirm that this means you cannot use what SQL offers for a "foreign key", and hence should mean that a QSqlRelationalTableModel is not applicable here and presumably should not be used. I think you would need to implement what you want from this entirely in your own code. And may well be problematic.

                    M Offline
                    M Offline
                    Mark81
                    wrote on last edited by
                    #9

                    @JonB oh, I didn't think it would be so difficult!
                    If it is so hard, what else could I do?

                    JonBJ 1 Reply Last reply
                    0
                    • M Mark81

                      @JonB oh, I didn't think it would be so difficult!
                      If it is so hard, what else could I do?

                      JonBJ Offline
                      JonBJ Offline
                      JonB
                      wrote on last edited by
                      #10

                      @Mark81
                      It is "difficult" because you do not have a proper foreign key relation, which is what QSqlRelationalTableModel requires. Having some other column (eventType) whose value dictates which of multiple tables (alarms, sessions, ...) is the foreign one in which to look up the key column's value is supported neither in SQL nor Qt's QSqlRelationalTableModel --- it is not allowed in the definition of a foreign key.

                      Either think about redefining/rearchitecting your intended relationships which fits better with SQL or (in my opinion) give up trying to use QSqlRelationalTableModel. Instead read in the contents of all required/linked tables into their own normal QSqlTableModels, as well as the original "source" table, and implement your desired logic yourself in client code as required for your desired behaviour.

                      1 Reply Last reply
                      1

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved