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. QStyledItemDelegate inside a QTableView?
QtWS25 Last Chance

QStyledItemDelegate inside a QTableView?

Scheduled Pinned Locked Moved Unsolved General and Desktop
20 Posts 4 Posters 2.7k 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.
  • mzimmersM Offline
    mzimmersM Offline
    mzimmers
    wrote on last edited by mzimmers
    #1

    Hi all -

    I'm using a QTableView to display the contents of a model. I need each entry in the table to be able to display a QWidget (I have to paint these widgets to update them). Is there a straightforward way to do this, or am I using the wrong approach?

    Thanks...

    JonBJ 1 Reply Last reply
    0
    • mzimmersM mzimmers

      Hi all -

      I'm using a QTableView to display the contents of a model. I need each entry in the table to be able to display a QWidget (I have to paint these widgets to update them). Is there a straightforward way to do this, or am I using the wrong approach?

      Thanks...

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

      @mzimmers
      Normally you are supposed to use a QStyledItemDelegate to "draw" what you want, as using QWidgets in QTableViews is resource-intensive and to be avoided.
      If you do want a QWidget there I would look to see how it is done in QTableWidget::setCellWidget().

      mzimmersM 1 Reply Last reply
      3
      • JonBJ JonB

        @mzimmers
        Normally you are supposed to use a QStyledItemDelegate to "draw" what you want, as using QWidgets in QTableViews is resource-intensive and to be avoided.
        If you do want a QWidget there I would look to see how it is done in QTableWidget::setCellWidget().

        mzimmersM Offline
        mzimmersM Offline
        mzimmers
        wrote on last edited by mzimmers
        #3

        @JonB thanks. I'm going to re-title this thread accordingly, as it seems that QStyledItemDelegate is what I want. A couple questions:

        • from the doc: "QStyledItemDelegate is the default delegate for all Qt item views, and is installed upon them when they are created." Does this mean that my QTableView already has an actual QStyledItemDelegate object, or do I still need to create one?
        • do I then use QStyledItemDelegate::paint() to do my painting? If so, how do I direct the delegate to a particular row/column in the view?
        • should I be using QTableWidget instead of QTableView for this application?

        Thanks...

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          You can set a delegate for one column or one row if you want.

          Otherwise, the usual, check the index and do your custom drawing if it matches the conditions.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          1
          • mzimmersM Offline
            mzimmersM Offline
            mzimmers
            wrote on last edited by mzimmers
            #5

            So, here's the signature of my delegate's paint() method:

            void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                                     const QModelIndex &index) const
            
            1. how do I derive my model from the index? The model will dictate what I draw.
            2. can I position my paint object (in this case, a rectangle) within a cell in the view? Currently, it shows up like this:
              timezone.PNG
              I want it in the Widget column. How do I designate which row/column the paint takes place?

            Thanks...

            EDIT: to make these questions a bit more concrete, let's say I wanted to check a different column in my current row. If the value in that column is odd, I'd make my rectangle green; if even, red.

            JonBJ 1 Reply Last reply
            0
            • mzimmersM mzimmers

              So, here's the signature of my delegate's paint() method:

              void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                                       const QModelIndex &index) const
              
              1. how do I derive my model from the index? The model will dictate what I draw.
              2. can I position my paint object (in this case, a rectangle) within a cell in the view? Currently, it shows up like this:
                timezone.PNG
                I want it in the Widget column. How do I designate which row/column the paint takes place?

              Thanks...

              EDIT: to make these questions a bit more concrete, let's say I wanted to check a different column in my current row. If the value in that column is odd, I'd make my rectangle green; if even, red.

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

              @mzimmers said in QStyledItemDelegate inside a QTableView?:

              how do I derive my model from the index? The model will dictate what I draw.

              What do you mean by this? index is an index into the model being drawn. const QAbstractItemModel *QModelIndex::model() const gives you the model, i.e. from index.model() in the paint() event. And index.data() gives you the data value from the model.

              This paint() method is called for every cell to draw, one at a time, as necessary. You look at index.row/column() if that is relevant to your painting. If you want to do something special in certain rows/columns use an if/switch statement. Just call the base class paint() for the default painting. So you do not "position the object into/designate which row/column the paint takes place", you wait till it's called with index.column() == 2 for your Widget column.

              If you used QAbstractItemView::setItemDelegate(QAbstractItemDelegate *delegate) to set your delegate on the tableview it is set/will be called for every cell drawn. However, as a convenience to save you having to use an if/switch for the Widget column, you could instead use QAbstractItemView::setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate) to set a delegate for an individual column.

              1 Reply Last reply
              3
              • mzimmersM Offline
                mzimmersM Offline
                mzimmers
                wrote on last edited by
                #7

                So, I guess the correct way to get a new index is something like this:

                void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
                {
                    QModelIndex qmi;
                    if (index.column() == COLUMN_WIDGET) {
                        qmi = index.model()->index(index.row(), COLUMN_CURRENTTIME);
                        QString qs = qmi.data().toString();
                        etc...
                

                And then process qs as necessary to influence how I do my painting.

                And, thanks for the reference to setItemDelegateForColumn(). That is indeed very convenient.

                JonBJ Christian EhrlicherC 2 Replies Last reply
                0
                • mzimmersM mzimmers

                  So, I guess the correct way to get a new index is something like this:

                  void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
                  {
                      QModelIndex qmi;
                      if (index.column() == COLUMN_WIDGET) {
                          qmi = index.model()->index(index.row(), COLUMN_CURRENTTIME);
                          QString qs = qmi.data().toString();
                          etc...
                  

                  And then process qs as necessary to influence how I do my painting.

                  And, thanks for the reference to setItemDelegateForColumn(). That is indeed very convenient.

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

                  @mzimmers said in QStyledItemDelegate inside a QTableView?:

                  index.model()->index(index.row(), COLUMN_CURRENTTIME);

                  That can be written as index.siblingAtColumn(COLUMN_CURRENTTIME).

                  1 Reply Last reply
                  2
                  • mzimmersM mzimmers

                    So, I guess the correct way to get a new index is something like this:

                    void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
                    {
                        QModelIndex qmi;
                        if (index.column() == COLUMN_WIDGET) {
                            qmi = index.model()->index(index.row(), COLUMN_CURRENTTIME);
                            QString qs = qmi.data().toString();
                            etc...
                    

                    And then process qs as necessary to influence how I do my painting.

                    And, thanks for the reference to setItemDelegateForColumn(). That is indeed very convenient.

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

                    @mzimmers said in QStyledItemDelegate inside a QTableView?:

                    And then process qs as necessary to influence how I do my painting.

                    why? You already have the correct index - it's the one which is passed to your paint function... why would you paint something from another cell in the one you have to paint?

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

                    mzimmersM 1 Reply Last reply
                    0
                    • Christian EhrlicherC Christian Ehrlicher

                      @mzimmers said in QStyledItemDelegate inside a QTableView?:

                      And then process qs as necessary to influence how I do my painting.

                      why? You already have the correct index - it's the one which is passed to your paint function... why would you paint something from another cell in the one you have to paint?

                      mzimmersM Offline
                      mzimmersM Offline
                      mzimmers
                      wrote on last edited by mzimmers
                      #10

                      @Christian-Ehrlicher I need the contents of another cell in order to know what to paint. The other cell in question contains a QString with digits; I need to know those digits in order to know what to paint. (I'm sort of doing a manual implementation of the Qt LCD class.)

                      EDIT: this brings me to another issue, though -- the delegate can access the data from within its paint() method, but there doesn't appear to be a way to pass that to the object that actually does the painting. I'm using the StarDelegate example, and the delegate looks like this:

                      void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                                               const QModelIndex &index) const
                      {
                          QModelIndex qmi;// = new QModelIndex(m_model->index(index.row(), COLUMN_CURRENTTIME));
                      
                          if (index.column() == COLUMN_WIDGET) {
                              // qmi = index.model()->index(index.row(), COLUMN_CURRENTTIME);
                              qmi = index.siblingAtColumn(COLUMN_CURRENTTIME);
                              
                              QString qs = qmi.data().toString();
                      
                              StarRating starRating = qvariant_cast<StarRating>(index.data());
                              starRating.paint(painter, option.rect, option.palette,
                                               StarRating::EditMode::ReadOnly);
                      

                      My starRating object needs to know the data in order to paint. I don't know how to pass that information to that object. Or...am I doing this wrong?

                      EDIT 2: I see that I can modify the signature of my starRating to include the value I want to paint, so disregard my last question.

                      JonBJ 1 Reply Last reply
                      0
                      • mzimmersM mzimmers

                        @Christian-Ehrlicher I need the contents of another cell in order to know what to paint. The other cell in question contains a QString with digits; I need to know those digits in order to know what to paint. (I'm sort of doing a manual implementation of the Qt LCD class.)

                        EDIT: this brings me to another issue, though -- the delegate can access the data from within its paint() method, but there doesn't appear to be a way to pass that to the object that actually does the painting. I'm using the StarDelegate example, and the delegate looks like this:

                        void TimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                                                 const QModelIndex &index) const
                        {
                            QModelIndex qmi;// = new QModelIndex(m_model->index(index.row(), COLUMN_CURRENTTIME));
                        
                            if (index.column() == COLUMN_WIDGET) {
                                // qmi = index.model()->index(index.row(), COLUMN_CURRENTTIME);
                                qmi = index.siblingAtColumn(COLUMN_CURRENTTIME);
                                
                                QString qs = qmi.data().toString();
                        
                                StarRating starRating = qvariant_cast<StarRating>(index.data());
                                starRating.paint(painter, option.rect, option.palette,
                                                 StarRating::EditMode::ReadOnly);
                        

                        My starRating object needs to know the data in order to paint. I don't know how to pass that information to that object. Or...am I doing this wrong?

                        EDIT 2: I see that I can modify the signature of my starRating to include the value I want to paint, so disregard my last question.

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

                        @mzimmers
                        A different way of abstracting sounds like doing the work/mapping for the Widgets column in a QAbstractProxyModel instead of in the delegate. A QIdentityProxyModel with an extra Widgets column. The delegate then just draws the rectangle or whatever without doing the logic of calculating anything. Just a possibility.

                        1 Reply Last reply
                        0
                        • mzimmersM Offline
                          mzimmersM Offline
                          mzimmers
                          wrote on last edited by
                          #12

                          Thanks to everyone's help, I think I'm very close to having this functional. I'm able to paint the data in the manner I want, and it displays correctly.

                          One remaining issue: when the data changes, the display doesn't automatically repaint. (It does repaint if I defocus, or resize or much of anything else, just not on its own.) What is the recommended way of getting this to update? I do have a one second timer in my worker thread that updates the model and signals the display thread, but that doesn't provoke a repaint (I guess because I have the delegate).

                          Thanks...

                          JonBJ 1 Reply Last reply
                          0
                          • mzimmersM mzimmers

                            Thanks to everyone's help, I think I'm very close to having this functional. I'm able to paint the data in the manner I want, and it displays correctly.

                            One remaining issue: when the data changes, the display doesn't automatically repaint. (It does repaint if I defocus, or resize or much of anything else, just not on its own.) What is the recommended way of getting this to update? I do have a one second timer in my worker thread that updates the model and signals the display thread, but that doesn't provoke a repaint (I guess because I have the delegate).

                            Thanks...

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

                            @mzimmers
                            The fact that you have your own delegate should not be at issue here. The fact that it does repaint when resized (calls for a complete redraw) but not when data changes indicates the right dataChanged() signal is not being emitted. Put in qDebug() statements to see when that does/does not occur. If you are still computing one column's value/need to redraw from another column's value change, you would need the dataChanged() signal parameters to include both columns, or multiple signals.

                            mzimmersM 1 Reply Last reply
                            1
                            • JonBJ JonB

                              @mzimmers
                              The fact that you have your own delegate should not be at issue here. The fact that it does repaint when resized (calls for a complete redraw) but not when data changes indicates the right dataChanged() signal is not being emitted. Put in qDebug() statements to see when that does/does not occur. If you are still computing one column's value/need to redraw from another column's value change, you would need the dataChanged() signal parameters to include both columns, or multiple signals.

                              mzimmersM Offline
                              mzimmersM Offline
                              mzimmers
                              wrote on last edited by
                              #14

                              @JonB I have this connection in my Widget:

                                  QObject::connect(m_model, &QStandardItemModel::dataChanged, this, &Widget::processChange);
                              void Widget::processChange(QModelIndex qmi1, QModelIndex qmi2) {
                                  Q_UNUSED(qmi1)
                                  Q_UNUSED(qmi2)
                                  ui->tableView->repaint();
                              }
                              

                              The slot is called, but the repaint() doesn't seem to do anything until I click on the Widget.

                              Could this somehow be related to the use of an incorrect model class? I'm using QStandardItemView, but my display widget is a QTableView.

                              Thanks...

                              1 Reply Last reply
                              0
                              • Christian EhrlicherC Offline
                                Christian EhrlicherC Offline
                                Christian Ehrlicher
                                Lifetime Qt Champion
                                wrote on last edited by
                                #15

                                When a model changes you don't have to call update on the view - this is done automatically when the dataChanged() emits with the correct indexes.

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

                                mzimmersM 1 Reply Last reply
                                1
                                • Christian EhrlicherC Christian Ehrlicher

                                  When a model changes you don't have to call update on the view - this is done automatically when the dataChanged() emits with the correct indexes.

                                  mzimmersM Offline
                                  mzimmersM Offline
                                  mzimmers
                                  wrote on last edited by
                                  #16

                                  @Christian-Ehrlicher well then, I'm doing something wrong. My model has the following columns:

                                  enum MODEL_COLUMNS {
                                      COLUMN_LABEL,
                                      COLUMN_OFFSET,
                                      COLUMN_TEXT_TIME,
                                      COLUMN_PAINT_TIME,
                                      NBR_COLS
                                  } ;
                                  

                                  In my Widget:

                                      ui->tableView->setModel(model);
                                      timeDelegate = new TimeDelegate();
                                      ui->tableView->setItemDelegateForColumn(COLUMN_PAINT_TIME, timeDelegate);
                                  

                                  The idea is that when something in column COLUMN_TEXT_TIME changes, I want to alter the corresponding entry in COLUMN_PAINT_TIME (in the view). I'm getting the signal from the model, and I do repaint, but the repaint only becomes visible when I click on the window (or try to resize it, or try to move it). These actions also cause the signal to be sent again.

                                  Am I missing something here?

                                  Thanks...

                                  Christian EhrlicherC 1 Reply Last reply
                                  0
                                  • mzimmersM mzimmers

                                    @Christian-Ehrlicher well then, I'm doing something wrong. My model has the following columns:

                                    enum MODEL_COLUMNS {
                                        COLUMN_LABEL,
                                        COLUMN_OFFSET,
                                        COLUMN_TEXT_TIME,
                                        COLUMN_PAINT_TIME,
                                        NBR_COLS
                                    } ;
                                    

                                    In my Widget:

                                        ui->tableView->setModel(model);
                                        timeDelegate = new TimeDelegate();
                                        ui->tableView->setItemDelegateForColumn(COLUMN_PAINT_TIME, timeDelegate);
                                    

                                    The idea is that when something in column COLUMN_TEXT_TIME changes, I want to alter the corresponding entry in COLUMN_PAINT_TIME (in the view). I'm getting the signal from the model, and I do repaint, but the repaint only becomes visible when I click on the window (or try to resize it, or try to move it). These actions also cause the signal to be sent again.

                                    Am I missing something here?

                                    Thanks...

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

                                    @mzimmers said in QStyledItemDelegate inside a QTableView?:

                                    Am I missing something here?

                                    As I said - you may be calling dataChanged() with wrong indexes or roles but you don't show us the code. Did you disabled updates for your widgets somehow?
                                    Also you should rather use update() instead repaint()

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

                                    mzimmersM 1 Reply Last reply
                                    0
                                    • Christian EhrlicherC Christian Ehrlicher

                                      @mzimmers said in QStyledItemDelegate inside a QTableView?:

                                      Am I missing something here?

                                      As I said - you may be calling dataChanged() with wrong indexes or roles but you don't show us the code. Did you disabled updates for your widgets somehow?
                                      Also you should rather use update() instead repaint()

                                      mzimmersM Offline
                                      mzimmersM Offline
                                      mzimmers
                                      wrote on last edited by mzimmers
                                      #18

                                      @Christian-Ehrlicher I don't understand why columns or roles play a factor here. The dataChanged() signal is getting delivered; I've verified that in the debugger. And when it does, I unconditionally call:

                                      ui->tableView->repaint();
                                      

                                      (I'll change that to update().)

                                      Shouldn't this be sufficient without regard to roles or columns?

                                      I thought I'd posted all the relevant code; what else would be helpful?

                                      Thanks...

                                      EDIT: I verified that updates are indeed enabled on the widget.

                                      Christian EhrlicherC 1 Reply Last reply
                                      0
                                      • mzimmersM mzimmers

                                        @Christian-Ehrlicher I don't understand why columns or roles play a factor here. The dataChanged() signal is getting delivered; I've verified that in the debugger. And when it does, I unconditionally call:

                                        ui->tableView->repaint();
                                        

                                        (I'll change that to update().)

                                        Shouldn't this be sufficient without regard to roles or columns?

                                        I thought I'd posted all the relevant code; what else would be helpful?

                                        Thanks...

                                        EDIT: I verified that updates are indeed enabled on the widget.

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

                                        @mzimmers said in QStyledItemDelegate inside a QTableView?:

                                        The dataChanged() signal is getting delivered

                                        When you use the wrong indexes it will not help. But do what you want.

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

                                        mzimmersM 1 Reply Last reply
                                        0
                                        • Christian EhrlicherC Christian Ehrlicher

                                          @mzimmers said in QStyledItemDelegate inside a QTableView?:

                                          The dataChanged() signal is getting delivered

                                          When you use the wrong indexes it will not help. But do what you want.

                                          mzimmersM Offline
                                          mzimmersM Offline
                                          mzimmers
                                          wrote on last edited by mzimmers
                                          #20

                                          @Christian-Ehrlicher I'm sorry, but I'm just not understanding something here. If I get a signal from the model, and respond to that signal by repainting/updating/whatever my QTableView, is that not sufficient to trigger my delegate's paint() event (which will update the COLUMN_PAINT_TIME column)?

                                          Thanks....

                                          EDIT: well, what @Christian-Ehrlicher said was 100% right, but...I still don't understand the mechanism. I added a line in my worker thread to update the COLUMN_PAINT_TIME in the model, and now it seems to be working. (The date of model update in this case seems to be irrelevant, as the display is changed by the delegate.)

                                          What I don't understand is, if the COLUMN_PAINT_TIME in the view is under control of a delegate, why does the model have to be updated? If my widget slot is calling repaint/update/whatever unconditionally, why isn't that sufficient irrespective of the arguments in the signal/slot mechanism?

                                          1 Reply Last reply
                                          0

                                          • Login

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