Windows 10 在 Qt5 中围绕屏幕区域绘制高亮边框

Drawing a highlighting border around a region of the screen in Qt5 on Windows 10

我正在 Windows 10 上编写一个桌面应用程序,其功能类似于 "share your screen/app window",我遇到了试图在屏幕上突出显示感兴趣区域的经典问题。所以我需要绘制一个亮而粗的矩形,让矩形始终可见并且'on top',并且不干扰用户输入、鼠标移动等(即所有直通)。

我无法让它与 Qt v5.7 一起正常工作。我得到一个不透明的 window(我看不到它是什么 "below")和右边框,或者一个透明的 window 只有一个黑色的 1 像素边框。

我隐约知道如果我要使用 Windows 特定的 API,我可以创建一个 window 和 WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVE 风格等。 (使用类似 "chrome window" 的东西 - C# here 中的示例),但除了我还没有尝试过的事实(并且需要在 C++ 而不是 C# 中做),我宁愿这样做使用 Qt 是可能的。

案例 A 我还是 Qt 的新手,但我认为使用 QFrame 是可行的方法,所以我写了一些代码:

//
// A- Displays a completely transparent rectangle with a black 1-pixel border.
//
QFrame  *frame = new QFrame();
frame->setFrameStyle(QFrame::Box | QFrame::Plain);
frame->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint);
frame->setAttribute(Qt::WA_TranslucentBackground, true);
frame->setGeometry(1000,500,600,300);    // Just some fixed values to test
frame->show();

这给了我一个带有黑色 1 像素粗边框的矩形:

很棒,因为矩形位于其他所有内容之上,是透明的,可以通过鼠标输入等,无法获得焦点,无法调整大小或移动,也不会显示在任务栏上.

案例 B 我认为剩下的唯一问题是画一个粗的明亮边框,所以我在调用 frame->show():[=26= 之前编写了这个代码]

// Set a solid green thick border.
frame->setObjectName("testframe");
frame->setStyleSheet("#testframe {border: 5px solid green;}");

.. 但这完全没有给我任何帮助。 QFrame 根本没有显示!

案例C 所以作为测试,我去掉了Qt::WA_TranslucentBackground的设置。这是代码:

//
// C- Displays an opaque pass-through window with a green 5-pixel border.
//
QFrame  *frame = new QFrame();
frame->setFrameStyle(QFrame::Box | QFrame::Plain);
frame->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint);
// Disabled Qt::WA_TranslucentBackground
//frame->setAttribute(Qt::WA_TranslucentBackground, true);
frame->setGeometry(1000,500,600,300);    // Just some fixed values to test
// Set a solid green thick border.
frame->setObjectName("testframe");
frame->setStyleSheet("#testframe {border: 5px solid green;}");
frame->show();

那 window 仍然是完全直通、置顶等,并且有正确的边框,当然它是不透明的。

现在,事实是:

。当我自己更改边框样式时,它完全位于 window 框架内,因此当 window 透明时消失(案例 B)- 我认为。

有人知道正确的样式 sheet 设置应该是什么吗 以便我得到 window 完全透明的 5 像素绿色边框?

此外,我找不到任何文档可以确切地告诉我什么样式适用于什么类型的 widget/window(特别是 QFrame,对于我的测试用例)。那存在于任何地方吗?知道的话会很方便,因为我还想为边框使用渐变色,可能还有其他效果。

尝试将代码中的 QFrame 替换为...

class rubber_band: public QRubberBand {
  using super = QRubberBand;
public:
  template<typename... Types>
  explicit rubber_band (const Types &... args)
    : super(args...)
    {
      setAttribute(Qt::WA_TranslucentBackground, true);
    }
protected:
  virtual void paintEvent (QPaintEvent *event) override
    {
      QPainter painter(this);
      QPen pen(Qt::green);
      pen.setWidth(10);
      painter.setPen(pen);
      painter.drawLine(rect().topLeft(), rect().topRight());
      painter.drawLine(rect().topRight(), rect().bottomRight());
      painter.drawLine(rect().bottomRight(), rect().bottomLeft());
      painter.drawLine(rect().bottomLeft(), rect().topLeft());
    }
};

上述 rubber_band class 的实例应显示为具有完全透明主体的绿色边框。用作...

rubber_band *frame = new rubber_band(QRubberBand::Rectangle);
frame->setGeometry(1000, 500, 600, 300);
frame->show();

(注意:在使用 X11 的平台上,上述内容将要求 xcompmgr 等复合管理器为 运行。这通常不是问题。)

我找到了一个解决方案,即:

  • QFrame 上设置遮罩 排除 框架内部 - 这样 就变得完全透明了。
  • 未设置 Qt::WA_TranslucentBackground(否则看不到任何内容,根据 问题 中的 案例 B)。
  • 我还将不透明度设置为 0.5,以便渲染的边框部分透明。

最终代码为:

//
// D- Displays a completely transparent pass-through window with a green 5-pixel translucent border.
//
QFrame  *frame = new QFrame();
frame->setFrameStyle(QFrame::Box | QFrame::Plain);
frame->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint);
frame->setGeometry(1000,500,600,300);    // Just some fixed values to test
// Set a solid green thick border.
frame->setObjectName("testframe");
frame->setStyleSheet("#testframe {border: 5px solid green;}");
// IMPORTANT: A QRegion's coordinates are relative to the widget it's used in. This is not documented.
QRegion wholeFrameRegion(0,0,600,300);
QRegion innerFrameRegion = wholeFrameRegion.subtracted(QRegion(5,5,590,290));
frame->setMask(innerFrameRegion);
frame->setWindowOpacity(0.5);
frame->show();

我们最终得到的是:

我有点担心所有这些屏蔽会导致不是非常理想的解决方案,但后来我意识到 C# example 使用 Windows API 实际上做了一些非常相似的事情(矩形内的矩形排除该区域以保持透明)。所以也许这是正确的方法。