Qt实现Windows 8风格无边框自定义窗口

4
我最近在Windows 7上安装了Github for Windows,喜欢它的自定义框架,它非常适合整体应用程序主题,并且有自己的标题栏按钮,排列得非常好,非常流畅,并且看起来非常自然。
我进行了一些调查,并找到了两个标志,可以完全清除边框。经过一些定制,我也使我的应用程序具有了漂亮的定制外观,这是直观的,但与所有旧Windows边框的应用程序不同。
问题是它不像其他窗口那样流畅和自然地响应,它很卡顿,我很容易用鼠标移动窗口,但它经常出现故障,并能够在不应该点击和拖动的区域移动,如禁用按钮。
最大化按钮链接到showMaximize方法,只会将整个窗口放大以占据整个桌面,您仍然可以移动它(并没有真正最大化)。
窗口没有响应任何系统信号,例如单击任务栏将其最小化等。
经过很多修复,我最终放弃了,这很遗憾,因为我真的很喜欢它的外观,而且非常直观,就像Github for Windows非常直观一样。
有没有办法可以实现这一点,我真的还没有准备好放弃。
我知道,在制作原始Windows API应用程序时,您必须将其链接到XP内置样式,因为它默认继承Windows 95样式,也许有一个与Qt不连接的Windows 8样式,我还没有进行深入研究。

我知道这已经很老了,但最近我开发了一个带有自定义边框(甚至可调整大小)的基础程序。因此,如果其他人需要,这是链接:https://github.com/Nintersoft/CustomTitlebar/ - mauroaraujo
2个回答

5

通过单击任务栏将窗口最小化

似乎 Qt::FramelessWindowHint 的实现是有限制的。当设置了此标识时,Windows 认为该窗口不能最小化或最大化。我尝试了在纯 winapi 中实现的这个解决方案。通过单击任务栏最小化和恢复无边框窗口可以正常工作。显然 Qt 设置了一些错误的标识阻止了这个功能。可能有很好的理由,我不知道。

我们可以使用 winapi 和 Qt 一起工作,但是这很麻烦。首先,在您设置窗口标志并使用 Qt 显示窗口后,应该执行 winapi 代码。否则,Qt 将覆盖窗口标志。

另一个问题是,当我们使用 winapi 移除边框时,窗口几何图形会突然改变,而 Qt 不知道这一点。渲染和事件映射(包括鼠标点击位置)变得无效。我没有找到任何文档记录的方法来更新映射关系。我发现我们可以告诉 Qt 屏幕方向已经改变了,它就会强制重新计算窗口几何图形。但是这看起来像一个肮脏的小技巧。此外,QWidget::windowHandle 函数在 Qt 4 中缺失,在 Qt 5 中 "可能会更改"。因此,这种方法不可靠。但无论如何,它现在可以工作了。下面是完整的代码(在 Windows 8 中测试通过),应放置在顶层窗口类构造函数中:

#include "windows.h"
#include <QWindow>
//...
show();
HWND hwnd = reinterpret_cast<HWND>(effectiveWinId());
LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU);
SetWindowLong(hwnd, GWL_STYLE, lStyle);
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
windowHandle()->reportContentOrientationChange(Qt::PrimaryOrientation);

解决这个问题的正确方法是修改Window Qt平台插件(请参见Qt源代码中的QWindowsWindow类)。也许有一种方式可以从默认实现中继承,进行修改并在您的应用程序中使用。您还可以询问Qt开发人员此行为是否合理或者是一个bug。我认为这个问题可以通过补丁来解决。
如果您仍打算使用此代码,并且其他操作系统也应该得到支持,请不要忘记在#ifdef Q_OS_WIN中包装特定于Windows的实现。
仅当单击标题栏并且窗口未最大化时启用窗口拖动
其他问题可以更轻松地解决。当处理鼠标事件以实现窗口拖动时,请检查窗口状态和事件位置,并在不需要移动时禁用它。
void MainWindow::mousePressEvent(QMouseEvent *e) {
  if (!isMaximized() && 
      e->button() == Qt::LeftButton && 
      ui->title->geometry().contains(e->pos())) {
    window_drag_start_pos = e->pos();
  }
}

