Qt-无边框窗口 设置任务栏缩略图

本文最后更新于:2022年1月30日 晚上

前情提要

任务栏缩略图可以在不激活窗口的状态下提供窗口的缩略信息

对于音乐播放器等软件,尤其重要,不仅可以增加观感,还能添加控制按钮

是专业程序员的不二之选

都1202年了,不会还有人不用无边框窗口(Framless)

でも

Qt自带的QWinThumbnailToolBar类是可以很方便地进行任务栏缩略图的控制

但是,对于无边框窗口,貌似出现了一些问题:

  • 缩略图周围出现了[类似标题栏的边框] 导致图片错位 十分影响美观

错位边框

在仔细阅读了Qt文档后,并没有什么收获,该类十分简洁,并无多于函数可供操作

止まるんじゃねよ

不要停下来啊 团长!

毕竟Qt还是封装的Windows API

所以还得从Windows API文档入手

在搜寻了关于TaskBar的内容后,我找到了这样一个函数:DwmSetIconicThumbnail

这个函数与Qt-QWinThumbnailToolBar::setIconicThumbnailPixmap不能说很像,简直就是同父异母

DwmSetIconicThumbnail

1
2
3
4
5
HRESULT DwmSetIconicThumbnail(
[in] HWND hwnd,
[in] HBITMAP hbmp,
[in] DWORD dwSITFlags
);

重点在于最后一个参数 dwSITFlags

  • 0 (0x00000000) - 提供的缩略图周围不显示任何框架
  • DWM_SIT_DISPLAYFRAME (0x00000001) - 在提供的缩略图周围显示一个框架

主要就在于这个参数中的框架二字,联想我们之前出现的错位情况,那可不就是多了一个框架

那么只要将参数设置为0,不就可以解决了嘛

根源

可是,Qt 为什么会出现这样的问题,难道是!

说罢,我便去查阅了Qt源码

やはり啊

1
2
3
4
5
6
7
8
9
10
11
inline void QWinThumbnailToolBarPrivate::updateIconicThumbnail(const MSG *message)
{
if (!iconicThumbnail)
return;
const QSize maxSize(HIWORD(message->lParam), LOWORD(message->lParam));
if (const HBITMAP bitmap = iconicThumbnail.bitmap(maxSize)) {
const HRESULT hr = DwmSetIconicThumbnail(message->hwnd, bitmap, dWM_SIT_DISPLAYFRAME);
if (FAILED(hr))
qWarning() << QWinThumbnailToolBarPrivate::msgComFailed("DwmSetIconicThumbnail", hr);
}
}

注意观察第7行:

1
const HRESULT hr = DwmSetIconicThumbnail(message->hwnd, bitmap, dWM_SIT_DISPLAYFRAME);

最后一个参数 写死了 dWM_SIT_DISPLAYFRAME

导致强制产生边框,与无边框窗口 产生冲突

而且还不提供接口修改参数

Qt程序员也太不走心了吧

Solution

那咋办

其实哈,与Windows API打交道是很烦人的一件事

所以,能少写就少写,尽量用Qt封装的类

だから、我们将采用Qt APIWindows API结合的方式 完成缩略图

毕竟 就只有setIconicThumbnailPixmap这一个函数写得不好嘛

我们只要用DwmSetIconicThumbnail代替它即可

