做窗口预览时,定时截图再刷新图片虽然直观,但代码量、刷新频率和资源占用都要自己处理。Windows 的 DWM 缩略图接口可以把一个现有窗口的画面实时合成到另一个窗口中,DWMThumbnail_v1 就是把这套接口整理成 AHK v1 可直接使用的类。
这个库源自 thqby 的实现,由 dbgba 移植到 AHK v1。它不需要额外 DLL,核心依赖是系统自带的 dwmapi.dll。
它能完成什么
- 为指定窗口创建一个独立的实时预览窗口。
- 按源窗口比例缩放预览,并随预览窗口尺寸变化自动调整。
- 设置预览透明度,只显示客户区,或临时显示、隐藏缩略图。
- 查询源窗口尺寸,并通过
updateProperties()进一步修改 DWM 缩略图属性。
创建对象时可以传窗口句柄,也可以传 AHK 能识别的窗口条件。类内部会保存同一源窗口对应的缩略图对象,避免重复注册。
主要方法怎么理解
new DWMThumbnail(hwnd) 负责注册并显示预览;setOpacity() 接收 0 到 255 的透明度;onlyClientArea(true) 可以排除标题栏和边框;show()、hide() 控制显示状态;不用时调用 Destroy() 释放 DWM 缩略图和 GUI。
示例先创建一个蓝色源窗口,再为它创建实时缩略图。这样不依赖其他软件,适合先确认系统和库能正常工作。
示例代码:DWMThumbnail_v1_示例.ahk
库源码:DWMThumbnail_v1.ahk
; AutoHotkey v1 port of DWMThumbnail.ahk.
; Original author: thqby (https://github.com/thqby/ahk2_lib)
; Ported to AutoHotkey v1 by: dbgba
class DWMThumbnail {
static thumbnails := {}
Call(hwnd) {
hwnd := DWMThumbnail._ResolveHwnd(hwnd)
if (!hwnd)
throw Exception("Invalid source window.", -1)
if (DWMThumbnail.thumbnails.HasKey(hwnd)) {
thumbnail := DWMThumbnail.thumbnails[hwnd]
if IsObject(thumbnail)
return thumbnail
}
return new DWMThumbnail(hwnd)
}
__New(hwnd) {
DWMThumbnail._EnsureDwmApi()
hwnd := DWMThumbnail._ResolveHwnd(hwnd)
if (!hwnd)
throw Exception("Invalid source window.", -1)
this.sourceHwnd := hwnd
this.ptr := 0
this._closed := false
this.guiName := "DWMThumbnail_v1_" hwnd "_" A_TickCount "_" A_Now
guiName := this.guiName
DWMThumbnail._EnsureMessageHooks()
Gui, %guiName%:New, +AlwaysOnTop +E0x08000000 +ToolWindow +Resize -DPIScale +HwndhGui
this.guiHwnd := hGui
thumbnail := 0
hr := DllCall("dwmapi\DwmRegisterThumbnail", "Ptr", hGui, "Ptr", hwnd, "Ptr*", thumbnail, "Int")
if (hr) {
Gui, %guiName%:Destroy
throw DWMThumbnail._Error(hr, -1)
}
this.ptr := thumbnail
DWMThumbnail.thumbnails[hwnd] := this
guiMap := DWMThumbnail._GuiMap()
guiMap[hGui] := this
guiMap[guiName] := this
this.querySourceSize(w, h)
showW := Round(w / 2)
showH := Round(h / 2)
if (showW < 1)
showW := 1
if (showH < 1)
showH := 1
Gui, %guiName%:Show, % "w" showW " h" showH " NA", DWMThumbnail
this.show()
}
__Delete() {
this.Destroy()
}
Destroy() {
if (this._closed)
return
this._closed := true
if (this.ptr) {
DllCall("dwmapi\DwmUnregisterThumbnail", "Ptr", this.ptr, "Int")
this.ptr := 0
}
if (this.sourceHwnd && DWMThumbnail.thumbnails.HasKey(this.sourceHwnd))
DWMThumbnail.thumbnails.Delete(this.sourceHwnd)
guiMap := DWMThumbnail._GuiMap()
if (this.guiHwnd && guiMap.HasKey(this.guiHwnd))
guiMap.Delete(this.guiHwnd)
if (this.guiName != "" && guiMap.HasKey(this.guiName))
guiMap.Delete(this.guiName)
if (this.guiName != "") {
guiName := this.guiName
Gui, %guiName%:Destroy
this.guiName := ""
}
}
querySourceSize(ByRef width, ByRef height) {
VarSetCapacity(size, 8, 0)
hr := DllCall("dwmapi\DwmQueryThumbnailSourceSize", "Ptr", this.ptr, "Ptr", &size, "Int")
if (hr)
throw DWMThumbnail._Error(hr, -1)
width := NumGet(size, 0, "Int")
height := NumGet(size, 4, "Int")
}
updateProperties(properties) {
if !IsObject(properties)
throw Exception("properties must be an object.", -1)
VarSetCapacity(buf, 48, 0)
flags := 0
for k, v in properties {
key := k
StringLower, key, key
if (key = "rcdestination" || key = "dest" || key = "d") {
flags |= 1
DWMThumbnail._PutRect(buf, 4, v)
} else if (key = "rcsource" || key = "src" || key = "s") {
if IsObject(v) {
flags |= 2
DWMThumbnail._PutRect(buf, 20, v)
}
} else if (key = "opacity" || key = "o") {
flags |= 4
NumPut(v, buf, 36, "UChar")
} else if (key = "fvisible" || key = "vis" || key = "v") {
flags |= 8
NumPut(v ? 1 : 0, buf, 40, "Int")
} else if (key = "fsourceclientareaonly" || key = "client" || key = "c") {
flags |= 16
NumPut(v ? 1 : 0, buf, 44, "Int")
}
}
NumPut(flags, buf, 0, "UInt")
hr := DllCall("dwmapi\DwmUpdateThumbnailProperties", "Ptr", this.ptr, "Ptr", &buf, "Int")
if (hr)
throw DWMThumbnail._Error(hr, -1)
}
show(dst := "", src := "") {
if !IsObject(dst) {
DWMThumbnail._GetClientSize(this.guiHwnd, w, h)
dst := [0, 0, w, h]
}
guiName := this.guiName
Gui, %guiName%:Show, NA
this.updateProperties({v: 1, d: dst, s: src})
}
hide() {
this.updateProperties({v: 0})
guiName := this.guiName
Gui, %guiName%:Hide
}
onlyClientArea(client := true) {
this.updateProperties({c: client})
}
setOpacity(opacity) {
this.updateProperties({o: opacity})
}
_OnGuiSize(id, minMax, width, height) {
guiMap := DWMThumbnail._GuiMap()
if (!guiMap.HasKey(id))
return
thumbnail := guiMap[id]
if (!IsObject(thumbnail) || !thumbnail.ptr || width = "" || height = "" || width <= 0 || height <= 0)
return
DWMThumbnail._UpdateToClientSize(thumbnail.ptr, width, height)
}
_OnGuiClose(id) {
guiMap := DWMThumbnail._GuiMap()
if (!guiMap.HasKey(id))
return
thumbnail := guiMap[id]
if IsObject(thumbnail)
thumbnail.Destroy()
}
_UpdateToClientSize(thumbnail, width, height) {
VarSetCapacity(buf, 48, 0)
NumPut(1, buf, 0, "UInt")
VarSetCapacity(size, 8, 0)
hr := DllCall("dwmapi\DwmQueryThumbnailSourceSize", "Ptr", thumbnail, "Ptr", &size, "Int")
if (!hr) {
w := NumGet(size, 0, "Int")
h := NumGet(size, 4, "Int")
if (w > 0 && h > 0 && w * height > width * h) {
h0 := Round(h * width / w)
h1 := Round((height - h0) / 2)
DWMThumbnail._PutRect(buf, 4, [0, h1, width, h0 + h1])
} else if (w > 0 && h > 0) {
w0 := Round(w * height / h)
w1 := Round((width - w0) / 2)
DWMThumbnail._PutRect(buf, 4, [w1, 0, w0 + w1, height])
} else {
DWMThumbnail._PutRect(buf, 4, [0, 0, width, height])
}
} else {
DWMThumbnail._PutRect(buf, 4, [0, 0, width, height])
}
DllCall("dwmapi\DwmUpdateThumbnailProperties", "Ptr", thumbnail, "Ptr", &buf, "Int")
}
_PutRect(ByRef buf, offset, rc) {
NumPut(rc[1], buf, offset + 0, "Int")
NumPut(rc[2], buf, offset + 4, "Int")
NumPut(rc[3], buf, offset + 8, "Int")
NumPut(rc[4], buf, offset + 12, "Int")
}
_GetClientSize(hwnd, ByRef width, ByRef height) {
VarSetCapacity(rc, 16, 0)
DllCall("GetClientRect", "Ptr", hwnd, "Ptr", &rc, "Int")
width := NumGet(rc, 8, "Int") - NumGet(rc, 0, "Int")
height := NumGet(rc, 12, "Int") - NumGet(rc, 4, "Int")
}
_ResolveHwnd(hwnd) {
if hwnd is integer
return hwnd + 0
return WinExist(hwnd)
}
_GuiMap() {
static guiMap := {}
return guiMap
}
_EnsureMessageHooks() {
static hooked := false
if (hooked)
return
hooked := true
OnMessage(0x0005, "DWMThumbnail_v1_WM_SIZE")
OnMessage(0x0010, "DWMThumbnail_v1_WM_CLOSE")
}
_EnsureDwmApi() {
static hDwmApi := 0
if (!hDwmApi)
hDwmApi := DllCall("LoadLibrary", "Str", "dwmapi.dll", "Ptr")
if (!hDwmApi)
throw Exception("LoadLibrary failed for dwmapi.dll.", -1, A_LastError)
return hDwmApi
}
_Error(hr, what := -1) {
return Exception("DWM call failed.", what, Format("0x{:08X}", hr & 0xFFFFFFFF))
}
}
; GUI messages are handled with functions instead of labels so this file can be included at the top of a v1 script.
DWMThumbnail_v1_WM_SIZE(wParam, lParam, msg, hwnd) {
guiMap := DWMThumbnail._GuiMap()
if (!guiMap.HasKey(hwnd))
return
width := lParam & 0xFFFF
height := (lParam >> 16) & 0xFFFF
DWMThumbnail._OnGuiSize(hwnd, wParam, width, height)
}
DWMThumbnail_v1_WM_CLOSE(wParam, lParam, msg, hwnd) {
guiMap := DWMThumbnail._GuiMap()
if (!guiMap.HasKey(hwnd))
return
DWMThumbnail._OnGuiClose(hwnd)
return 0
}
使用边界
DWM Thumbnail 是系统合成出来的实时预览,不是截图文件,因此不能直接拿到像素数据做保存、识别或编辑。源窗口也必须仍然存在;源窗口关闭后,应及时销毁对应对象。
它更适合窗口监看、小窗预览、GUI 中展示其他程序状态。需要真正截屏、离屏取图或图像识别时,应改用截图类库或 WinCapture 一类方案。
声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。

评论(0)