利用SendMessage實現窗口拖動

                                    利用SendMessage實現窗口拖動
                                           周銀輝

想想以前用跟蹤鼠標位移的方式來實現窗口拖動的方式還真有些傻, 後來, .Net3.0以來的Window類內置了DragMove方法, 似乎讓我們方便的不少, 但, 最近這個方法也不能滿足需求了, 因爲我需要DragMove過程中向外發事件來通知我"拖動開始了"和"拖動結束了", 可惜的是Window類沒有提供者兩個事件 (也曾企圖通過其他方式來得到通知, 比如監視MouseUp等, 效果不好).
所以就自己來實現窗口拖動吧
不必同監視鼠標位移手動更新窗口位置, 其實通過向窗口發送SC_MOVE命令來移動窗口就可以了,這個命令會幫我們完成位置計算和更新工作:
        public const int SC_MOVE = 0xf012;
        
public const int WM_SYSCOMMAND = 0x112;
        
public const int WM_LBUTTONUP = 0x202;

        [DllImport(
"user32.dll", CharSet = CharSet.Auto)]
        
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        
private static void DragAndMoveInner(IntPtr hwnd)
        {
            OnDragAndMoveStarted(hwnd);

            
SendMessage(hwnd, WM_SYSCOMMAND, (IntPtr)SC_MOVE, IntPtr.Zero);
            
SendMessage(hwnd, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);

            OnDragAndMoveEnded(hwnd);
        }
其中WM_SYSCOMMAND是說明向窗口發送指定的命名, 命令的具體值通過第3個參數傳進去.
注意到上面在拖動結束時發送了一個WM_LBUTTONUP消息, 這是因爲當鼠標左鍵按下(並移動)時我們會調用該函數來開始拖動,你的應用程序師可以檢測到開始拖動前的這個MouseDown事件de, 但SC_MOVE會攔截MouseUp來結束拖動.你的應用程序監視不到這個MouseUp事件,所以你可能會發現鼠標左鍵Down和Up數目不配對, 所以在拖動結束時我們Mock了一個Up事件.
由於SendMessage 方法是不會立即返回的(同步的, SendMessageCallback  與 SendNotifyMessage 是立即放回的), 所以在SendMessage執行完畢時,也就是我們"拖動"操作完畢之時, 所以我們可以在這裏調用OnDragAndMoveEnded(hwnd)來引發我們自定義的"拖動結束"事件

SendMessage第三個參數(wParam)可以包含的具體的指令值,可以參考下面的枚舉:
        public enum WM_SYSCOMMAND_WPARAM
        {
            SC_FIRST 
= 0xF000,

            
// Sizes the window.
            SC_SIZE = SC_FIRST,

            
// Moves the window.
            SC_MOVE = SC_FIRST + 0x10,

            
// Minimizes the window.
            SC_MINIMIZE = SC_FIRST + 0x20,

            
// Maximizes the window.
            SC_MAXIMIZE = SC_FIRST + 0x30,

            
// Moves to the next window.
            SC_NEXTWINDOW = SC_FIRST + 0x40,

            
// Moves to the previous window.
            SC_PREVWINDOW = SC_FIRST + 0x50,

            
// Closes the window.
            SC_CLOSE = SC_FIRST + 0x60,

            
//Scrolls vertically
            SC_VSCROLL = SC_FIRST + 0x70,

            
// Scrolls horizontally.
            SC_HSCROLL = SC_FIRST + 0x80,

            
// Retrieves the window menu as a result of a mouse click.
            SC_MOUSEMENU = SC_FIRST + 0x90,

            
// Retrieves the window menu as a result of a keystroke.
            
// For more information, see the Remarks section.
            SC_KEYMENU = SC_FIRST + 0x100
             
            SC_ARRANGE 
= SC_FIRST + 0x110,

            
// Restores the window to its normal position and size.
            SC_RESTORE = SC_FIRST + 0x120,

            
// Activates the Start menu.
            SC_TASKLIST = SC_FIRST + 0x130,

            
// Executes the screen saver application specified 
            
// in the [boot] section of the System.ini file.
            SC_SCREENSAVE = SC_FIRST + 0x140,

            
// Activates the window associated with the application-specified hot key. 
            
// The lParam parameter identifies the window to activate.
            SC_HOTKEY = SC_FIRST + 0x150,

            
// Selects the default item; 
            
// the user double-clicked the window menu.
            SC_DEFAULT = SC_FIRST + 0x160,

            
// Sets the state of the display.
            
// This command supports devices that have power-saving features,
            
// such as a battery-powered personal computer.
            
// The lParam parameter can have the following values:
            
// -1 - the display is powering on
            
//  1 - the display is going to low power
            
//  2 - the display is being shut off
            SC_MONITORPOWER = SC_FIRST + 0x170
           
            
// Changes the cursor to a question mark with a pointer. 
            
// If the user then clicks a control in the dialog box, 
            
// the control receives a WM_HELP message.
            SC_CONTEXTHELP = SC_FIRST + 0x180

            SC_SEPARATOR 
= 0xF00F
        }

完整的代碼,參考下面, 其支持WinForm和WPF 窗口:
    public static class DragMoveExtention
    {
        
public static event EventHandler DragAndMoveStarted;
        
public static event EventHandler DragAndMoveEnded;

        
public const int SC_MOVE = 0xf012;
        
public const int WM_SYSCOMMAND = 0x112;
        
public const int WM_LBUTTONUP = 0x202;

        [DllImport(
"user32.dll", CharSet = CharSet.Auto)]
        
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        
private static void DragAndMoveInner(IntPtr hwnd)
        {
            OnDragAndMoveStarted(hwnd);

            SendMessage(hwnd, WM_SYSCOMMAND, (IntPtr)SC_MOVE, IntPtr.Zero);
            SendMessage(hwnd, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);

            OnDragAndMoveEnded(hwnd);
        }


        
private static void OnDragAndMoveStarted(Object sender)
        {
            
if(DragAndMoveStarted != null)
            {
                DragAndMoveStarted(sender, EventArgs.Empty);
            }
        }

        
private static void OnDragAndMoveEnded(Object sender)
        {
            
if(DragAndMoveEnded != null)
            {
                DragAndMoveEnded(sender, EventArgs.Empty);
            }
        }

        
// use it like this: 
        
// wpfWindow.MouseMove += delegate{ wpfWindow.DragAndMove(); };
        public static void DragAndMove(this Window window)
        {
            
if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                IntPtr hwnd 
= new WindowInteropHelper(window).Handle;
                DragAndMoveInner(hwnd);
            }
        }

        
// use it like this: 
        
// winForm.MouseMove += delegate { winForm.DragAndMove(); };
        public static void DragAndMove(this Form form)
        {
            
if (Control.MouseButtons == MouseButtons.Left)
            {
                DragAndMoveInner(form.Handle);
            }
        }
        
    }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章