Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Programmatically force redraw of QHeaderView



  • Hi there.

    I'm writing a generalized version of the Frozen Column example. While I've gotten quite far, there is one particular problem for which a solution eludes me.

    A "frozen" view basically consists of two independent (table) views stacked on top of each other. This includes header views. The problem is that if the header view resizes in the other direction than its orientation (e.g. the width of vertical or the height of horizontal header view) there is no (official) way of catching that.

    The reason I need to catch it is to force the "frozen" header view to have the same size as the "non-frozen" in the direction that isn't frozen. For example: I have a vertical header view which as item text contains a row number. As that number gets bigger (e.g. from 9 to 10 or from 99 to 100) the non-frozen header view will automaticallly resize itself (get wider). The frozen header view won't, because there's not event or signal triggered. At least none that I've been able to identify.

    Now, I've (sort of) managed to crack the issue of detecting the resize on the non-frozen header view (abusing the virtual QHeaderView::sectionSizeFromContents() method), but now I can't find a way to tell the frozen header view about it.

    I've tried pretty much everything I can think of: update(), updateGeometry(), updateGeometries() on both the widget and the viewport, where applicable. Nothing seems to work.

    I'd be truly happy for any idea or suggestion.


  • Qt Champions 2019

    I'm not sure if I'm on the right track but doesn't QHeaderView::setSectionSize() or QHeaderView::resize() help here?



  • @Christian-Ehrlicher Thanks for your input, but I need the orthogonal size. The section size only reflects the size for the orientation of the header view, i.e. the width for horizontal header views and the height for vertical header views. Seems the Qt developers didn't think of a use case for the orthogonal sizes.


  • Qt Champions 2019

    The you should try setMiniumWidth/Height.



  • @Christian-Ehrlicher Sadly, that won't help me either. I need to react to the changes to the underlying non-frozen header view, so they both stay in sync.


  • Qt Champions 2019

    So why can't you modify the size of the other one with setMin/MaxWidth/Height?



  • Because it has no effect whatsoever.


  • Qt Champions 2019

    The please provide a minmal example. For me this sounds like https://bugreports.qt.io/browse/QTBUG-34095 which is fixed in 5.12



  • @Christian-Ehrlicher It seems you're right about that bug. It does look very similar to my issue. However, even if it's fixed in 5.12 that would be of no use to me. We're currently on 5.4.1 (commercial) and planning to move to 5.9.7 when it gets available. At the very least this fix should be backported to the 5.9 branch (long term support and all that), preferably 5.9.7. Although I could probably reimplement QHeaderView::resizeEvent() since I'm subclassing anyway, as stated below.

    I wasn't aware you're working on Qt (by the way, Thorbjørn is a former colleague of mine and (hopefully still :-) a friend).

    As for the minimal example, sadly I cannot provide one. The code in question is several thousand lines long. What I can do is post my subclassed version of QHeaderView, FrozenHeaderView. It should be possible to plug this in to the Frozen Column example mentioned initially.

    The FrozenHeaderView class works on two levels. At the base level it's just a wrapper on the normal QHeaderView with added code to try and catch resizing in "the other direction" (i.e. width for vertical headers and height for horizontal headers). That's the one without a "base header". As you can see, I've tried hooking into both sectionSizeFromContents() and paintSection() to get the size change. Both work equally well; that is, the respective signals/slots are emitted/called, but applying the changed width/height in updateSize() to the "frozen" header yields no result, and that seems to be my main issue. I can see that the updatedGeometries() signal is more elegant, but alas, it does not work. And even if, I still can't get the changed size applied.

    The other level is the "frozen" header. Its sizeHint () method calls the base header's sizeHint() and adjusts the applicable width/height. That bit seems to work OK, but apparently sizeHint() isn't called often. At least not in a way that is helpful to my current problem.

    Declaration:

    class FrozenHeaderView : public QHeaderView
    {
      Q_OBJECT
    
    public:
      explicit FrozenHeaderView(Qt::Orientation orientation, QWidget *parent = nullptr);
      explicit FrozenHeaderView(QHeaderView *baseheader, QWidget *parent = nullptr);
      virtual  ~FrozenHeaderView();
    
      void setBaseHeader(QHeaderView *baseheader);
      QHeaderView* baseHeader() const;
    
      virtual QSize sizeHint() const override;
      virtual QSize minimumSizeHint() const override;
    
    Q_SIGNALS:
      void frozenHeaderResized(int section, int size);
      void sizeHintChanged(QSize) const;
      void frozenSizeHintChanged(QSize) const;
      void sizeChanged(int) const;
      void sizeUpdated();
    
    protected Q_SLOTS:
      void headerResized(int logicalIndex, int oldSize, int newSize);
      void updateSize(int);
    
    protected:
      //virtual QSize sectionSizeFromContents(int logicalIndex) const override;
      virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
    
      FrozenHeaderView *m_baseheader;
      mutable QSize m_previoussize;
      mutable QSize m_cachedsizehint;
      mutable int   m_basecachedsize;
      bool m_frozen;
    };
    
    

    Implementation:

    FrozenHeaderView::FrozenHeaderView(Qt::Orientation orientation, QWidget *parent)
    : QHeaderView(orientation, parent), m_baseheader(nullptr), m_frozen(false), m_basecachedsize(0)
    {
      connect(this, &QHeaderView::sectionResized, this, &FrozenHeaderView::headerResized);
      m_cachedsizehint = QHeaderView::sizeHint();
    }
    
    FrozenHeaderView::FrozenHeaderView(QHeaderView *baseheader, QWidget *parent)
    : QHeaderView(baseheader->orientation(), parent), m_baseheader(nullptr), m_frozen(true), m_basecachedsize(0)
    {
      setBaseHeader(baseheader);
      connect(this, &QHeaderView::sectionResized, this, &FrozenHeaderView::headerResized);
      m_cachedsizehint = QHeaderView::sizeHint();
    }
    
    FrozenHeaderView::~FrozenHeaderView()
    {
      disconnect(this, &QHeaderView::sectionResized, this, &FrozenHeaderView::headerResized);
      if (m_baseheader)
        disconnect(m_baseheader, &FrozenHeaderView::sizeChanged, this, &FrozenHeaderView::updateSize);
    }
    
    void FrozenHeaderView::setBaseHeader(QHeaderView *baseheader)
    {
      if (baseheader == m_baseheader)
        return;
    
      if (m_baseheader)
        disconnect(m_baseheader, &FrozenHeaderView::sizeChanged, this, &FrozenHeaderView::updateSize);
    
      m_baseheader = qobject_cast<FrozenHeaderView*>(baseheader);
    
      if (m_baseheader)
        connect(m_baseheader, &FrozenHeaderView::sizeChanged, this, &FrozenHeaderView::updateSize, Qt::QueuedConnection);
    }
    
    QHeaderView* FrozenHeaderView::baseHeader() const
    {
      return m_baseheader;
    }
    
    QSize FrozenHeaderView::sizeHint() const
    {
      QSize frozenSize = QHeaderView::sizeHint();
    
      if (m_baseheader)
      {
        QSize baseSize = m_baseheader->sizeHint();
    
        if (orientation() == Qt::Horizontal)
          frozenSize.setHeight(baseSize.height());
        else
          frozenSize.setWidth(baseSize.width());
    
        if (frozenSize != m_previoussize)
        {
          m_previoussize = frozenSize;
          emit sizeHintChanged(frozenSize);
        }
      }
    
      return frozenSize;
    }
    
    QSize FrozenHeaderView::minimumSizeHint() const
    {
      return sizeHint();
    }
    
    void FrozenHeaderView::headerResized(int logicalIndex, int oldSize, int newSize)
    {
      emit frozenHeaderResized(logicalIndex, newSize);
    }
    
    void FrozenHeaderView::updateSize(int size)
    {
      if (m_baseheader)
      {
        int w = width();
        QRect rect = geometry();
        if (orientation() == Qt::Horizontal)
          rect.setHeight(size);
        else
          rect.setWidth(size);
        setGeometry(rect);
        setMinimumSize(rect.size());
        setMaximumSize(rect.size());
        setAttribute(Qt::WA_Resized);
        emit sizeUpdated();
      }
    }
    
    //QSize FrozenHeaderView::sectionSizeFromContents(int logicalIndex) const
    //{
    //  QSize s = QHeaderView::sectionSizeFromContents(logicalIndex);
    //  if (m_baseheader == nullptr) // this is the base header
    //  {
    //    int newsize = orientation() == Qt::Horizontal ? s.height() : s.width();
    //
    //    if (newsize != m_basecachedsize)
    //    {
    //      m_basecachedsize = newsize;
    //      emit sizeChanged(newsize);
    //    }
    //  }
    //  
    //  return s;
    //}
    
    void FrozenHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
    {
      QHeaderView::paintSection(painter, rect, logicalIndex);
    
      if (m_baseheader == nullptr) // this is the base header
      {
        int newsize = orientation() == Qt::Horizontal ? rect.height() : rect.width();
    
        if (newsize != m_basecachedsize)
        {
          m_basecachedsize = newsize;
          emit sizeChanged(newsize);
        }
      }
    }
    


  • Hmm... Reimplementing resizeEvent() is a much cleaner solution than (ab)using either sectionSizeFromContents() or paintSection(). I'm not sure why I did not go this route earlier. However, I still cannot get the new size applied to the frozen header... :S


  • Qt Champions 2019

    I would try to mimic the behavior of the patch by catching QEvent::Resize and then doing something so d->invalidateCachedSizeHint(); is callled. You could call this directly but then you need to use private headers which should be avoided... maybe by calling dataChanged() with invalid indexes (it calls invalidateCachedSizeHint() without checking the indexes).



  • Dumb question: Should I call dataChanged() on the frozen header or on the view that contains the frozen header? I'm asking because the former doesn't seem to work (as everything else I've tried so far).


  • Qt Champions 2019

    On the headerView which you wanted to resize. Maybe you should first play around with the testcase in the bugreport to find a solution there - so we have a common base and a minimal testcase.



  • Sorry for taking so long to update this. I had to do some changes to the code from the bug, so it would work with Qt 5.4.1. Also, I needed to change it so I could add one line at a time to the view. Well, and regular work. ;-)

    I wanted to upload the code, but apparently I do not have enough privileges.

    Well, to make it short, everything works in the test case. Now I just need to figure out why that is and why my (substantially larger) version doesn't. I'll keep this thread updated with my findings.


  • Qt Champions 2019

    At least good to hear that the testcase works :)
    Uploading to the bug is only possible for bug-owners. You have to upload it here in the forum.



  • That's what I meant. I'm not allowed to upload the code here.

    While I'm at it, I've made some progress. Essentially it boils down to me having done too much instead of too little. What I needed to do was simply to emit a signal in the frozen header resize event and then in the main view call updateFrozenTableGeometry() instead of trying to force resizing inside the frozen header itself.

    I'm not completly there yet. Still have a few issues I need to iron out.



  • Well, it all seems to work reasonably well now. Now I only need to figure out how to synchronize two frozen views (one upper and one lower) so that everything stays in sync. It used to work previously when the upper view didn't resize itself. Now it does, but the size change isn't propagated to the lower one.



  • Eureka! Seems like I finally cracked the last piece of the puzzle! I need to call updateGeometries() on the linked (lower) table view. That will call sizeHint() on the header views associated with it and thus propagate the size change and sync the upper and lower table views.

    @Christian-Ehrlicher thanks for all your input and pointing me in the direction of that bug report. (I still think that should be backported to 5.9.x. :-)


  • Qt Champions 2019

    @marcbf nice to hear that it's working now. But I don't think I can convince them to merge it back to 5.9 since it's really old, not prioritized and not much people complained about it. So you've a good argument updating to 5.12 soon :)



  • @Christian-Ehrlicher the reason I'm banging on about 5.9 is that it's the latest long time support branch. Since I'm not doing my own thing with Qt but rather working at a company long term support it quite important to us. We don't have the luxury to upgrade to a newer Qt version every month or so. But maybe I should just get in touch with commercial support about this. :-)


Log in to reply