前情提要
任务栏缩略图可以在不激活窗口的状态下提供窗口的缩略信息
对于音乐播放器等软件,尤其重要,不仅可以增加观感,还能添加控制按钮
是专业程序员的不二之选
且
都1202年了,不会还有人不用无边框窗口(Framless)吧
でも
Qt自带的QWinThumbnailToolBar类是可以很方便地进行任务栏缩略图的控制
但是,对于无边框窗口,貌似出现了一些问题:
- 缩略图周围出现了[类似标题栏的边框] 导致图片错位 十分影响美观
在仔细阅读了Qt文档后,并没有什么收获,该类十分简洁,并无多于函数可供操作
焯
止まるんじゃねよ
不要停下来啊 团长!
毕竟Qt还是封装的Windows API
所以还得从Windows API文档入手
在搜寻了关于TaskBar的内容后,我找到了这样一个函数:DwmSetIconicThumbnail
这个函数与Qt-QWinThumbnailToolBar::setIconicThumbnailPixmap不能说很像,简直就是同父异母
DwmSetIconicThumbnail
HRESULT DwmSetIconicThumbnail(
[in] HWND hwnd,
[in] HBITMAP hbmp,
[in] DWORD dwSITFlags
);
重点在于最后一个参数 dwSITFlags:
- 0 (0x00000000) - 提供的缩略图周围不显示任何框架
- DWM_SIT_DISPLAYFRAME (0x00000001) - 在提供的缩略图周围显示一个框架
主要就在于这个参数中的框架二字,联想我们之前出现的错位情况,那可不就是多了一个框架嘛
那么只要将参数设置为0,不就可以解决了嘛
根源
可是,Qt 为什么会出现这样的问题,难道是!
说罢,我便去查阅了Qt源码:
やはり啊
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行:
const HRESULT hr = DwmSetIconicThumbnail(message->hwnd, bitmap, dWM_SIT_DISPLAYFRAME);
最后一个参数 写死了 dWM_SIT_DISPLAYFRAME
导致强制产生边框,与无边框窗口 产生冲突
而且还不提供接口修改参数
Qt程序员也太不走心了吧
Solution
那咋办
其实哈,与Windows API打交道是很烦人的一件事
所以,能少写就少写,尽量用Qt封装的类
だから、我们将采用Qt API与Windows API结合的方式 完成缩略图
毕竟 就只有setIconicThumbnailPixmap这一个函数写得不好嘛
我们只要用DwmSetIconicThumbnail代替它即可
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 数值过低(版本不足),导致定义屏蔽
头文件中覆盖宏定义:
#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会导致耗时过长,导致缩略图刷新不及时,显示为空白(需要鼠标移动刷新)
焯
根源还是在于获取不到maxSize(Qt类获取NativeEvent后return true 拦截消息 不再传递)
所以还是得想办法以高优先级 获取NativeEvent
直接来吧
参考Qt源码
QCoreApplication::instance()->installNativeEventFilter(this);
要想以高优先级获取NativeEvent必须installNativeEventFilter并且重载nativeEventFilter函数
但要想重载这个函数,就必须实现QAbstractNativeEventFilter接口
在C++中,采用多继承实现:
class Widget : public QWidget, public QAbstractNativeEventFilter
关于installNativeEventFilter
If multiple event filters are installed, the filter that was installed last is activated first.
也就是如果多个filter被安装,则后安装的先激活
我们要与QWinThumbnailBar竞争所以必须在其之后安装,也就是在它构造之后
QWinThumbnailToolBar* thumbbar = new QWinThumbnailToolBar(this);
qApp->installNativeEventFilter(this); //后安装filter的先获取nativeEvent 所以在↑构造之后
这样,在nativeEventFilter中就可以接收到请求缩略图和实时预览图的消息了
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;
}
原本的信号连接就可以删了,但是
thumbbar->setIconicPixmapNotificationsEnabled(true); //进行一些属性设置,否则不能设置缩略图
这句话还是得留着的↑
我滴任务 又完成啦 啊哈哈哈哈
ちょっと待って(2022.1.30)
既然是对类的修正 就应该封装为类
重点就是:
- 继承
QAbstractNativeEventFilter - 安装过滤器
QCoreApplication::instance()->installNativeEventFilter(this); - 重载
nativeEventFilter并捕获WM_DWMSENDICONICTHUMBNAIL消息 - 重写
setIconicThumbnailPixmap,采用Windows API(DwmSetIconicThumbnail)
注:删去对WM_DWMSENDICONICLIVEPREVIEWBITMAP的捕获,没必要,直接用原生的即可
附:Qt源码
附:QtWinThumbnailToolBar原理浅析(Qt源码)
Native事件交给单独的QWinThumbnailToolBarPrivate类(继承自QAbstractNativeEventFilter)处理,本体持有其指针
接收到Native事件后,开始更新缩略图(如果用setIconicThumbnailPixmap设置过的话)
并emit iconicThumbnailPixmapRequested供用户更新缩略图
如果要调用Windows API手动设置缩略图 就不能调用setIconicThumbnailPixmap否则会被覆盖
setIconicLivePreviewPixmap不进行实际更新
实际更新在Native Event时updateIconicLivePreview()
所以我们在接收事件时必须往下传递,不能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/winextr…
c++ - Qt::nativeEvent 调用 - IT工具网