David
发布于 2014-07-22 / 11 阅读 / 0 评论 / 0 点赞

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中也可以只使用一句代碼即可实现鼠标拖动。