16,759
社区成员




QT中用GDI+绘制鼠标移动时的线段,当屏幕显示设置缩放比例为100%,直线段的起始位置和鼠标按下和移动相同,但缩放比例部位100%时,绘出的线段和鼠标位置有差异,且缩放比例越大,位置差别也越大。是什么原因?用VS2022和Gdi+绘图就没有此现象。
在QT中使用GDI+绘制鼠标移动时的线段,遇到屏幕缩放比例不为100%时绘出的线段和鼠标位置有差异的问题,主要原因涉及屏幕缩放比例(DPI缩放)和坐标映射。
一、原因分析
二、解决方案及相关书籍推荐
推荐书籍 | 图书特点 |
---|---|
《C++ GUI Programming with Qt 4》 | 作者:Jasmin Blanchette、Mark Summerfield,全面涵盖Qt 4知识,有大量示例代码,适合初学者深入学习QT编程,但未涉及部分新特性 |
《GDI+ Programming Guide》 | 作者:Mahmoud Elkoush,专门针对GDI+编程,详细讲解其绘图功能和原理,但缺乏与QT结合的案例 |
《Mastering Qt 5》 | 作者:Guillaume Lazar、Robin Penea,涵盖Qt 5新特性,对高DPI支持讲解深入,适合了解最新QT技术,但对旧版本QT读者可能有学习门槛 |
坐标转换问题
在 QT 中,当屏幕缩放比例不是 100% 时,鼠标坐标和绘图坐标之间的转换可能出现了问题。QT 可能会对窗口坐标进行缩放调整,而 GDI + 绘制时如果没有正确考虑这种缩放因素,就会导致绘制位置与鼠标位置不匹配。
当缩放比例改变时,QT 的窗口坐标系可能会根据缩放因子进行相应的变换,例如,在高缩放比例下,实际的物理像素和逻辑像素之间的映射关系发生了变化。而在 VS2022 中可能不存在这种自动的缩放调整或者其坐标转换机制与 QT 不同。
设备上下文(DC)相关差异
QT 在处理设备上下文和 GDI + 绘图的结合方式可能与 VS2022 有所不同。在 QT 环境下,设备上下文可能会受到 QT 的显示系统设置(如缩放)的影响。
当缩放比例改变时,QT 设备上下文的映射模式或者坐标转换可能导致 GDI + 绘制的线段起点和终点坐标被错误地转换。而在 VS2022 中,可能直接使用了 GDI + 的原始坐标系统,没有受到类似 QT 中额外的显示缩放因素的干扰。
事件处理和坐标获取差异
QT 的鼠标事件处理机制可能在获取鼠标位置时,返回的是经过 QT 内部缩放处理后的坐标。而在绘制过程中,如果没有按照相同的缩放规则将这些坐标转换为 GDI + 绘图可用的坐标,就会出现偏差。
相比之下,VS2022 中的鼠标事件坐标和 GDI + 绘图坐标之间可能有更直接的对应关系,没有经过像 QT 这样复杂的缩放调整,所以不会出现位置差异的现象。
在使用 Qt 中结合 GDI+ 绘制鼠标移动时的线段时,出现这种现象的主要原因是 屏幕缩放比例(DPI 缩放) 和 坐标映射 问题。屏幕缩放比例影响了应用程序的逻辑坐标和实际显示坐标之间的映射关系。如果处理不当,就会导致鼠标事件的坐标与绘图输出之间存在偏差,缩放比例越大,偏差也就越明显。
下面分析问题产生的具体原因以及解决方法:
原因分析
鼠标事件的坐标未考虑 DPI 缩放 在 Qt 中,鼠标事件的坐标(如 QMouseEvent::pos() 或 QMouseEvent::globalPos())是基于逻辑坐标系的。当系统的缩放比例不是 100% 时,这些坐标与 GDI+ 绘图的物理坐标(屏幕像素坐标)之间可能不一致。GDI+ 默认使用物理像素坐标,这导致了坐标差异。
Qt 默认支持高 DPI 缩放 Qt 的高 DPI 支持(由 QGuiApplication::highDpiScaleFactorRoundingPolicy 和 devicePixelRatio 控制)会根据系统缩放比例自动调整坐标系。例如,当缩放比例为 150% 时,Qt 中的逻辑坐标可能会自动缩放到 DPI 缩放后的比例,而 GDI+ 没有自动适配。
VS2022 中的绘图与 GDI+ VS2022 中的 GDI+ 绘图直接基于屏幕的物理像素坐标,而不涉及 Qt 的逻辑坐标调整,因此不会产生这种坐标偏移现象。
解决方案
获取正确的鼠标坐标 将鼠标事件的逻辑坐标转换为物理像素坐标,确保传递给 GDI+ 的是正确的坐标。可以通过以下方法获取物理坐标:
QPoint logicalPos = event->pos(); // Qt 提供的逻辑坐标
QPointF physicalPos = logicalPos * devicePixelRatioF(); // 转换为物理像素坐标
使用 QWidget::devicePixelRatioF() 或 QScreen::devicePixelRatio() 获取设备的缩放系数。
适配 GDI+ 坐标系统 调整 GDI+ 的绘图模式,使其匹配 Qt 的逻辑坐标。例如,设置 GDI+ 的变换矩阵:
Graphics graphics(hdc);
float scaleFactor = devicePixelRatioF();
graphics.ScaleTransform(scaleFactor, scaleFactor);
这样,GDI+ 的坐标系统会与 Qt 的逻辑坐标保持一致。
禁用 Qt 的高 DPI 支持(仅作为测试) 如果需要排查问题,可以暂时禁用 Qt 的高 DPI 支持:
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
注意:禁用高 DPI 支持可能导致程序在高分辨率屏幕上显示不清晰。
校准绘图区域的坐标系统 如果使用 QPaintDevice 的 HDC,确保在绘图前设置正确的 DPI 缩放。例如:
HDC hdc = getDCForWidget(myWidget);
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, logicalWidth, logicalHeight, nullptr);
SetViewportExtEx(hdc, physicalWidth, physicalHeight, nullptr);
示例代码
以下是修正代码的简单示例
void MyWidget::mouseMoveEvent(QMouseEvent *event) {
// 获取逻辑坐标
QPoint logicalPos = event->pos();
// 转换为物理坐标
QPointF physicalPos = logicalPos * devicePixelRatioF();
// 使用 GDI+ 绘图
HDC hdc = GetDC(reinterpret_cast<HWND>(winId()));
Graphics graphics(hdc);
Pen pen(Color(255, 0, 0), 2);
graphics.DrawLine(&pen, Gdiplus::PointF(startPos.x(), startPos.y()),
Gdiplus::PointF(physicalPos.x(), physicalPos.y()));
ReleaseDC(reinterpret_cast<HWND>(winId()), hdc);
}
总结
问题的根源在于 Qt 的逻辑坐标和 GDI+ 的物理像素坐标未对齐。通过正确获取 DPI 缩放系数并在两者之间进行转换,可以解决鼠标位置与绘图结果之间的偏差问题。
应该是缩放代码出了问题 微软系统显卡输出比例不同大小也不同。比如说100%是10901280 150%就变成了1020720
// 这是代码。
// 在屏幕缩放比例为100%时,正常,大于100%时,位置有差异。不知道为何?
void MyWidget::mousePressEvent(QMouseEvent *event)
{
// 鼠标左键按下
if(event->button() == Qt::LeftButton)
{
startpos = event->pos();
endpos = startpos;
}
}
// 鼠标移动事件
void MyWidget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
endpos = event->pos();
this->update();
}
}
// 重绘事件
void MyWidget::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
HWND hwnd = (HWND)this->winId();
HDC hdc = GetDC(hwnd);
int nWidth = this->geometry().width();
int nHeight = this->geometry().height();
HDC memDC = CreateCompatibleDC(hdc);
HGDIOBJ memBmp = CreateCompatibleBitmap(hdc,nWidth, nHeight);
HGDIOBJ oldBmp = SelectObject(memDC, memBmp);
PatBlt(memDc, 0, 0, nWidth, nHeight, WHITENESS);//背景白色
DrawLine(memDC);
BitBlt(hdc, 0, 0, nWidth, nHeight, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, oldBmp);
DeleteObject(memBmp);
DeleteDC(memDC);
ReleaseDC(hwnd, hdc);
}
void MyWidget::DrawLine(HDC hdc)
{
Gdiplus::Graphics graphics(hdc);
graphics.SetPageUnit(Gdiplus::UnitPixel);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
Gdiplus::Pen pen(Gdiplus::Color(255, 255, 0, 0), 1);
graphics.DrawLine(&pen, startpos.x(), startpos.y(), endpos.x(), endpos.y());
}