QMainWindow之Dock Widget若干BUG小记

晨曦之光 发布于 2012/05/08 10:14
阅读 697
收藏 0

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

参考



原文链接:http://blog.csdn.net/dbzhang800/article/details/7096950
加载中
返回顶部
顶部