利用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);
            }
        }
        
    }


posted @ 2009-08-28 16:14  周银辉  阅读(8612)  评论(1编辑  收藏  举报