做窗口预览时,定时截图再刷新图片虽然直观,但代码量、刷新频率和资源占用都要自己处理。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 一类方案。

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。