话说,.Net Compact Framework比它的兄弟--完整版的Framework精简了不少,自然很多功能也没能提供了。然而对功能的需求是无止境的。这里我们要做的就是找寻那消失的属于ListView的Context Menu。
先说说遇到的问题吧。.Net Compact Framework中,ListView提供了Context Menu属性,但是设置之后实测,那个”点圈“倒是出现了,但是菜单没有。那么自然的想到在鼠标点击的事件中模拟,可惜这个事件没有(真没有)。自定义控件重写鼠标点击函数呢?人家不理你,根本不进来。
那怎么办呢?
1. 通过SetWindowLong,替换ListView的消息处理函数,并截获鼠标点击消息WM_LBUTTONDOWN
2. 在WM_LBUTTONDOWN消息中,使用SHRecognizeGesture模拟Tap and Hold
3. 显示Context Menu。
1. 替换ListView的消息处理函数,并截获鼠标点击消息WM_LBUTTONDOWN
将替换消息处理函数并转发消息的功能封装为一个类Subclasser:
- 函数SubClass负责替换传入的hWindow的消息函数
- 函数
UnsubClass负责恢复hWindow的消息函数
-
函数
WndProc就是新的消息处理函数,如果Message有效,就转发出去。
Subclasser主要代码如下:
public class Subclasser : IDisposable
{
public delegate IntPtr WindowProc(IntPtr hwnd, uint uMsg, IntPtr wParam, IntPtr lParam);
public static WindowProc NewWindowDelegate;
private static IntPtr OldWindowProc;
private int GWL_WNDPROC = -4;
[DllImport("coredll.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("coredll.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("coredll.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private IntPtr m_windowHandle = (IntPtr)0;
public event MessageEventHandler Message;
public void SubClass(IntPtr hWindow)
{
if ((IntPtr)0 != hWindow)
{
m_windowHandle = hWindow;
int newWndProc = Marshal.GetFunctionPointerForDelegate(NewWindowDelegate).ToInt32();
int result = SetWindowLong(m_windowHandle, GWL_WNDPROC, newWndProc);
OldWindowProc = (IntPtr) result;
}
}
private void UnsubClass(IntPtr hWindow)
{
SetWindowLong(hWindow, GWL_WNDPROC, OldWindowProc.ToInt32());
}
public IntPtr WndProc(IntPtr hwnd, uint uMsg, IntPtr wParam, IntPtr lParam)
{
Message msg = new Message();
msg.HWnd = hwnd;
msg.Msg = (int)uMsg;
msg.WParam = wParam;
msg.LParam = lParam;
MessageEventArgs msgEventArgs = new MessageEventArgs(msg);
if (Message != null)
{
Message(this, msgEventArgs);
}
if (msgEventArgs.Result == (IntPtr)0)
{
return CallWindowProc(OldWindowProc, hwnd, uMsg, wParam, lParam);
}
return msgEventArgs.Result;
}
2. 创建User Control,通过Subclasser接收并过滤出mouse down消息,实现Context Menu。
2.1 创建User Control,仅包含ListView,封装并实现ListView的属性Columns,Items,View等等。
public ListView.ColumnHeaderCollection Columns
{
get { return listView.Columns; }
}
public ListView.ListViewItemCollection Items
{
get
{
return listView.Items;
}
}
public View View
{
get { return listView.View; }
set { listView.View = value; }
}
2.2 通过Subclasser接收并过滤消息
m_subClasser = new Subclasser();
m_subClasser.Message += SubClasser_Message;
private void SubClasser_Message(object source, MessageEventArgs args)
{
if (args.Msg == WindowsMessages.WM_LBUTTONDOWN)
{
Byte[] point = BitConverter.GetBytes(args.LParam.ToInt32());
Byte[] pointXByte = new byte[4];
Byte[] pointYByte = new byte[4];
Array.Copy(point,0,pointXByte,0,2);
Array.Copy(point,2,pointYByte,0,2);
int pointX = BitConverter.ToInt32(pointXByte,0);
int pointY = BitConverter.ToInt32(pointYByte, 0);
if (CheckContext(new MouseEventArgs(MouseButtons.Right, 1, pointX, pointY, 0), listView, listView.Handle))
{
args.Result = (IntPtr) 1;
}
else
{
args.Result = (IntPtr)0;
}
}
}
2.3 模拟Tab and Hold
internal struct SHRGINFO
{
public int cbSize;
public IntPtr hwndClient;
public int ptDownX;
public int ptDownY;
public SHRGFLags dwFlags;
}
[Flags]
internal enum SHRGFLags
{
SHRG_RETURNCMD = 0x00000001,
SHRG_NOTIFYPARENT = 0x00000002,
SHRG_LONGDELAY = 0x00000008,
SHRG_NOANIMATION = 0x00000010,
}
public const int GN_CONTEXTMENU = 1000;
[DllImport("aygshell")]
extern private static int SHRecognizeGesture(ref SHRGINFO shr);
protected bool CheckContext(MouseEventArgs e, Control control, IntPtr hwnd)
{
if (RecognizeGesture(hwnd,e.X,e.Y))
{
ShowContext(control,e);
return true;
}
return false;
}
protected virtual bool RecognizeGesture(IntPtr hwnd, int x, int y)
{
SHRGINFO info = new SHRGINFO();
info.cbSize = Marshal.SizeOf(info);
info.ptDownX = x;
info.ptDownY = y;
info.dwFlags = SHRGFLags.SHRG_RETURNCMD;
info.hwndClient = hwnd;
int cmd = SHRecognizeGesture(ref info);
return (cmd == GN_CONTEXTMENU);
}
2.4 显示Context Menu
protected void ShowContext(Control control, MouseEventArgs e)
{
if (ContextMenu != null)
{
ContextMenu.Show(control, new Point(e.X, e.Y));
}
if (null != TabHoldHandler)
{
TabHoldHandler(control,e);
}
}
3. 注意
一定要在控件做完初始化工作之后,再通过Subclasser替换掉默认的消息处理函数,否则有可能抛异常(原因目前未知)。或者说,在使用该控件的窗体的form loaded事件中进行替换比较好。
到这里,功能基本上都实现了,可能代码贴的有点多。希望不影响大家的理解。也不知道csdn怎么添加代码附件,:(
最后,说说没能解决的问题,由于想要封装,便于使用,于是做成了控件,但是不知道怎么实现design time support,也即无法在属性对话框中进行编辑。添加了该控件的窗体,即使通过代码在InitializeComponent函数中添加了该控件的行列,也会导致打开设计窗口时出错。求解。
参考文章:
http://msdn.microsoft.com/zh-cn/magazine/cc185722(en-us).aspx
http://msdn.microsoft.com/en-us/magazine/cc188736.aspx