Qt Weekly #5: Widgets on a QHeaderView

Published Friday April 11th, 2014 | by

Just recently someone had asked about having QComboBoxes placed over the sections inside a QHeaderView, initially I thought this would be fairly straightforward as you could use the setViewportMargins() and put the widgets at the top.  But unfortunately this is not possible to do because the itemviews set the viewport margins itself and setting it outside of this will cause problems so that approach is not recommended.

Therefore the way to get it working is to actually create the widgets and place them on the QHeaderView directly, this means that we have to adjust them manually when sections are resized, moved or when the itemview itself is scrolled.  So in order to do this we need to start off by subclassing QHeaderView.  In this case I am focusing purely on a horizontal header as this makes it more straight forward to focus on instead of trying to account for the direction sections are moving.

MyHorizontalHeader(QWidget *parent = 0) : QHeaderView(Qt::Horizontal, parent)
{
     connect(this, SIGNAL(sectionResized(int, int, int)), this, 
             SLOT(handleSectionResized(int)));
     connect(this, SIGNAL(sectionMoved(int, int, int)), this, 
             SLOT(handleSectionMoved(int, int, int)));
     setMovable(true);
}

We have added two slots already here, one is for handling when a section is resized and one for when a section is moved.  We will get to them in a bit, but first we will cover the initialization of the widgets.  We cannot do this in the constructor (although we could do if we know more about the itemview it will be associated with, but for this case I want to be as generic as possible) as we don’t know how many sections there will be.  So the best place to do this in a reimplemented showEvent():

void showEvent(QShowEvent *e)
{
    for (int i=0;i<count();i++) {
       if (!boxes[i]) {
          QComboBox *box = new QComboBox(this);
          boxes[i] = box;
       }
       boxes[i]->setGeometry(sectionViewportPosition(i), 0, 
                                sectionSize(i) - 5, height());
       boxes[i]->show();
    }
    QHeaderView::showEvent(e);
 }

The boxes array is just a QMap<int, QComboBox *> with the integer referring to the logical index of the section the combobox is connected to.  In order to get the correct position on the viewport for the section we use sectionViewportPosition() and sectionSize() is used to get the size of the section.  The reason the -5 is there is purely so that I can see a bit of the section underneath to allow moving of the sections.  If you only want to see the splitter to resize the sections you can change it to -2.

What is left then is to react to the resize of the sections and the moving of the sections with the following slots:

void handleSectionResized(int i)
{
    for (int j=visualIndex(i);j<count();j++) {
        int logical = logicalIndex(j);
        boxes[logical]->setGeometry(sectionViewportPosition(logical), 0, 
                                       sectionSize(logical) - 5, height());
    }
}
void handleSectionMoved(int logical, int oldVisualIndex, int newVisualIndex)
{
    for (int i=qMin(oldVisualIndex, newVisualIndex);i<count();i++){
        int logical = logicalIndex(i);
        boxes[logical]->setGeometry(sectionViewportPosition(logical), 0, 
                                       sectionSize(logical) - 5, height());
    }
}

The code is fundamentally the same in both slots but the loop is tweaked to limit how many widgets have to be touched, when a section is resized we only go from the visual index of the one resized throughout the rest of the sections after that.  When a section is moved we need to go from where the first visual index is hit to the end of the headerview as the other sections will have to be updated to adjust.

So far so good, the only thing left for us to do now is handle the case where the itemview is scrolled as we need to reposition the widgets again.  Since the QHeaderView is not being notified in any way that this occurs as it is on the viewport we need to connect this up from the itemview itself when a scroll occurs.  To do this we need to reimplement scrollContentsBy():

void scrollContentsBy(int dx, int dy)
{
   QTableWidget::scrollContentsBy(dx, dy);
   if (dx != 0)
      horizHeader->fixComboPositions();
}

What is not shown here is the constructor which creates an instance of the MyHorizontalHeader class and sets it with setHorizontalHeader().  So whenever the contents are scrolled in either horizontal direction we call fixComboPositions() which does:

void fixComboPositions()
{
    for (int i=0;i<count();i++)
        boxes[i]->setGeometry(sectionViewportPosition(i), 0, 
                                 sectionSize(i) - 5, height());
}

In effect that is the same as what showEvent() does, since we don’t know how much was made visible or hidden the easiest way is to just go over all the widgets.

And there you have it, a fairly straightforward approach to getting widgets on a QHeaderView on a per section basis which should be easily adaptable for your own needs.

Qt Weekly is a new blog post series that aims to give your daily Qt usage a boost. Stay tuned for next week’s post!

Did you like this? Share it:

Posted in Uncategorized

7 comments to Qt Weekly #5: Widgets on a QHeaderView

DavidB says:

Thanks for the post. HeaderView could use a little more love. I’d like to have an option that allows you to write header text at an angle (45 degrees for example). This would allow one to have long column names without wasting space for integer values of only two or three digits. Sure one could do it them self but it is a royal pain.

Hope Widgets start getting some love some time soon. After all, it really is how Digia butters their bread.

Thanks

Maria says:

very nice but please post a screenshot

Juha says:

Why not also a complete example with source code, or am I just being lazy :)?

Pablo says:

Yes, a picture is worth a thousand words…

dj says:

You can see a screenshot here:

Tuukka Turunen says:

@dj: the link is: http://imgur.com/mpJTGbv

Commenting closed.