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