Qt虽然提供了诸如 Qt::FramelessWindowHint 之类的属性可以移除窗体的边框,但是移除边框之后,窗体的一些默认行为同时也被移除了,如鼠标拖动改变大小、双击标题栏最大化等,这些行为需要开发者自己来实现。

本文主要介绍实现无边框窗体的几种方案,并在最后分享了作者实现的方案。

笔者认为,一个完美的无边框窗体解决方案需要支持如下功能:

  1. 支持通过使用鼠标拖拽来改变窗体位置和大小;
  2. 支持双击标题栏最大化窗体和还原窗体;
  3. 支持 Windows Areo Snap 特性;
  4. 支持系统阴影;
  5. 支持跨不同 DPI 的屏幕拖拽;
  6. 适应分辨率和 DPI 改变;

在Qt中实现无边框窗体有2种方案:

一、Hook方案

通过重载 nativeEvent 函数拦截 Windows 消息(如 WM_NCHITTEST)来实现,大致步骤如下:

  1. 给窗体设置 WS_THICKFRAME | WS_CAPTION 属性从而还原窗体边框和标题栏,这样窗体就可以接收到 WM_NCHITTEST 消息。
  2. 在 WM_NCCALCSIZE 消息处理中再移除边框和标题栏。
  3. 在 WM_NCHITTEST 消息处理中通过判断鼠标位置来设置鼠标行为( HTLEFT, HTRIGHT等)。

这种方案的优点是可以支持Windows Areo Snap系统阴影的特性,但是针对Windows消息处理起来很复杂而且需要兼容Qt的各个版本,目前我还没有找到一个通过这种方案来完美实现无边框的解决方案。

据我所知,有如下的开源项目是通过这种方式来实现的,但都有些许问题,如不支持跨不同DPI屏幕拖拽、不能适应分辨率和DPI改变、WM_NCHITTEST有时无响应等。另外,在设置了背景透明属性之后(如 Qt::WA_TranslucentBackground),系统阴影特性也将消失。

二、纯Qt方案

这种方案不Hook windows的 WM_NCHITTEST、WM_NCCALCSIZE 消息,也不改变窗体样式,通过纯Qt方式实现。通过对每个Widget设置MouseTracking,来使每个Widget都可以响应鼠标事件(mouseMoveEvent、mousePressEvent、mouseReleaseEvent等),然后这些事件中判断鼠标位置来设置鼠标的形状和行为。

这种方式虽然对鼠标位置的判断逻辑比较繁琐,但兼容性较好,较纯粹,不需要处理Windows的各个消息。

三、Qt-FramelessWindow

👉 我根倾向于使用纯Qt方案,简单、稳定、兼容性好,不用关注那些烦人的Windows消息。针对该方案的实现,可以参考笔者的开源项目【Qt-FramelessWindow】,目前可以支持上述除“Windows Areo Snap”和“系统阴影”特性之外的所有无边框窗体的特性,而且仅有一个头文件,使用起来也非常方便。


限于政策原因,在您看到该文章时,博客可能已经关闭了评论功能🥺

您可以通过在 blog-comment 项目中提交Issue来间接地发表评论🍀