forked from ahdung/FormDraggerDemo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FormDragger.cs
365 lines (324 loc) · 12.5 KB
/
FormDragger.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows.Forms;
namespace AhDung.WinForm
{
/// <summary>
/// 窗体拖拽器
/// <para>- 使窗体客户区可拖动</para>
/// </summary>
[SuppressUnmanagedCodeSecurity]
public static class FormDragger
{
static bool _enabled;
static MouseLeftDownMessageFilter _filter;
/// <summary>
/// 拖拽发生时
/// <para>- 令Cancel = true可取消拖拽</para>
/// <para>- 注意sender为null</para>
/// </summary>
public static event EventHandler<FormDraggingCancelEventArgs> Dragging;
private static void OnDragging(FormDraggingCancelEventArgs e)
{
var handler = Dragging;
if (handler != null)
{
handler(null, e);
}
}
/// <summary>
/// 拖拽器开关状态改变后
/// </summary>
public static event EventHandler EnabledChanged;
private static void OnEnabledChanged(EventArgs e)
{
var handler = EnabledChanged;
if (handler != null)
{
handler(null, e);
}
}
/// <summary>
/// 获取或设置是否启用拖拽器
/// </summary>
public static bool Enabled
{
get { return _enabled; }
set
{
if (_enabled == value) { return; }
if (value)
{
if (_filter == null)
{
_filter = new MouseLeftDownMessageFilter();
}
Application.AddMessageFilter(_filter);
}
else
{
Application.RemoveMessageFilter(_filter);
}
_enabled = value;
OnEnabledChanged(EventArgs.Empty);
}
}
static List<Control> _excludeControls;
/// <summary>
/// 例外控件
/// </summary>
public static List<Control> ExcludeControls
{
get { return _excludeControls ?? (_excludeControls = new List<Control>()); }
}
/// <summary>
/// 左键单击消息过滤器
/// </summary>
private class MouseLeftDownMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
Point point;
switch (m.Msg)
{
case 0x201://WM_LBUTTONDOWN
//Debug.WriteLine(m.HWnd.ToString("X8") + "\t" + m.WParam + "\t" + GetPoint(m.LParam));
if ((int)m.WParam != 1) { break; }//仅处理单纯的左键单击
var c = Control.FromHandle(m.HWnd);
if (c != null && CanDrag(c, (point = GetPoint(m.LParam))))
{
return DoDrag(c, point, true);
}
break;
case 0xA1://WM_NCLBUTTONDOWN
var ht = (int)m.WParam;
if (ht == 5/*HTMENU*/)//点到MainMenu时
{
//判断是否点到菜单项。若不是或菜单项不可用则拖拽
//点击菜单时m.HWnd为窗体句柄而非菜单句柄
point = GetPoint(m.LParam);
var menu = GetMenu(m.HWnd);
var item = MenuItemFromPoint(m.HWnd, menu, (POINT)point);
if (item == -1
|| (GetMenuState(menu, item, 0x400/*MF_BYPOSITION*/) & 2/*MF_DISABLED*/) == 2)
{
return DoDrag(Control.FromHandle(m.HWnd), point, false);
}
}
else if (ht == 4/*HTSIZE*/ || ht == 18/*HTBORDER*/)//点到两个滚动条右下空白、控件边框时
{
return DoDrag(Control.FromHandle(m.HWnd), GetPoint(m.LParam), false);
}
else if (ht == 6/*HTHSCROLL*/ || ht == 7/*HTVSCROLL*/)//点到滚动条时
{
//获取滚动条状态,若处于无效则拖拽
//OBJID_HSCROLL=0xFFFFFFFA
//OBJID_VSCROLL=0xFFFFFFFB
var idObject = unchecked((int)(ht + 0xFFFFFFF4));
SCROLLBARINFO sbi = new SCROLLBARINFO { cbSize = SizeOfSCROLLBARINFO };
if (GetScrollBarInfo(m.HWnd, idObject, ref sbi)
&& sbi.scrollBarInfo == 1/*STATE_SYSTEM_UNAVAILABLE*/)
{
return DoDrag(Control.FromHandle(m.HWnd), GetPoint(m.LParam), false);
}
}
break;
}
return false;
}
}
/// <summary>
/// 执行拖拽
/// </summary>
/// <returns>指示是否拦截消息</returns>
private static bool DoDrag(Control c, Point point, bool isClientArea)
{
try
{
Form form;
if (c == null || (form = c.FindForm()) == null)
{
return false;
}
var e = new FormDraggingCancelEventArgs(c, point, isClientArea);
OnDragging(e);
if (e.Cancel)
{
return false;
}
//当作为MDI子窗体且最大化时,拖MDI主窗体
if (form.WindowState == FormWindowState.Maximized)
{
form = form.MdiParent ?? form;
}
SendMessage(form.Handle, 0xA1, (IntPtr)2, IntPtr.Zero);
return true;
}
catch
{
return false;
}
}
static PropertyInfo _linkState;
/// <summary>
/// 判断是否处于可拖拽情形
/// </summary>
private static bool CanDrag(Control c, Point pt)
{
if (_excludeControls != null && _excludeControls.Contains(c))
{
return false;
}
if (c is Form
|| (c is Label && !(c is LinkLabel))
|| c is Panel
|| c is GroupBox
|| c is PictureBox
|| c is ProgressBar
|| c is StatusBar
|| c is MdiClient
)
{
return true;
}
if (c is LinkLabel)
{
//若点到链接部分且链接可用则不拦截
//取State优于调用PointInLink
foreach (LinkLabel.Link link in ((LinkLabel)c).Links)
{
if (_linkState == null)
{
_linkState = typeof(LinkLabel.Link).GetProperty("State", BindingFlags.Instance | BindingFlags.NonPublic);
}
var state = (LinkState)_linkState.GetValue(link, null);
if ((state & LinkState.Hover) == LinkState.Hover && link.Enabled)
{
return false;
}
}
return true;
}
if (c is ToolStrip)
{
var item = ((ToolStrip)c).GetItemAt(pt);
if (item == null
|| !item.Enabled
|| item is ToolStripSeparator
|| (item is ToolStripLabel && !((ToolStripLabel)item).IsLink))
{
return true;
}
return false;
}
if (c is ToolBar)
{
//判断鼠标是否点击在按钮上
var ptPoint = GCHandle.Alloc((POINT)(pt), GCHandleType.Pinned);
var index = (int)SendMessage(c.Handle, (0x400 + 69)/*TB_HITTEST */, IntPtr.Zero, ptPoint.AddrOfPinnedObject());
ptPoint.Free();
//若点击在按钮上且当按钮可用时则不拦截
if (index >= 0 && ((ToolBar)c).Buttons[index].Enabled)
{
return false;
}
return true;
}
if (c is DataGridView)
{
return ((DataGridView)c).HitTest(pt.X, pt.Y).Type == DataGridViewHitTestType.None;
}
return false;
}
#region Win32 API
// ReSharper disable MemberCanBePrivate.Local
// ReSharper disable FieldCanBeMadeReadOnly.Local
private static Point GetPoint(IntPtr lParam)
{
return new Point(unchecked((int)(long)lParam));
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetScrollBarInfo(IntPtr hwnd, int idObject, ref SCROLLBARINFO psbi);
static readonly int SizeOfSCROLLBARINFO = Marshal.SizeOf(typeof(SCROLLBARINFO));
[StructLayout(LayoutKind.Sequential)]
private struct SCROLLBARINFO
{
public int cbSize;
public int rcLeft;
public int rcTop;
public int rcRight;
public int rcBottom;
public int dxyLineButton;
public int xyThumbTop;
public int xyThumbBottom;
public int reserved;
public int scrollBarInfo;
public int upArrowInfo;
public int largeDecrementInfo;
public int thumbnfo;
public int largeIncrementInfo;
public int downArrowInfo;
}
[DllImport("user32.dll")]
private static extern int GetMenuState(IntPtr hMenu, int uId, int uFlags);
[DllImport("user32.dll")]
private static extern IntPtr GetMenu(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int MenuItemFromPoint(IntPtr hWnd, IntPtr hMenu, POINT ptScreen);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int X;
public int Y;
public static explicit operator POINT(Point pt)
{
return new POINT { X = pt.X, Y = pt.Y };
}
public static explicit operator Point(POINT pt)
{
return new Point(pt.X, pt.Y);
}
public override string ToString()
{
return X + ", " + Y;
}
}
// ReSharper restore FieldCanBeMadeReadOnly.Local
// ReSharper restore MemberCanBePrivate.Local
#endregion
}
/// <summary>
/// 窗体拖拽取消事件参数
/// </summary>
public class FormDraggingCancelEventArgs : CancelEventArgs
{
/// <summary>
/// 获取点击控件
/// </summary>
public Control Control { get; private set; }
/// <summary>
/// 获取鼠标位置
/// <para>- 取自点击消息产生时的lParam</para>
/// <para>- 当IsClientArea = true时,相对Control的客户区坐标,否则相对屏幕坐标</para>
/// </summary>
public Point MousePosition { get; private set; }
/// <summary>
/// 是否控件客户区
/// </summary>
public bool IsClientArea { get; private set; }
public FormDraggingCancelEventArgs(Control c, Point point, bool isClientArea)
{
this.Control = c;
this.MousePosition = point;
this.IsClientArea = isClientArea;
}
}
}