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(); }