使用原生回调子类化 TextBox






4.76/5 (13投票s)
通过使用原生回调进行子类化,
引言
在上一篇文章中,我讨论了为 Windows Forms 中的 TextBox
添加 Click 事件。不幸的是,相同的代码在智能设备应用程序中不起作用,因为 .NET Compact Framework 不支持 textbox
的该重写的 onClick 实现。因此,我们需要找到新的解决方案。
背景
Compact Framework 顾名思义,与 .NET Framework 相比,它提供的功能有限但必要,对事件的支持也有限,以减小其尺寸,因为它必须在资源有限的设备上可用。同时,他们提供了一些类,您可以使用它们来实现您需要的事件;在我们的例子中,是 TextBox
上的 Click 事件。这些类会捕获由机器本身处理的鼠标 Click 消息。捕获事件的过程是当鼠标按钮被点击时,会生成一个消息,该消息被传递到设备,作为回报,操作系统会返回一个具有其他参数的对象句柄,该对象引发了事件。对于提供的类中可用的窗口消息列表,定义了不同的结构和方法,通过使用对象句柄,您可以在控件上实现事件。这种在控件上实现事件的方法称为“使用原生回调子类化控件”。
Using the Code
我使用了以下类来创建一个智能设备控件库项目
- WndProcHooker.cs
- Win32.cs
您可以在 这里 找到这些类。
类中的代码都有很好的注释,可以很好地理解方法中发生的事情。(两个类都包含在示例项目中。)
创建 CF 控件库项目所需的步骤如下所示
- 打开您的 Visual Studio 2005 IDE,然后选择“新建项目”>“Visual C#”>“智能设备”>“Pocket PC 2003”>“从可用模板中选择控件库”。将其命名为
ClickableTextBox
或您喜欢的任何名称。 - 添加一个新的 *.cs 类,将其命名为
WndProcHooker
,然后将下载的WndProcHooker
类的内容复制到其中。 - 添加一个新的 *.cs 类,将其命名为
Win32
,然后将下载的Win32
类的内容复制到其中。 - 添加一个新的
Component
类,并将其命名为ClickableTextBox
。
在其源代码中,将基类更改为TextBox
。请记住在 designer.cs 类中也将基类更改为TextBox
。 -
以下是您需要在
Component
类中编写的代码:.public partial class ClickableTextBox : TextBox { public delegate void onClick(object sender, EventArgs e); public event onClick ClickableTextBox_Clicked; public ClickableTextBox() { InitializeComponent(); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_LButtonDown_Handler), Win32.WM_LBUTTONDOWN); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_LButtonUp_Handler), Win32.WM_LBUTTONUP); } #region OnClick /// <summary> /// The method that gets called when a WM_NOTIFY message is received by the /// TextBox's parent. /// </summary> /// <param name="hwnd">The handle of the window that received the message /// </param> /// <param name="msg">The message received</param> /// <param name="wParam">The wParam arguments for the message</param> /// <param name="lParam">The lParam arguments for the message</param> /// <param name="handled">Set to true to indicate that /// this message was handled</param> /// <returns>An appropriate return code for the message handled (see MSDN) /// </returns> int WM_Notify_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { Win32.NMHDR nmHdr = new Win32.NMHDR(); System.Runtime.InteropServices.Marshal.PtrToStructure ((IntPtr)lParam, nmHdr); switch (nmHdr.code) { case Win32.WM_LBUTTONDOWN: case Win32.WM_LBUTTONUP: // get the cursor coordinates on the client Point msgPos = Win32.LParamToPoint((int)Win32.GetMessagePos()); msgPos = this.PointToClient(msgPos); RaiseMouseClickEvent(MouseButtons.Left, msgPos); break; default: break; } return 0; } public void OnMouseClick(MouseEventArgs e) { this.OnClick(new EventArgs()); } protected override void OnClick(EventArgs e) { base.OnClick(e); } /// <summary> /// Raises the MouseClick event for the TextBox with the specified handle. /// </summary> /// <param name="hNode">The handle of the node for which the event is raised /// </param> /// <param name="button">The [mouse] buttons that were pressed /// to raise the event</param> /// <param name="coords">The [client] cursor coordinates /// at the time of the event</param> public void RaiseMouseClickEvent(MouseButtons button, Point coords) { MouseEventArgs e = new MouseEventArgs(button, 1, coords.X, coords.Y, 0); OnMouseClick(e); } // The callback called when the window receives a WM_LBUTTONDOWN // message. We capture the mouse and draw the button in the "pushed" // state. // hwnd - The handle to the window that received the // message. // wParam - Indicates whether various virtual keys are // down. // lParam - The coordinates of the cursor. // handled - Set to true if we don't want to pass this // message on to the original window procedure. // Returns zero if we process this message. int WM_LButtonDown_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { // Start capturing the mouse input. this.Capture = true; // someone clicked on us so grab the focus this.Focus(); // Fire the MouseDown event ClickableTextBox_Clicked(this, null); // We have handled this windows message and we don't want the // sub-classed window to do anything else. handled = true; return 0; } // The callback called when the window receives a WM_LBUTTONUP // message. We release capture on the mouse, draw the button in the // "un-pushed" state and fire the OnMouseUp event if the cursor was // let go of inside our client area. // hwnd - The handle to the window that received the // message // wParam - Indicates whether various virtual keys are // down. // lParam - The coordinates of the cursor // handled - Set to true if we don't want to pass this // message // on to the original window procedure // Returns zero if we process this message. int WM_LButtonUp_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { this.Capture = false; // TODO : implement your login on mouse key up event handled = true; return 0; } #endregion }
让我们来分析一下
ClickableTextBox
类中的代码。- 在
ClickableTextBox
构造函数中,会挂接鼠标左键抬起和鼠标左键按下事件消息。 WM_Notify_Handler
是在鼠标引发某个事件时被调用的通知方法。此方法检查鼠标左键抬起和按下事件,并调用win32
函数来获取光标位置,将其转换为相应的客户端点,并引发MouseClickEvent
,进而引发OnMouseClick
,最后在TextBox
上触发onClick
事件。
- 在
-
添加一个具有
public
访问修饰符的delegate
,其形式如下,并附带其处理程序public delegate void onClick(object sender, EventArgs e); public event onClick Clicked;
WM_LButtonDown_Handler
是鼠标左键按下的处理程序,我将其留空未实现。- 最后一个函数是
WM_LButtonUp_Handler
。我在这里添加了处理程序ClickableTextBox_Clicked
。
就是这样。编译项目。您可能会遇到错误!:)
如果您使用了 MSDN 下载的WndProcHooker
和Win32
类,它们在顶部没有添加必要的库。在相应的类中添加它们,现在您就不会遇到错误了(如果上帝保佑)。 - 创建一个测试 Pocket PC 应用程序,通过右键单击工具栏、选择“项”并浏览到指定 DLL 来添加 ClickableTextBox.dll。该控件将作为一个拖放控件添加。
- [可选] 如果 ClickableTextBox.dll 未添加到项目引用中,请通过拖放添加对它的引用。
- 从工具栏拖动
ClickableTextBox
控件,并检查其事件列表。将添加一个名为“Clicked”的新事件到列表中。添加事件并用一些警报消息进行测试,这就完成了。
编程愉快。
关注点
这个 Click 事件不是真正的 Click 事件,因为它缺少一些东西。我在 LButton
抬起事件上引发了 ClickEvent
,所以即使您在 Textbox
内部按下了 LButton
并在 Textbox
外部释放(即拖动事件),它也会触发 Click 事件。因此,您需要保留旧的光标位置,并将其与左键抬起事件位置的坐标进行比较。这个功能缺失了,所以如果有人喜欢,可以添加这个功能。
历史
- 2008 年 1 月 3 日:初始发布