想想以前用跟踪鼠标位移的方式来实现窗口拖动的方式还真有些傻, 后来, .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);
}
}
}