1
2
3
4
5
HBITMAP hbm = QtWin::toHBITMAP(pixmap.scaled(maxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
if (hbm) {
HRESULT hr = DwmSetIconicThumbnail((HWND)this->winId(), hbm, 0);
DeleteObject(hbm);
}

困难总比办法多

即可?什么TM的叫TM的即可

想在Qt中使用这样一个冷门的Windows API可不容易

+头文件:dwmapi.h

Error:undefined function 缺少定义

What,不是加了头文件吗,一看,由于 _WIN32_WINNT 数值过低(版本不足),导致定义屏蔽

头文件中覆盖宏定义:

1
2
#define WINVER 0x0A00
#define _WIN32_WINNT 0x0A00 //Win10

Error:undefined reference 缺少引用(需要链接库)

+.pro:LIBS += -lDwmapi -lGdi32

这就完了?Nonono

缩略图大小是有限制的,Windows不会自动帮你缩放

那么如何知道大小限制

答:在Windows传递的native Message中有

但是很可惜 我们用了QWinThumbnailToolBar,导致事件被拦截,我们无法获取

什么叫寄人篱下

这时候就只能想点偷鸡摸狗的办法了

DwmSetIconicThumbnail函数的返回值可以标识调用是否成功

图片过大则不成功

Size从大到小开始遍历,直到返回S_OK

我滴任务,完成啦!啊哈哈哈哈

成品

TNND 跟我玩阴的是吧

遍历Size会导致耗时过长,导致缩略图刷新不及时,显示为空白(需要鼠标移动刷新)

根源还是在于获取不到maxSizeQt类获取NativeEventreturn true 拦截消息 不再传递)

所以还是得想办法以高优先级 获取NativeEvent

直接来吧

参考Qt源码

1
QCoreApplication::instance()->installNativeEventFilter(this);

要想以高优先级获取NativeEvent必须installNativeEventFilter并且重载nativeEventFilter函数

但要想重载这个函数,就必须实现QAbstractNativeEventFilter接口

C++中,采用多继承实现:

1
class Widget : public QWidget, public QAbstractNativeEventFilter

关于installNativeEventFilter

If multiple event filters are installed, the filter that was installed last is activated first.

也就是如果多个filter被安装,则后安装的先激活

我们要与QWinThumbnailBar竞争所以必须在其之后安装,也就是在它构造之后

1
2
QWinThumbnailToolBar* thumbbar = new QWinThumbnailToolBar(this);
qApp->installNativeEventFilter(this); //后安装filter的先获取nativeEvent 所以在↑构造之后

这样,在nativeEventFilter中就可以接收到请求缩略图和实时预览图的消息了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool Widget::nativeEventFilter(const QByteArray& eventType, void* message, long* result)
{
const MSG* msg = static_cast<const MSG*>(message);
switch (msg->message) {
case WM_DWMSENDICONICTHUMBNAIL: {
const QSize maxSize(HIWORD(msg->lParam), LOWORD(msg->lParam));//获取最大Size
setThumbnailPixmap(pixmap, maxSize);
}
break;
case WM_DWMSENDICONICLIVEPREVIEWBITMAP:
setLivePreviewPixmap(this->grab());//实时预览图 没有大小限制 直接截图
break;
}
return false;
}

原本的信号连接就可以删了,但是

1
thumbbar->setIconicPixmapNotificationsEnabled(true); //进行一些属性设置,否则不能设置缩略图

这句话还是得留着的↑

我滴任务 又完成啦 啊哈哈哈哈


ちょっと待って(2022.1.30)

既然是对类的修正 就应该封装为类

重点就是:

  • 继承QAbstractNativeEventFilter
  • 安装过滤器 QCoreApplication::instance()->installNativeEventFilter(this);
  • 重载nativeEventFilter并捕获WM_DWMSENDICONICTHUMBNAIL消息
  • 重写setIconicThumbnailPixmap,采用Windows API(DwmSetIconicThumbnail)

注:删去对WM_DWMSENDICONICLIVEPREVIEWBITMAP的捕获,没必要,直接用原生的即可

附:Qt源码

https://code.woboq.org/qt5/qtwinextras/src/winextras/qwinthumbnailtoolbar.cpp.html

附:QtWinThumbnailToolBar原理浅析(Qt源码)

Native事件交给单独的QWinThumbnailToolBarPrivate类(继承自QAbstractNativeEventFilter)处理,本体持有其指针

接收到Native事件后,开始更新缩略图(如果用setIconicThumbnailPixmap设置过的话)

emit iconicThumbnailPixmapRequested供用户更新缩略图

如果要调用Windows API手动设置缩略图 就不能调用setIconicThumbnailPixmap否则会被覆盖

setIconicLivePreviewPixmap不进行实际更新

实际更新在Native EventupdateIconicLivePreview()

所以我们在接收事件时必须往下传递,不能return true,否则没有代码去实现LivePreview

# 其实根本没有必要重写与拦截LivePreview事件,用原生的就行

# 重写Thumbail就行

Reference

任务栏扩展 - Win32 apps | Microsoft Docs

DwmSetIconicThumbnail 函数 (dwmapi.h) - Win32 应用 | 微软文档

Update WINVER and _WIN32_WINNT | Microsoft Docs

一个体验好的Windows 任务栏缩略图开发心得 - 网易数帆 - 博客园

C++ (Cpp) DwmSetIconicThumbnail Examples - HotExamples

qwinthumbnailtoolbar.cpp source code [qtwinextras/src/winextras/qwinthumbnailtoolbar.cpp] - Woboq Code Browser

c++ - Qt::nativeEvent 调用 - IT工具网

使用Qt接口获取Windows系统的事件_Qt君-CSDN博客

qt捕获全局windows消息_weixin_34179968的博客-CSDN博客


Qt-无边框窗口 设置任务栏缩略图
https://mrbeancpp.github.io/2021/12/23/Qt-无边框窗口-设置任务栏缩略图/
作者
MrBeanC
发布于
2021年12月23日
许可协议