QMainWindow之Dock Widget若干BUG小记

13 篇文章 5 订阅
12 篇文章 9 订阅

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

参考


在C++中,`dockWidget` 通常与Qt框架的窗口部件相关,其中 `QDockWidget` 是用于实现停靠窗口的类。要实现 `dockWidget` 贴边隐藏的效果,通常需要调用 `QDockWidget` 的 `setFeatures` 方法并设置合适的窗口特性。 以下是一个简单的例子,展示如何设置 `QDockWidget` 以便在它靠边时自动隐藏: ```cpp #include <QDockWidget> #include <QMainWindow> #include <QVBoxLayout> #include <QWidget> class MyMainWindow : public QMainWindow { public: MyMainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { // 创建一个 dock widget QDockWidget *dockWidget = new QDockWidget("Side Panel", this); setDockOptions(dockOptions() | QMainWindow::ForceTabbedDocks); // 强制使用标签页停靠 // 设置 dock widget 的特性,使其在靠边时隐藏 dockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); connect(dockWidget, &QDockWidget::topLevelChanged, this, [dockWidget](bool floating) { if (!floating) { dockWidget->hide(); } }); // 将 dock widget 添加到主窗口 addDockWidget(Qt::LeftDockWidgetArea, dockWidget); } }; ``` 在这个例子中,我们创建了一个 `QDockWidget` 对象并设置了一些特性,如可移动、可浮动和可关闭。我们还连接了 `topLevelChanged` 信号到一个 lambda 表达式,当停靠窗口不再是顶层窗口时(即被靠边时),它会被隐藏。 请确保你的项目已经设置了Qt环境,并且你的编译器支持C++11或更高版本,因为上述代码使用了C++11的lambda表达式。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值