周銀輝
想想以前用跟蹤鼠標位移的方式來實現窗口拖動的方式還真有些傻, 後來, .Net3.0以來的Window類內置了DragMove方法, 似乎讓我們方便的不少, 但, 最近這個方法也不能滿足需求了, 因爲我需要DragMove過程中向外發事件來通知我"拖動開始了"和"拖動結束了", 可惜的是Window類沒有提供者兩個事件 (也曾企圖通過其他方式來得到通知, 比如監視MouseUp等, 效果不好).
所以就自己來實現窗口拖動吧
不必同監視鼠標位移手動更新窗口位置, 其實通過向窗口發送SC_MOVE命令來移動窗口就可以了,這個命令會幫我們完成位置計算和更新工作:
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_LBUTTONUP消息, 這是因爲當鼠標左鍵按下(並移動)時我們會調用該函數來開始拖動,你的應用程序師可以檢測到開始拖動前的這個MouseDown事件de, 但SC_MOVE會攔截MouseUp來結束拖動.你的應用程序監視不到這個MouseUp事件,所以你可能會發現鼠標左鍵Down和Up數目不配對, 所以在拖動結束時我們Mock了一個Up事件.
由於SendMessage 方法是不會立即返回的(同步的, SendMessageCallback 與 SendNotifyMessage 是立即放回的), 所以在SendMessage執行完畢時,也就是我們"拖動"操作完畢之時, 所以我們可以在這裏調用OnDragAndMoveEnded(hwnd)來引發我們自定義的"拖動結束"事件
SendMessage第三個參數(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 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);
}
}
}