Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >正確使用 SetCapture ReleaseCapture [譯]「建议收藏」

正確使用 SetCapture ReleaseCapture [譯]「建议收藏」

作者头像
全栈程序员站长
发布于 2022-09-01 07:41:16
发布于 2022-09-01 07:41:16
53100
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

本文描述瞭如何正確處理自定義窗口和控件中的鼠標捕獲操作。

原文鏈接: http://www.codeproject.com/Tips/127813/Using-SetCapture…correctly.aspx 原作者: pasztorpisti 轉載請註明出處:http://www.imoldman.com/2010/11/30/ using-setcaptu…ture-correctly

鼠標捕獲是Windows的一項特性。即使光標不在某個窗口或者控件(帶HWND句柄)內,它也可以將所有的鼠標消息和該指定的窗口相關聯。現假定你已經熟悉了這項特性及其相關API(包括SetCapture()函數, ReleaseCapture()函數及WM_CAPTURECHANGED消息),在這裡,我想告訴你一項開發人員經常犯的錯誤。 有些窗口和控件為了完成某項操作,有時需要捕獲鼠標。spy++程序上的窗口選擇器(即那個十字)是一個很好的例子,它允許你拖動一個十字光標到你桌面的某個窗口上,這樣你就可以選中它。

該文給出的示例程序創建了一個窗口,你可以拖動它的客戶區來移動它。只要在DefWindowProc()響應WM_NCHITTEST消息時返回HTCLIENT,就可以達到這種效果,但是這樣主循環就不工作了,就好像是你在拖拽著它的標題欄一樣。某些程序(如媒體播放器,遊戲)通常自繪整個窗口,並且以該示例代碼中的方式提供拖拽窗口的功能。這樣做,程序的主循環可以一直在運作。這樣的窗口移動功能需要捕獲鼠標,那時因為如果你按下了鼠標左鍵,然後把鼠標從客戶區猛地拉到外面去,窗口仍然要能夠接收到WM_MOUSEMOVE消息。

那麼這種“抓著客戶區移動窗口”操作的執行步驟是怎樣的呢?

  1. WM_LBUTTONDOWN,開始拖拽操作並且捕獲鼠標。
  2. WM_MOUSEMOVE,隨著光標一起移動窗口。
  3. WM_LBUTTONUP,僅僅釋放捕獲的鼠標。
  4. WM_CAPTURECHANGED,結束拖拽操作。

這篇文章的重點就是你要在WM_LBUTTONUPWM_CAPTURECHANGED消息響應中處理什麼。這裡重要的一點就是WM_LBUTTONUP僅僅釋放鼠標,只有WM_CAPTURECHANGED才會中止拖拽操作!!!之所以這樣,原因在於拖拽操作可以被其他操作終止掉,比如說你按了ALT+TAB組合鍵。這種情況下,你的程序根本接收不到WM_LBUTTONUP消息,但是仍然失去了鼠標捕獲,此時,窗口會接收到WM_CAPTURECHANGED消息,於是整個拖拽操作結束。

如果你把“拖拽操作結束”的相關代碼放在了WM_LBUTTONUP消息響應裡,一旦用戶按了ALT+TAB,你的程序會失去鼠標捕獲,此時另外一個窗口會成為前景窗口,你整個窗口的邏輯狀態就會亂掉,因為它還認為它是在拖動過程中。

所以結論就是,總是將操作結束的處理代碼放在WM_CAPTURECHANGED消息響應裡,並且在其他你想結束操作的地方調用ReleaseCapture(),這可以發生在任何地方,比如在WM_LBUTTONUP消息響應函數中,在WM_MOUSEMOVE消息響應函數中,等等。

編譯運行我給出的代碼,在拖拽主窗口客戶區的過程中,使用ALT+TAB按鍵將一個大些的窗口提到前面,這樣示例程序的主窗口就會全部被蓋住。之後釋放鼠標按鍵並且切回到示例程序,將鼠標在示例窗口上移動,一切正常。然後將WM_LBUTTONUPWM_CAPTURECHANGED消息處理代碼註釋掉,並且去掉有問題的代碼的註釋,再按照步驟試試ALT+TAB!這次,在ALT+TAB切換窗口、釋放鼠標按鍵並且使用ALT+TAB再切回我們的窗口後,將鼠標在示例窗口上移動,並且嘗試很快的速度移動光標,此時你會發現窗口的行為很瘋狂,除非你在窗口上單擊一下給它發個WM_LBUTTONUP消息,它才能回歸正常。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <windows.h>

