C# API拖動任意控件
公司最近在做PowerBuilder轉.Net的工作,各類報錯和BUG就不說了,上班時間緊迫,入正題。
事情是這樣的:
PB裏有一個自定義控件(名為ST,轉到C#也叫ST),這個控件其實是一個Label,用來分開倆個相鄰控件并實現拖動的時候改變相鄰控件的大小。這個功能上來說跟.Net裏的SplitContainer控件是相同的。
ST的如何改變其他控件大小就不提了,關鍵是鼠標拖動ST的時候。
首先說PB的情況吧(得先說明情況)
在ST鼠標左鍵按下(MousrDown)的時候,就會調用SendMessage發送消息給ST(自己發給自己),代碼:
Send(handle(this), 274, 61458, 0)
Send = SendMessage
handle(this) = 當前控件的句柄
274 = WM_SYSCOMMAND = 0x112
61458 = SC_MOVE = 0xf012
這裡僅僅寫了一行代碼,發送了一個消息,就實現拖動的效果。
Amazing……….
在MouseDown,MouseMove,MouseUp裏寫各種移動代碼都弱爆了。
驚歎完了,在看看轉成C#的坑爹代碼
在ST鼠標拖放事件裏(由於各種原因,PB裏拖放處理是關聯到MouseDown事件的,到C#就直接成了MouseDrag事件)
SendMessage(this.hwnd, WM_SYSCOMMAND, SC_MOVE, 0);
然後就是……..拖動沒反應,經過一輪百度、谷歌搜索資料。
我們把上面SendMessage放到MouseDown裏,失敗,放到MouseMove,MouseUp也不行。
瞬間心情低落……迷失在浩瀚的代碼海裏。(按照一般劇情,這個時候我們會遇到新的希望)
有篇文章提到使用這句代碼
SendMessage(hwnd, WM_LBUTTONUP, 0, 0);
WM_LBUTTONUP = 0x202
我們試著把下面兩句代碼放在MouseMove裏,結果令人振奮啊,它終於動了…….
SendMessage(pictureBox1.Handle, WM_SYSCOMMAND, SC_MOVE, 0); SendMessage(pictureBox1.Handle, WM_LBUTTONUP, 0, 0);
這裡說個題外話,我們把上面兩句調換了執行順序,然後放在MouseDown裏也可以正常執行
趕緊向上級報告。
“嗯~我們不僅僅要找出解決方法,還要找到為什麼要加多一句。.Net在處理鼠標事件的時候肯定做了些處理,你們可能要去反編譯下.Net的源碼,看下其中做了哪些處理,把結果寫個報告給我。”上級再次下達指令。
找到解決方法 – 支線任務【完成】
找到原因 – 主線任務【未完成】【新任務】
不經不覺一個上午已經過去了,吃飯先,星期一到星期五加班沒工資。
時間來到第二天早上,昨天下午一直在看反編譯出來的源碼。
我們在System.Windows.Control裏找到一個似乎很關鍵的東西
internal bool CaptureInternal
由於這東東屬於內部,非公開的,無法從外部直接改變他的值,我們不得不用反射來改變。
Type t = this.ST.GetType(); PropertyInfo property = t.GetProperty("CaptureInternal",BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic); object obj = property.GetValue(ST, null);// 查看 原始值 property.SetValue(ST, false, null);// 设置为 false obj = property.GetValue(ST, null);// 查看 设置后的值 SendMessage(this.ST.Handle, WM_SYSCOMMAND, SC_MOVE, 0);
現在把上面代碼放到控件的MouseDown,要注意上面也只發送了一次消息。
哈哈….還有誰,成功拖動,我們的目的不僅僅是這個。
來看看CaptureInternal內部都做哪些操作。
internal bool CaptureInternal { [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] get { return (this.IsHandleCreated && (UnsafeNativeMethods.GetCapture() == this.Handle)); } set { if (this.CaptureInternal != value) { if (value) { UnsafeNativeMethods.SetCapture(new HandleRef(this, this.Handle)); } else { SafeNativeMethods.ReleaseCapture(); } } } }
嗯…..關鍵在下面這句,趕緊百度下SetCapture
UnsafeNativeMethods.SetCapture(new HandleRef(this, this.Handle));
函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获。一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。同一时刻只能有一个窗口捕获鼠标。如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向指定的窗口。
结论: 这是由于设置鼠标捕获后,系统无法再次实现捕获鼠标拖动的控件后果。我們取消掉捕获在当前进程后,就可以只使用一句代碼,就可以实现鼠标拖动。反过来,也就是说,PB中是没有设置鼠标捕获为当前的进程,所以在PB中也可以只使用一句代碼即可实现鼠标拖动。