项目升级到Qt 5.15.2之后就发现这个诡异的现象,但是Qt 5.5就没有这个现象。很是麻烦,一点头绪都没有。
我们先来看下现象。
再看看异常的UI:
这两张图片可以看出明显的不一样。第二张图片里面的控件像是被拉伸,切割了一样。
其实在真实的项目场景中,UI就像被是切割了一样。
先说原因,其实这个是因为系统的DPI发生了变化,导致界面进行了缩放。
比如:我先把系统的DPI设置150%,然后软件也会跟着放大1.5倍。我再把系统的DPI还原为100%,此时软件应该恢复到原来的大小,但实际上并没有,而是大小被切割了一部分。
好,先说我的解决方案:
我先是设置禁止了高分辨率属性,再连接了Qt里面屏幕DPI,屏幕geometry发生变化的信号,然后再将窗口移动一个像素。
代码如下:
// main函数设置Qt::AA_DisableHighDpiScaling
int main()
{
...
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
...
}
// 窗口中连接信号
QWidget::QWidget(QWidget *parent)
{
QList<QScreen*> screens = QApplication::screens();
for (auto &item : screens)
{
VERIFY(connect(item, SIGNAL(logicalDotsPerInchChanged(qreal)), this, SLOT(OnLogicalDotsPerInchChanged(qreal))));
VERIFY(connect(item, SIGNAL(geometryChanged(const QRect &)), this, SLOT(OnGeometryChanged(const QRect &))));
}
}
void QWidget::OnLogicalDotsPerInchChanged(qreal dpi)
{
QTimer::singleShot(100, [this] () {this->move(this->pos()-QPoint(-1, -1));});
}
void QWidget::OnGeometryChanged(const QRect &)
{
QTimer::singleShot(100, [this] () {this->move(this->pos()-QPoint(-1, -1));});
}
这个刚开始问题完全是没有头绪,而且还是偶现的,还有经过我细心的观察这个bug还有2个特点,一个就是几个小时才会出现(这里就给我埋了个大坑),还有个就是你鼠标拖动这个窗口就会显示正常。
一点思路都没有,那就只能用排除法来做,不断的屏蔽一些代码。
这个窗口主要用到了QWebEngine作为浏览器去登录,所以就想到了设置这个模块的一些属性看看。
也是在网上乱搜,胡乱的配置一些属性。哎,发现好像没有这个问题了,在挂机几个小时测试后也没有这个问题了。
一次偶然的测试,发现上面的问题又能重现,心里极度郁闷。所以只能卸载模块。但是卸载之后,测试几天还是没有解决问题。
真的是一筹莫展,没办法,我觉得这个问题我暂时没法解决还是放一放吧。但是我还时刻观察这个问题。
就是再一次偶然的测试中,我发现一个异常。
我刚把笔记本HDMI接口拔掉,再去看软件的时候,软件UI被切割的现象重现。我从打开软件到现在不过才十几分钟,我突然意识到这个问题和时间没有关系。那到底是什么原因。我还是没有搞清楚。
只能重复我之前的动作,先接上外接显示器,再拔掉HDMI线。
果然,问题能够稳定的重现。心中舒了一口气,问题能够重新就相当于解决了一半,即使解决不了,我也要知道什么原因导致的。到时候也好有个交代,不然领导问原因,怎么办。
我开始思考到底什么原因,我的笔记本和显示器有什么联系?
我的笔记本是2k分辨率,我用的显示器是4k,我又是远程连接办公的。突然我想起了,难道是分辨率的不同。
嘿,终于有个线索了。我终于找到有用的信息,在Qt 5.5之前并不支持这个新的特性,在Qt5.6后加入了这个新的特性。
到现在我终于大概知道是什么原因了。原来是我在笔记本和显示器之间不断的来回切换分辨率和DPI,导致了Qt框架自身的不断缩放控件大小。
这就解释了为什么这个bug很难重现,原来是方法不对。
当我知道是DPI发生变化,引起的原因之后。心中的一块石头终于落下来,因为我大概记得有个属性是可以设置的。
当我满心欢喜去设置这个属性Qt::AA_DisableHighDpiScaling
,发现问题好像解决了一半。
将分辨率提高,窗口size不会发生变化了。但是如果我将分辨率从高降低,问题还是能重现。
哎……脑壳疼,咋办,这个属性好像不是银弹啊。
再看源码,发现这个属性只有高分辨的时候有效,而且我的确是看到代码直接return,说明这个属性还是有用的。
我还重写QWidget::resizeEvent(QResizEvent *)
事件,下了个断点方便调试。
我觉得我不能从源码往上推导为什么分辨率变化导致窗口大小发生变化,因为真的太费劲了。还有一点我对这个问题没有很强的好奇心。
所以我换了思路,从现象入手。
我还记得鼠标动一下,窗口就恢复了。所以我想到只要监听屏幕属性发生了变化,移动窗口1px就可以了。
于是就有了上面的解决方案。
我还是挺好奇的为什么move一下就可以了。看了源码,哦,原来如此。
一看你就知道了:
QWidget::move
--> QWidgetPrivate::setGeometry_sys
在这个QWidgetPrivate::setGeometry_sys
函数做了很多事情,它会重新计算geometry,并发送QResizeEvent
事件。
刚开始我发现这个问题的时候,我也是google了一段时间。但是我搜索的关键字是Qt UI被切割的现象,根本就没有有用的信息。
由于不知道原因,做了很多的无用功。所以对症下药真的非常重要。
当我知道原因后,花了很长时间试了网上很多的方案还是不能满足我的要求,最后被逼的去看源码。不然谁去看啊。没事,看两眼源码还是有很大的帮助的。