DwmSetIconicThumbnail函数使用疑问及任务栏图标预览首次显示异常问题排查
看起来你碰到了DwmSetIconicThumbnail在首次任务栏预览时的适配坑,我之前做WPF窗口自定义任务栏预览时也踩过几乎一样的问题,咱们一步步拆解原因和解决办法。
问题核心原因分析
从你描述的现象(首次预览异常、第二次正常)和代码来看,主要有两个关键问题:
- 钩子注册时机过晚:首次触发任务栏预览时,你的
IconicThumbnailHook还没注册成功,导致系统发送的WM_DWMSENDICONICTHUMBNAIL消息没被处理,只能 fallback 到窗口默认的缩略图(也就是你说的“和第一章图状态一样”的情况);第二次预览时钩子已经生效,才能正常处理自定义位图。 - 尺寸适配逻辑强制正方形:你在处理
WM_DWMSENDICONICTHUMBNAIL时用Math.Min(width, height)生成正方形尺寸,这会强制拉伸非正方形的图片,导致预览层宽度和图片实际比例不匹配。
针对代码的具体修复建议
1. 提前注册钩子,避免首次预览无处理
你现在是在第一次调用SetIconicThumbnail时才注册钩子,这时候系统可能已经触发了预览消息。建议把钩子注册提前到窗口初始化完成的时机,比如在主窗口的Loaded事件里:
// 放在主窗口初始化的地方(比如App.xaml.cs或者MainWindow.xaml.cs的Loaded事件) AppCore.MainWindow.Loaded += (sender, e) => { var hwnd = new WindowInteropHelper(AppCore.MainWindow).Handle; var source = HwndSource.FromHwnd(hwnd); source?.AddHook(IconicThumbnailHook); _iconicThumbnailInitialized = true; // 这里可以直接标记为已初始化 };
然后在SetIconicThumbnail里去掉钩子注册的判断逻辑,简化代码。
2. 调整尺寸适配逻辑,保持图片原比例
把WM_DWMSENDICONICTHUMBNAIL里的正方形强制逻辑改成按比例缩放,适配系统请求的预览尺寸:
if (msg == WM_DWMSENDICONICTHUMBNAIL) { var width = (int)((((long)lParam) >> 16) & 0xffff); var height = (int)((long)lParam & 0xffff); IntPtr hBitmap = IntPtr.Zero; try { // 计算图片和请求尺寸的比例,保持原比例缩放 double imageRatio = (double)_iconicThumbnailImage.PixelWidth / _iconicThumbnailImage.PixelHeight; double requestRatio = (double)width / height; int adaptedWidth, adaptedHeight; if (imageRatio > requestRatio) { // 图片更宽,按请求宽度缩放 adaptedWidth = width; adaptedHeight = (int)Math.Round(width / imageRatio); } else { // 图片更高,按请求高度缩放 adaptedHeight = height; adaptedWidth = (int)Math.Round(height * imageRatio); } var adaptedSize = new System.Drawing.Size(adaptedWidth, adaptedHeight); hBitmap = CreateBitmap(_iconicThumbnailImage, adaptedSize); _ = Win32.DwmSetIconicThumbnail(hwnd, hBitmap, 0); } finally { Win32.DeleteObject(hBitmap); } handled = true; }
这样生成的位图会和图片原比例一致,不会出现宽度适配异常的问题。
3. 调整窗口属性设置与Invalidate的顺序
确保在设置FORCE_ICONIC_REPRESENTATION和HAS_ICONIC_BITMAP之后,再更新图片并调用DwmInvalidateIconicBitmaps。另外,建议把Interlocked.Exchange更新图片的操作放在属性设置之前,确保处理消息时用的是最新的图片:
private static void SetIconicThumbnail(BitmapImage image) { // 先更新图片,确保后续钩子处理时用的是最新值 var oldImage = Interlocked.Exchange(ref _iconicThumbnailImage, image); var hwnd = new WindowInteropHelper(AppCore.MainWindow).Handle; var size = Marshal.SizeOf<int>(); var pBool = Marshal.AllocHGlobal(size); try { Marshal.WriteInt32(pBool, 1); // 先设置HAS_ICONIC_BITMAP,再设置FORCE_ICONIC_REPRESENTATION _ = Win32.DwmSetWindowAttribute(hwnd, Win32.DwmWindowAttributes.HAS_ICONIC_BITMAP, pBool, size); _ = Win32.DwmSetWindowAttribute(hwnd, Win32.DwmWindowAttributes.FORCE_ICONIC_REPRESENTATION, pBool, size); } finally { Marshal.FreeHGlobal(pBool); } // 强制系统刷新缩略图 _ = Win32.DwmInvalidateIconicBitmaps(hwnd); }
调整属性设置的顺序,让系统先知道我们有自定义位图,再强制使用它,逻辑上更合理。
额外的排查点
如果还是有问题,可以检查你的CreateBitmap函数是否正确将BitmapImage转换为符合尺寸的GDI位图,比如是否正确处理了DPI缩放、像素格式转换。另外,在WM_DWMSENDICONICTHUMBNAIL处理完后,可以尝试调用DwmInvalidateIconicBitmaps再次刷新,但一般来说上面的修复已经能解决问题。
内容来源于stack exchange