void MainWindow::mouseReleaseEvent(QMouseEvent *e) {
  window_drag_start_pos = QPoint(0, 0);
}

void MainWindow::mouseMoveEvent(QMouseEvent *e) {
  if (!window_drag_start_pos.isNull()) {
    move(pos() + e->pos() - window_drag_start_pos);
  }
}

void MainWindow::on_minimize_clicked() {
  showMinimized();
}

void MainWindow::on_maximize_clicked() {
  if (isMaximized()) {
    showNormal();
  } else {
    showMaximized();
  }
}

这里的ui->title是用于显示虚拟标题栏的标签,而QPoint window_drag_start_pos是一个类变量。


isMaximized方法似乎也没有什么用,我尝试了on_maximize_clicked()的确切代码,它检查窗口是否位于0,0位置并且是屏幕的宽度和高度。窗口仍然可以移动,一旦移动,它就不再位于0,0,因此isMaximized返回false,因此它尝试最大化,但大小仍然是监视器的宽度和高度,因此什么也不会发生。第二次单击显然会将其恢复到还原模式。另外,我也尝试了标题栏,如果标题栏按钮在标题栏中,则单击并拖动按钮会移动窗口。 - 黒い雪
我通过在标题栏旁边的按钮中添加一个不可见的手柄来解决了这个问题,这使得它可以工作,但我从未完全做到,你必须在手柄的中间单击,尽管我已经删除了所有的边距并垂直扩展了手柄等等... 最后,我想这只是不可能的,否则会变得太混乱,我更喜欢一个流畅的应用程序,而不是看起来漂亮而优雅但非常有故障的应用程序。 - 黒い雪
我没有遇到描述的问题。on_maximize_clicked 代码和 isMaximized 对我来说完美无缺。ui->title->geometry() 不包含最大化/最小化按钮的位置,因此通过单击这些按钮移动窗口是不可能的。也许这些问题是由你代码中的某些问题引起的。尝试运行我的完整代码 - Pavel Strakhov
1
关于最小化:如果您设置了 Qt::WindowMinimizeButtonHintQt::FramelessWindowHint(即 setWindowFlags(windowFlags() | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);),那么窗口将没有最小化按钮,但仍然可以通过单击任务栏图标来最小化。 - Ignitor
Qt::FramelessWindowHint不是最佳解决方案。这样做会失去所有Windows Aero特定的功能,并且最大化窗口无法正常工作(而是进入全屏模式,隐藏任务栏)。 - i know nothing

1
如果您使用Qt :: FramelessWindowHint,则会失去所有与Windows框架相关的功能,例如停靠、快捷键、最大化等。您可以自己实现其中一些功能,在某些情况下需要付出很大努力,但其他事情仍然无法正常工作,包括处理多个监视器和Windows Key的所有功能。如果您需要应用程序像普通的Windows应用程序一样运行,Qt :: FramelessWindowHint基本上是一个死胡同。
唯一真正的解决方案是使用Windows提供的DWM API来完成此目的,使用DWM自定义窗口框架。他们提供了如何在框架区域内执行任何您想要的操作的示例,同时保留了所有标准的窗口管理器行为。

像Chrome这样的应用程序确实使用DWM(请参见Chromium源代码中的AeroGlassFrame类)。当然,Chrome没有使用Qt,因此它不必绕过QMainWindow来处理Windows特定的消息,他们的代码有很多注释表明它是多么混乱,例如:

 // Hack necessary to stop black background flicker, we cut out
 // resizeborder here to save us from having to do too much
 // addition and subtraction in Layout() ...

同样地,你提到的 Github-for-Windows 应用程序是建立在一个名为 Electron 的平台上的,而这个平台又使用了 DWM。

我还没有找到任何关于在 Qt 应用程序周围使用 DWM 框架的可行示例,但希望这能提供一个起点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接