QMainWindow之Dock Widget若干BUG小記

QTBUG8107

在QMainWindow中,我們可以通過拖動中心窗體和停靠窗體之間的分割線(Sepearator)來改變中心窗口的大小。

QTBUG8107描述這樣一個問題:

  • 當通過拖動Sepearator改變了停靠窗體的大小後,一旦我們改變整個QMainWindow窗口的大小時,停靠窗體的大小會自動跳到原來的大小。

這是Qt4.6.2(包括)之前Bug,現已修復,但由於它的修復代碼造成了其他bug,故爾,還是有必要看看

原因

當我們改變 QMainWindow 窗口大小時,其內部的QLayout要重新計算所有widget的大小

首先:QMainWindow 的矩形框

Rect0

菜單欄 和 狀態欄簡單,高度不變,直接減掉即可

得到一個Rect1

頂部和底部、左側和右側的工具欄區域,分別從矩形區域中除

得到一個Rect2

接下來:在Rect2 中,如何得到合理的中心窗體和停靠窗體的大小?

 
  • 這個東西很讓人頭痛,因爲它已經導致了很多了Bug

解決

當QMainWindow改變時,我們期待中心窗體相應地放大或縮小,而讓停靠窗體的寬度或高度保持不變

QTBUG15689

QTBUG15689 是 QTBUG8107 的一個副作用:

  • 如果一開始,QMainWindow沒有設置中心窗體,而是等界面顯示以後,我們再設置一箇中心窗體。此時,由於原有的停靠窗體傾向於保持自己的大小不變,那麼中心窗體只能佔據很小的一點點的空間。

原因?

思考的核心是,當佈局改變時,我們的停靠窗體是應該

  • 傾向於使用自己當前的大小

還是

  • 傾向於使用自己的sizeHint()

這是由 QDockAreaLayout中的

bool fallbackToSizeHints; //determines if we should use the sizehint for the dock areas (true until the layout is restored or the central widget is set)

所控制的。

注意看其中的註釋,它的意圖應該是:當QMainWindow被設置了中心窗體

QMainWindow::setCentralWidget()

或者恢復到先前保存的狀態

QMainWindow::restoreState()

後,每次都傾向於使用停靠窗口的當前大小。

但是,伴隨着QTBUG-8107的修復,已經改變了這個行爲(儘管此處的註釋沒有改動)

解決?

如何解決QTBUG15689而不影響QTBUG8107?

重新思考前面的問題:當佈局改變時,我們的停靠窗體是應該

  • 傾向於使用自己當前的大小

還是

  • 傾向於使用自己的sizeHint()

恩,似乎應該這樣

當且僅當調用了

QMainWindow::restoreState()

或者

手動通過分隔線調整了停靠窗口和中心窗口的大小

後,才讓停靠窗體傾向於使用自己的大小

具體修復起來比較簡單,一兩行代碼即可。

QTBUG15080

QTBUG15080描述這樣一個問題:

  • 用QMainWindow::saveState() 保存當前狀態,退出程序,然後再次啓動並使用QMainWindow::restoreState()恢復到先前佈局狀態。結果QDockWidget記不住先前的大小。

原因

前面的描述是一個表象,很難讓人聯想到QWidget::setStylesheet()這種看似完全無害的東西。

MainWindow::MainWindow()
{
     ...
     QSettings settings("MyCompany", "MyApp");
     restoreGeometry(settings.value("myWidget/geometry").toByteArray());
     restoreState(settings.value("myWidget/windowState").toByteArray());

     ui->lineEdit.setStylesheet("background-color: lime");
}

恩,這個bug的直接原因就在於此。

當我們調用setStylesheet時,會觸發QEvent::StyleChange事件,進而到了:

bool QMainWindow::event(QEvent *event)
{
    Q_D(QMainWindow);
    switch (event->type()) {
        case QEvent::StyleChange:
#ifndef QT_NO_DOCKWIDGET
            d->layout->layoutState.dockAreaLayout.styleChangedEvent();
#endif

然後:

void QDockAreaLayout::styleChangedEvent()
{
    sep = mainWindow->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, 0, mainWindow);
    fitLayout();
}

此處的fitLayout()負責調整DockArea內各個區域的大小。

在討論它爲何會造成問題之前,似乎我們可以先看看如何解決它...

如何避免?

該Bug後面的其他人的commit給出了各種避免的方法。比如使用QTimer將restoreState()的調用推後。其實直接將restoreState語句和setStylesheet交換位置也可以。

考慮QLayout如何起起作用的...

  • 調用QWidget::show()[即QWidget::setVisible()]時,在顯示之前,通過調用

QLayout::activate()

來激活佈局(如果有的話)

  • 改變窗體的大小時,

void QLayout::widgetEvent(QEvent *e)
{
...
    switch (e->type()) {
    case QEvent::Resize:
        if (d->activated) {
            QResizeEvent *r = (QResizeEvent *)e;
            d->doResize(r->size());
        } else {
            activate();
        }
        break;
  • 某個QWidget內的子Widget的大小自身變化,需要通知佈局進行重新計算

QWidget::updateGeometry()

如果parent有layout佈局,直接讓佈局無效(強制Layout重新計算大小)。而如果parent沒有佈局,它給父widget發送 LayoutRequest 事件

QDockAreaLayout::fitLayout()如何被調用?

QLayout起作用時,

QMainWindowLayout::setGeometry()

會重新計算各個widget的尺寸,而停靠區域的計算也在這兒被調用。

原因(續)

接前面的

void QDockAreaLayout::styleChangedEvent()
{
    sep = mainWindow->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, 0, mainWindow);
    fitLayout();
}

這段代碼是爲了修復QTBUG2774而引入的,當樣式改變時,直接調用fitLayout()來重新計算中心窗體和停靠窗體的大小。由於在窗口顯示之前,fitLayout()這個東西還會被調用多次,此處看起來也似乎沒什麼危害

但是,此時,我們已經調用了

QMainWindow::restoreState()

來試圖恢復狀態,而在QLayout尚未activate()之前,QDockAreaLayout::fitLayout()的調用,破壞了我們試圖要恢復的狀態。

解決

似乎只要加半條語句即可:

void QDockAreaLayout::styleChangedEvent()
{
    sep = mainWindow->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, 0, mainWindow);
    if (mainWindow->isVisible())
        fitLayout();
}

參考


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章