HINSTANCE g_hInstance = (HINSTANCE)GetModuleHandle(NULL);
HWND g_hMainWnd = NULL;
bool g_MovingMainWnd = false;
POINT g_OrigCursorPos;
POINT g_OrigWndPos;
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_LBUTTONDOWN:
		// here you can add extra check and decide whether to start
		// the window move or not
		if (GetCursorPos(&g_OrigCursorPos))
		{
			RECT rt;
			GetWindowRect(hWnd, &rt);
			g_OrigWndPos.x = rt.left;
			g_OrigWndPos.y = rt.top;
			g_MovingMainWnd = true;
			SetCapture(hWnd);
		}
		return 0;
	// THE RIGHT WAY OF DOING IT:
	//*
	case WM_LBUTTONUP:
		ReleaseCapture();
		return 0;
	case WM_CAPTURECHANGED:
		g_MovingMainWnd = (HWND)lParam == hWnd;
		return 0;
	/**/
	// THE WRONG WAY OF DOING IT:
	/*
	case WM_LBUTTONUP:
		g_MovingMainWnd = false;
		ReleaseCapture();
		return 0;
	// buggy programs usually do not handle WM_CAPTURECHANGED at all
	case WM_CAPTURECHANGED:
		break;
	/**/
	case WM_MOUSEMOVE:
		if (g_MovingMainWnd)
		{
			POINT pt;
			if (GetCursorPos(&pt))
			{
				int wnd_x = g_OrigWndPos.x +
				  (pt.x - g_OrigCursorPos.x);
				int wnd_y = g_OrigWndPos.y +
				  (pt.y - g_OrigCursorPos.y);
				SetWindowPos(hWnd, NULL, wnd_x,
				  wnd_y, 0, 0, SWP_NOACTIVATE|
				  SWP_NOOWNERZORDER|SWP_NOZORDER|
				  SWP_NOSIZE);
			}
		}
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
bool CreateMainWnd()
{
	static const char CLASS_NAME[] = "MainWndClass";
	WNDCLASS wc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hInstance = g_hInstance;
	wc.lpfnWndProc = &MainWndProc;
	wc.lpszClassName = CLASS_NAME;
	wc.lpszMenuName = NULL;
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
	if (!RegisterClass(&wc))
		return false;
	g_hMainWnd = CreateWindowEx(
		0,
		CLASS_NAME,
		"Main Window",
		WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
		CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
		NULL,
		NULL,
		g_hInstance,
		NULL
		);
	return true;
}
int main()
{
	if (!CreateMainWnd())
		return -1;
	ShowWindow(g_hMainWnd, SW_SHOW);
	UpdateWindow(g_hMainWnd);
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;

ps:這篇文章是昨天看到的,當時感覺寫的挺好的(其實主要是因為短

),所以想拿來翻譯下,一來鍛煉下自己的英語水平,二來充實下自己的blog,跑去CPOL看了下,沒有提到翻譯相關的內容,然後給作者留了言,問他能不能翻譯下,人家說可以,不過你還是問下CodeProject的工作人員吧,然後屁顛屁顛的跑去問,結果被告知他們沒權利允許我翻譯拿來貼自己的blog上,因為文章的版權是原作者的,我想這好辦了,原作者都同意了,跟人家說下然後開始翻譯吧,結果在作者的About頁面愣是沒找到他的Email。於是在沒有得到最終許可的情況下,我翻譯了這篇文章,想來應該不會違反License的,如果需要轉載記得保留開始三個鏈接!

就這樣吧,謝謝原作者pasztorpisti和CodeProjcet平台

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/141917.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Rust日报】 2019-09-09:glint 用Rust編寫的commit 提示工具
然後再用 Linux perf 跑看看,發現分析結果與 cargo-profiler 相同
MikeLoveRust
2019/09/11
4200
【Rust日报】 2019-09-09:glint 用Rust編寫的commit 提示工具
在Jetson上玩转大模型Day7:執行RAG功能的Jetson Copilot
爲了要協助處理這種現象,目前最流行的方法就是使用俗稱“檢索增強生成”的RAG(Retrieval-Augmented Generation)技術,爲手上的大語言模型助手提供補強或微調的功能。
GPUS Lady
2024/10/31
2140
在Jetson上玩转大模型Day7:執行RAG功能的Jetson Copilot
RESTful
HTTP 状态码(HTTP Status Code)是用以表示网页服务器 HTTP 响应状态的 3 位数字代码。所有状态码的第一个数字代表了响应的五种状态之一。 除非另有说明,状态码是 HTTP / 1.1 标准(RFC 7231)的一部分。
Cell
2022/02/25
8670
【Rust日报】 2019-08-30 - Linux 未來可以使用 Rust 開發內核
Josh Triplett (Linux主要開發者之一)在一次的演講提到了Rust的可能性,
MikeLoveRust
2019/09/03
6050
Using Automake and Autoconf「建议收藏」
Murray Cumming <murrayc@usa.net> Chen Chih-Chia <pigfoot@CDPA.nsysu.edu.tw>
全栈程序员站长
2022/07/25
4510
RN之回調函數-百步九折縈巖巒
函數可以稱得上是編程語言的靈魂所在之處,它就像是一個個工廠,總能創造出開發者需要的效果。本文就將介紹RN中回調函數的四種寫法。 方法一:使用箭頭函數指向回調。這種寫法就不需要bind函數來綁定。{(newText)this.updateNum=>this.updateNum(newText)}這句代碼的意思是在花括號中有一個箭頭符號定義的函數,它將收到的字符串為參數調用本類組件的updateNum函數,并將該函數的返回值返回。 //構造函數 constructor(props) { super(p
谦谦君子修罗刀
2018/05/02
6640
計算機程序設計:7大編程原則
編程的工作同石匠的工作相類似,即是技術活,也是體力活,而編寫優秀的軟件,算是一件比較難的事。編程大牛們並不是直接上手編寫,而是根據需求進行設計,不但將代碼中 Bug 出現的機率降到最低,還要讓代碼具有高可讀性,高安全性等等。
一个会写诗的程序员
2020/09/01
6180
【Rust日报】 2020-01-10 track_caller 錯誤處理大突破
use anyhow + thiserror in place of failure
MikeLoveRust
2020/02/12
5720
【Rust日报】 2019-10-25 例子學習:基於Autoref的穩定特化
然後再將其上傳到數據庫,並且要在/opt/docs-rs-prefix/documentations目錄中進行。
MikeLoveRust
2019/10/31
4360
分析 WordPress 3.8.2 修復的cookie偽造漏洞
4月8日,wordpress發布了一個重要更新,在該次更新中,修復了一系列安全漏洞。其中最顯眼的就是cookie伪造漏洞(CVE -2014- 0166)。 我們來看修補的代碼: $key = wp_hash($username . $pass_frag . '|' . $expiration, $scheme); $hash = hash_hmac('md5', $username . '|' . $expiration, $key); - if ( $hmac != $hash ) { + if (
FB客服
2018/02/02
8490
分析 WordPress 3.8.2 修復的cookie偽造漏洞
linux 信号sigabrt,關於Linux中的SIGABRT信號
SIGABRT是中止一個程序,它可以被捕捉,但不能被阻塞。處理函數返回后,所有打開的文件描述符將會被關閉,流也會被flush。程序會結束,有可能的話還會core dump。 當程序調用abort(3)時,該進程會向自己發送SIGABRT信號。所以,SIGABRT一般用於信號中一些關鍵的處理,assert失敗時也會使用它。你不應該去捕捉SIGSEGV和SIGABRT信號,如果收到這種信號,說明進程處於一個不確定的狀態,很可能會直接掛起。
全栈程序员站长
2022/07/23
3K0
解密特斯拉自動駕駛晶片背後的一號人物
源自:虎嗅網 作者:國仁 在自動駕駛領域,AI晶片其實並非新奇玩意。Mobileye的EyeQ系列晶片,就是較早應用於自動駕駛的AI晶片之一。 而在這個月早些時候,矽谷鋼鐵俠馬斯克,還宣佈了特斯拉正在研發用於自動駕駛的AI晶片,回顧馬斯克對自動駕駛晶片的各種動作,我們發現近來各種跳票“不靠譜”的他,其實對技術發展的趨勢,遠比看上去更理解。 馬斯克對特斯拉自行研發自動駕駛晶片的佈局,從兩年前就開始了。 一、從Mobileye到英偉達 特斯拉的自動駕駛焦慮症 2014年,特斯拉找上Mobileye,用上了它
企鹅号小编
2018/01/25
9720
第一性原理 《禅与计算机程序设计艺术》 / 陈光剑
第一性原理(First Principle Thinking),指的是回歸事物最基本的條件,將其拆分成各要素進行解構分析,從而找到實現目標最優路徑的方法。
一个会写诗的程序员
2021/04/20
1.2K0
第一性原理 《禅与计算机程序设计艺术》 / 陈光剑
web 开发规则,代码规范
參數取得需透過 filter_input 函數取得,不得使用 _GET、 _POST
Cell
2022/02/25
7970
C#-笔记-面向对象-第一章
我們把這些具有相同屬性和相同的方法的對象進一步的封裝,抽象出來類這個概念,類就是模子,確定了對象應該具有的屬性和方法。 對象是根據類創建出來的。
用户9857551
2022/06/28
3320
C#-笔记-面向对象-第一章
在Jetson上玩转大模型Day7:執行RAG功能的Jetson Copilot
在前面的大語言模型測試過程,一定會發現這些智能助手都存在一些“胡言亂語”、“答非所問”之類的狀況,其實可以將它視爲“不瞭解邊界”的小孩童一樣,因爲所有人工智能模型,都是再它的知識庫中,尋找“機率最高”的答案,很有可能這個答案的正確率還不到5%,但它不能回答“不知道”,必須找到一組以上的回覆(response),即便其正確率並不高。
GPUS Lady
2024/10/29
1640
在Jetson上玩转大模型Day7:執行RAG功能的Jetson Copilot
【2022】最新下載 YouTube 影片方法
在觀看油管視頻時,有一些精彩的視頻或者音訊,你一定會有想要保存到你的設備中的想法,在本文就將告訴你2022年依舊有效的方法,無需下載app,即可線上輕鬆完成下載Youtube視頻的5種方法。 如果你要求比較高,甚至有些網站可以下載高清視頻,當然如果你想要下載YouTube字幕甚至是MP3音訊也是可以的。 當然我們盡可能介紹支援中文介面的,方便大家使用。
用户1385557
2022/11/29
2.8K0
區塊鏈在網絡安全中的應用
通過數字化日常生活的各個方面逃離現實世界危險的人們開始意識到,與離線相比,互聯網不再是一個安全的地方。在2018年,我們看到了網絡犯罪分子造成的許多災難性故障,看起來在即將到來的一年中,我們將會有更多的故障。 幸運的是,黑客並不是唯一一個正在開發其電力庫的人 - 多年來,網絡安全中的區塊鏈一直在增長,並在所有可能的行業中得到開發。儘管該技術僅用於存儲加密貨幣比特幣,但其影響和影響現在已經擴散到更多不同的領域。在本文中,我們將討論Blockchain在2019年的含義,為您提供安全性中Blockchain的一些用例。
用户4624829
2019/02/15
3690
主动学习与被动学习
美國學者艾德格‧戴爾(Edgar Dale)提出了「學習金字塔」(Cone of Learning)的理論:在初次學習兩個星期後,透過閱讀學習能夠記住內容的10%;透過聽講學習能夠記住內容的20%;透過圖片學習能夠記住內容的30%;透過影像、展覽、示範、現場觀摩來學習能夠記住50%;參與討論、提問、發言來學習能夠記住70%;做報告、教學、模擬體驗、實際操作能夠記住90%。美國緬因州國家訓練實驗室(National Training Laboratories)做過類似的研究,結論跟戴爾差不多。由此可知,閱讀是最沒用的學習方式,而模擬、體驗與實作才是最好的學習方式。是嗎?
超蛋lhy
2018/08/31
1.8K0
主动学习与被动学习
【Rust日报】 2019-12-20 Serverless - Rust 使用 WASM 加 Cloudflare
#[non_exhaustive] structs, enums, and variants
MikeLoveRust
2019/12/25
8050
相关推荐
【Rust日报】 2019-09-09:glint 用Rust編寫的commit 提示工具
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验