在项目中总是遇到各种奇怪的需求,这不,碰到一个。
我们想要的我们的程序支持一种特性,就是有的界面支持高DPI。但是有的保持原来的大小。
这可怎么办?Qt中支持高DPI很简单的一行代码 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling)
,但是它针对整个进程中所有的UI。
那没办法了,只能硬着头皮修改Qt的源码重新编译了。
对了,我的Qt版本是Qt 5.15.2.0。
其实我刚开始思路就是很简单,就是给QWidget设置一个flag,凡是这个flag为false表示不支持DPI缩放。这就是我整个的思路。
这里我就把修改的代码,生成patch文件,放在这里。可以通过git将patch应用到Qt源码中。
这个里面的.gitattributes,command.bat,copy_qch.sh
这三个文件的修改直接忽略掉。
保存的文件格式要注意:unix格式换行符,GB2312编码
From cedae90de66f68638162198374be5ac848a5a6f9 Mon Sep 17 00:00:00 2001
From: "Shixiong.Liu" <635672377@qq.com>
Date: Fri, 16 Sep 2022 14:28:27 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0widget=E5=8A=A8=E6=80=81?=
=?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=94=AF=E6=8C=81=E9=AB=98DPI=E7=89=B9?=
=?UTF-8?q?=E6=80=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitattributes | 1 +
command.bat | 17 ++++++++++
copy_qch.sh | 31 +++++++++++++++++++
qtbase/src/gui/kernel/qhighdpiscaling.cpp | 3 +-
qtbase/src/gui/kernel/qplatformwindow.cpp | 7 ++++-
qtbase/src/gui/kernel/qwindow.cpp | 28 +++++++++++++----
qtbase/src/gui/kernel/qwindow.h | 3 ++
qtbase/src/gui/kernel/qwindow_p.h | 1 +
.../platforms/windows/qwindowscontext.cpp | 5 +++
.../platforms/windows/qwindowswindow.cpp | 4 +--
qtbase/src/widgets/kernel/qwidget.cpp | 2 ++
qtbase/src/widgets/kernel/qwidget.h | 7 ++++-
12 files changed, 97 insertions(+), 12 deletions(-)
create mode 100644 .gitattributes
create mode 100644 command.bat
create mode 100644 copy_qch.sh
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..dfdb8b771c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.sh text eol=lf
diff --git a/command.bat b/command.bat
new file mode 100644
index 0000000000..7d7b2af180
--- /dev/null
+++ b/command.bat
@@ -0,0 +1,17 @@
+REM 找到你的vs2019初始化环境的bat目录,我用的enterprise版本,不用的版本只需要替换下面的"Enterprise"就可以了
+CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
+
+SET _ROOT=F:\qt5.15.2_doc
+SET PATH=D:\soft_dev\build_qt_devs\cmake-3.22.0\bin;%PATH%
+SET PATH=%_ROOT%\qtbase\bin;%PATH%
+SET PATH=D:\soft_dev\build_qt_devs\strawberry-perl-5.32.1.1\perl\bin;%PATH%
+SET PATH=D:\soft_dev\build_qt_devs\ninja-win;%PATH%
+SET LLVM_INSTALL_DIR=C:\Program Files (x86)\LLVM\
+
+set "MY_INSTALL_PATH=F:\qt5.15.2_doc\bin"
+
+REM configure.bat -prefix %MY_INSTALL_PATH% -DQT_NO_EXCEPTIONS=1 -debug-and-release -force-debug-info -platform win32-msvc -opensource -confirm-license
+
+REM configure.bat -prefix %MY_INSTALL_PATH% -DQT_NO_EXCEPTIONS=0 -nomake tests -nomake examples -debug-and-release -force-debug-info -platform win32-msvc -opensource -confirm-license OPENSSL_PREFIX=D:\qt\openssl-1.1.1p\win32-release -openssl-linked -I D:\qt\openssl-1.1.1p\win32-release\include -L D:\qt\openssl-1.1.1p\win32-release\lib OPENSSL_LIBS="libssl.lib libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib"
+
+configure.bat -prefix %MY_INSTALL_PATH% -nomake tests -nomake examples -debug -force-debug-info -platform win32-msvc -opensource -confirm-license OPENSSL_PREFIX=D:\qt\openssl-1.1.1p\win32-release -openssl-linked -I F:\openssl-1.1.1p\win32-release\include -L F:\openssl-1.1.1p\win32-release\lib OPENSSL_LIBS="libssl.lib libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib"
diff --git a/copy_qch.sh b/copy_qch.sh
new file mode 100644
index 0000000000..b00835a808
--- /dev/null
+++ b/copy_qch.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+dst="./bin/doc/"
+blank=""
+
+RED='\e[1;31m' # 绾?+RES='\e[0m'
+
+function echo_color {
+ echo -e "${RED}$1${RES}"
+}
+
+for f in $(find . -name '*.qch');
+ do
+ # cp ${f} ${dst}
+ file_name=`basename ${f}`
+ if [ ! -f "${dst}${file_name}" ];then
+ cp ${f} ${dst}
+ else
+ echo_color "${f} already exist"
+ fi
+ # echo ${file_name}
+ file_name_no_postfix=${file_name//.qch/${blank}}
+ dir_name="${dst}${file_name_no_postfix}"
+ if [ ! -d ${dir_name} ];then
+ mkdir ${dir_name}
+ cp -r ${f//.qch/${blank}} ${dst}
+ else
+ echo_color "${dir_name} already exist"
+ fi
+ done;
diff --git a/qtbase/src/gui/kernel/qhighdpiscaling.cpp b/qtbase/src/gui/kernel/qhighdpiscaling.cpp
index 9bbf2773a9..11e5c72447 100644
--- a/qtbase/src/gui/kernel/qhighdpiscaling.cpp
+++ b/qtbase/src/gui/kernel/qhighdpiscaling.cpp
@@ -716,9 +716,8 @@ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *s
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
{
- if (!m_active)
+ if (!m_active || (window && !window->isAdaptDPI()))
return { qreal(1), QPoint() };
-
QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
const bool searchScreen = !window || window->isTopLevel();
return scaleAndOrigin(screen, searchScreen ? nativePosition : nullptr);
diff --git a/qtbase/src/gui/kernel/qplatformwindow.cpp b/qtbase/src/gui/kernel/qplatformwindow.cpp
index fc736033c2..be91c10792 100644
--- a/qtbase/src/gui/kernel/qplatformwindow.cpp
+++ b/qtbase/src/gui/kernel/qplatformwindow.cpp
@@ -730,7 +730,12 @@ QRect QPlatformWindow::initialGeometry(const QWindow *w, const QRect &initialGeo
}
}
}
- return QHighDpi::toNativePixels(rect, screen);
+
+ QRect geometry = rect;
+ if (w->isAdaptDPI()) {
+ geometry = QHighDpi::toNativePixels(rect, screen);
+ }
+ return geometry;
}
/*!
diff --git a/qtbase/src/gui/kernel/qwindow.cpp b/qtbase/src/gui/kernel/qwindow.cpp
index fd89e479b8..d1b5bc98c8 100644
--- a/qtbase/src/gui/kernel/qwindow.cpp
+++ b/qtbase/src/gui/kernel/qwindow.cpp
@@ -791,6 +791,18 @@ void QWindow::setModality(Qt::WindowModality modality)
emit modalityChanged(modality);
}
+bool QWindow::isAdaptDPI() const
+{
+ Q_D(const QWindow);
+ return d->adapt_dpi;
+}
+
+void QWindow::setAdaptDPI(bool adaptdpi)
+{
+ Q_D(QWindow);
+ d->adapt_dpi = adaptdpi;
+}
+
/*! \fn void QWindow::modalityChanged(Qt::WindowModality modality)
This signal is emitted when the Qwindow::modality property changes to \a modality.
@@ -1733,10 +1745,14 @@ void QWindow::setGeometry(const QRect &rect)
if (d->platformWindow) {
QRect nativeRect;
QScreen *newScreen = d->screenForGeometry(rect);
- if (newScreen && isTopLevel())
- nativeRect = QHighDpi::toNativePixels(rect, newScreen);
- else
- nativeRect = QHighDpi::toNativeLocalPosition(rect, newScreen);
+ if (this->isAdaptDPI()) {
+ if (newScreen && isTopLevel())
+ nativeRect = QHighDpi::toNativePixels(rect, newScreen);
+ else
+ nativeRect = QHighDpi::toNativeLocalPosition(rect, newScreen);
+ } else {
+ nativeRect = rect;
+ }
d->platformWindow->setGeometry(nativeRect);
} else {
d->geometry = rect;
@@ -2645,7 +2661,7 @@ QPoint QWindow::mapToGlobal(const QPoint &pos) const
return QHighDpi::fromNativeLocalPosition(d->platformWindow->mapToGlobal(QHighDpi::toNativeLocalPosition(pos, this)), this);
}
- if (QHighDpiScaling::isActive())
+ if (QHighDpiScaling::isActive() && d->adapt_dpi)
return QHighDpiScaling::mapPositionToGlobal(pos, d->globalPosition(), this);
return pos + d->globalPosition();
@@ -2669,7 +2685,7 @@ QPoint QWindow::mapFromGlobal(const QPoint &pos) const
return QHighDpi::fromNativeLocalPosition(d->platformWindow->mapFromGlobal(QHighDpi::toNativeLocalPosition(pos, this)), this);
}
- if (QHighDpiScaling::isActive())
+ if (QHighDpiScaling::isActive() && d->adapt_dpi)
return QHighDpiScaling::mapPositionFromGlobal(pos, d->globalPosition(), this);
return pos - d->globalPosition();
diff --git a/qtbase/src/gui/kernel/qwindow.h b/qtbase/src/gui/kernel/qwindow.h
index 7aae7ffffa..e9586c4413 100644
--- a/qtbase/src/gui/kernel/qwindow.h
+++ b/qtbase/src/gui/kernel/qwindow.h
@@ -168,6 +168,9 @@ public:
Qt::WindowModality modality() const;
void setModality(Qt::WindowModality modality);
+ bool isAdaptDPI() const;
+ void setAdaptDPI(bool adaptdpi);
+
void setFormat(const QSurfaceFormat &format);
QSurfaceFormat format() const override;
QSurfaceFormat requestedFormat() const;
diff --git a/qtbase/src/gui/kernel/qwindow_p.h b/qtbase/src/gui/kernel/qwindow_p.h
index 5a7ec518fd..f1514c76d1 100644
--- a/qtbase/src/gui/kernel/qwindow_p.h
+++ b/qtbase/src/gui/kernel/qwindow_p.h
@@ -139,6 +139,7 @@ public:
bool visible= false;
bool visibilityOnDestroy = false;
bool exposed = false;
+ bool adapt_dpi = true;
QSurfaceFormat requestedFormat;
QString windowTitle;
QString windowFilePath;
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
index fa757b0edc..ec84daa554 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -1438,6 +1438,11 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
#endif
} break;
case QtWindows::DpiChangedEvent: {
+ // ignore WM_DPICHANGEC message, if window adapt DPI property is false.
+ if (!platformWindow->window()->isAdaptDPI()) {
+ qCDebug(lcQpaWindows) << __FUNCTION__ << "Ignore WM_DPICHANGED event";
+ return false;
+ }
// Try to apply the suggested size first and then notify ScreenChanged
// so that the resize event sent from QGuiApplication incorporates it
// WM_DPICHANGED is sent with a size that avoids resize loops (by
diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
index d2c22f4100..63b2301ce6 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -1013,8 +1013,8 @@ void QWindowsGeometryHint::frameSizeConstraints(const QWindow *w, const QScreen
const QMargins &margins,
QSize *minimumSize, QSize *maximumSize)
{
- *minimumSize = toNativeSizeConstrained(w->minimumSize(), screen);
- *maximumSize = toNativeSizeConstrained(w->maximumSize(), screen);
+ *minimumSize = w->isAdaptDPI() ? toNativeSizeConstrained(w->minimumSize(), screen) : w->minimumSize();
+ *maximumSize = w->isAdaptDPI() ? toNativeSizeConstrained(w->maximumSize(), screen) : w->maximumSize();
const int maximumWidth = qMax(maximumSize->width(), minimumSize->width());
const int maximumHeight = qMax(maximumSize->height(), minimumSize->height());
diff --git a/qtbase/src/widgets/kernel/qwidget.cpp b/qtbase/src/widgets/kernel/qwidget.cpp
index 479d91be0e..05b52f3e7c 100644
--- a/qtbase/src/widgets/kernel/qwidget.cpp
+++ b/qtbase/src/widgets/kernel/qwidget.cpp
@@ -1021,6 +1021,7 @@ void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
data.in_show = 0;
data.in_set_window_state = 0;
data.in_destructor = false;
+ data.is_adapt_high_dpi = true;
// Widgets with Qt::MSWindowsOwnDC (typically QGLWidget) must have a window handle.
if (f & Qt::MSWindowsOwnDC) {
@@ -1311,6 +1312,7 @@ void QWidgetPrivate::create()
QWindowPrivate::WindowFrameInclusive : QWindowPrivate::WindowFrameExclusive;
if (q->windowType() != Qt::Desktop || q->testAttribute(Qt::WA_NativeWindow)) {
+ win->setAdaptDPI(data.is_adapt_high_dpi);
win->create();
// Enable nonclient-area events for QDockWidget and other NonClientArea-mouse event processing.
if (QPlatformWindow *platformWindow = win->handle())
diff --git a/qtbase/src/widgets/kernel/qwidget.h b/qtbase/src/widgets/kernel/qwidget.h
index 415a738eb4..ebdc8e6de3 100644
--- a/qtbase/src/widgets/kernel/qwidget.h
+++ b/qtbase/src/widgets/kernel/qwidget.h
@@ -117,7 +117,9 @@ public:
uint context_menu_policy : 3;
uint window_modality : 2;
uint in_destructor : 1;
- uint unused : 13;
+ uint is_adapt_high_dpi: 1;
+ // uint unused : 13;
+ uint unused : 12;
QRect crect;
mutable QPalette pal;
QFont fnt;
@@ -180,6 +182,7 @@ class Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice
Q_PROPERTY(QString windowIconText READ windowIconText WRITE setWindowIconText NOTIFY windowIconTextChanged) // deprecated
Q_PROPERTY(double windowOpacity READ windowOpacity WRITE setWindowOpacity)
Q_PROPERTY(bool windowModified READ isWindowModified WRITE setWindowModified)
+ Q_PROPERTY(bool adaptHighDPI READ adaptHighDPI WRITE setAdaptHighDPI)
#ifndef QT_NO_TOOLTIP
Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip)
Q_PROPERTY(int toolTipDuration READ toolTipDuration WRITE setToolTipDuration)
@@ -360,6 +363,8 @@ public:
void grabGesture(Qt::GestureType type, Qt::GestureFlags flags = Qt::GestureFlags());
void ungrabGesture(Qt::GestureType type);
#endif
+ bool adaptHighDPI() const { return data->is_adapt_high_dpi; }
+ void setAdaptHighDPI(bool on) { data->is_adapt_high_dpi = on; }
public Q_SLOTS:
void setWindowTitle(const QString &);
--
2.33.0.windows.2
qhighdpiscaling.cpp
这个文件就是UI缩放有着密切的关系,从名字就可以看出。这个基本是个静态类,内容不多。
qplatformwindow.cpp
这个class很复杂,应该是封装了平台相关的window。
qwindow.cpp
这个class和QWidget
关系相当密切,在Qt的内部通过操作这个class,来达到修改QWidget属性。
qwidget.cpp
我们最熟悉的QWidget控件。
这里有个class需要特别的注意,就是QScreen
这就是屏幕分辨率密切相关的。每个QWiget下面都会带着个QScreen实例,而且这个是实例会不断的切换。
比如:你有多个屏幕,从屏幕1切到到屏幕2,此时QScreen
就会被修改,同时根据此屏幕的DPI进行缩放。
再简单的画出class的关系:
再根据堆栈信息看下整个QWidget创建过程:
我最开始是定位到qhighdpiscaling_p.h
这个文件,找到如下的函数。这里是起点。
但是我一开始怎么找到这里的。没有任何的技巧,就是全局搜索关键字dpi
,有时候第一次并不总是成功,所以需要多尝试和调试,就是需要耐心。
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
{
if (!m_active || (window && !window->isAdaptDPI()))
return { qreal(1), QPoint() };
QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
const bool searchScreen = !window || window->isTopLevel();
return scaleAndOrigin(screen, searchScreen ? nativePosition : nullptr);
}
找到了起点之后,就是漫漫的编译调试之路了。接下来我一个个解释修改的地方含义。对照相应的patch片段来介绍。
1、首先QWidget
增加了一个自定义的bool属性,这样可以在Designer中可视化操作。
在QWidgetWindow
调用QWindow
父类create()
函数之前就提前将属性值设置进去。否则就会出现在高DPI屏幕时初始化就会进行放大。
2、拒接接受系统发送的DPI change Event消息
3、下面这几处就是调试过程发现,同时需要屏蔽的地方。
4、在到了进行缩放时根据属性值判断是否真的需要缩放。
在回顾下思路:就是给QWidget
提供一个对用户的接口,让用户自己去决定。然后QWidget
将属性值赋值给QWindow
,然后QWindow
带着属性值满世界的跑,需要的地方就进行判断。
整个思路还是很简单的。
也是第一次修改Qt的源码,整个过程很是费劲,而且编译等待过程也是相当的漫长。所以每次编译之前一定要确保代码的正确性。
在阅读的过程中,也是受益良多。