65.9K
CodeProject 正在变化。 阅读更多。
Home

使用原生回调子类化 TextBox

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (13投票s)

2008年1月4日

CPOL

4分钟阅读

viewsIcon

48875

downloadIcon

935

通过使用原生回调进行子类化,为 Textbox 添加 Click 事件

引言

在上一篇文章中,我讨论了为 Windows Forms 中的 TextBox 添加 Click 事件。不幸的是,相同的代码在智能设备应用程序中不起作用,因为 .NET Compact Framework 不支持 textbox 的该重写的 onClick 实现。因此,我们需要找到新的解决方案。

背景

Compact Framework 顾名思义,与 .NET Framework 相比,它提供的功能有限但必要,对事件的支持也有限,以减小其尺寸,因为它必须在资源有限的设备上可用。同时,他们提供了一些类,您可以使用它们来实现您需要的事件;在我们的例子中,是 TextBox 上的 Click 事件。这些类会捕获由机器本身处理的鼠标 Click 消息。捕获事件的过程是当鼠标按钮被点击时,会生成一个消息,该消息被传递到设备,作为回报,操作系统会返回一个具有其他参数的对象句柄,该对象引发了事件。对于提供的类中可用的窗口消息列表,定义了不同的结构和方法,通过使用对象句柄,您可以在控件上实现事件。这种在控件上实现事件的方法称为“使用原生回调子类化控件”。

Using the Code

我使用了以下类来创建一个智能设备控件库项目

  • WndProcHooker.cs
  • Win32.cs

您可以在 这里 找到这些类。

类中的代码都有很好的注释,可以很好地理解方法中发生的事情。(两个类都包含在示例项目中。)

创建 CF 控件库项目所需的步骤如下所示

  1. 打开您的 Visual Studio 2005 IDE,然后选择“新建项目”>“Visual C#”>“智能设备”>“Pocket PC 2003”>“从可用模板中选择控件库”。将其命名为 ClickableTextBox 或您喜欢的任何名称。
  2. 添加一个新的 *.cs 类,将其命名为 WndProcHooker,然后将下载的 WndProcHooker 类的内容复制到其中。
  3. 添加一个新的 *.cs 类,将其命名为 Win32,然后将下载的 Win32 类的内容复制到其中。
  4. 添加一个新的 Component 类,并将其命名为 ClickableTextBox
    在其源代码中,将基类更改为 TextBox。请记住在 designer.cs 类中也将基类更改为 TextBox
  5. 以下是您需要在 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 类中的代码。

    1. ClickableTextBox 构造函数中,会挂接鼠标左键抬起和鼠标左键按下事件消息。
    2. WM_Notify_Handler 是在鼠标引发某个事件时被调用的通知方法。此方法检查鼠标左键抬起和按下事件,并调用 win32 函数来获取光标位置,将其转换为相应的客户端点,并引发 MouseClickEvent,进而引发 OnMouseClick,最后在 TextBox 上触发 onClick 事件。
  6. 添加一个具有 public 访问修饰符的 delegate,其形式如下,并附带其处理程序

    public delegate void onClick(object sender, EventArgs e);
    
    public event onClick Clicked;  
  7. WM_LButtonDown_Handler 是鼠标左键按下的处理程序,我将其留空未实现。
  8. 最后一个函数是 WM_LButtonUp_Handler。我在这里添加了处理程序 ClickableTextBox_Clicked
    就是这样。编译项目。您可能会遇到错误!:)
    如果您使用了 MSDN 下载的 WndProcHookerWin32 类,它们在顶部没有添加必要的库。在相应的类中添加它们,现在您就不会遇到错误了(如果上帝保佑)。
  9. 创建一个测试 Pocket PC 应用程序,通过右键单击工具栏、选择“项”并浏览到指定 DLL 来添加 ClickableTextBox.dll。该控件将作为一个拖放控件添加。
  10. [可选] 如果 ClickableTextBox.dll 未添加到项目引用中,请通过拖放添加对它的引用。
  11. 从工具栏拖动 ClickableTextBox 控件,并检查其事件列表。将添加一个名为“Clicked”的新事件到列表中。添加事件并用一些警报消息进行测试,这就完成了。

编程愉快。

关注点

这个 Click 事件不是真正的 Click 事件,因为它缺少一些东西。我在 LButton 抬起事件上引发了 ClickEvent,所以即使您在 Textbox 内部按下了 LButton 并在 Textbox 外部释放(即拖动事件),它也会触发 Click 事件。因此,您需要保留旧的光标位置,并将其与左键抬起事件位置的坐标进行比较。这个功能缺失了,所以如果有人喜欢,可以添加这个功能。

历史

  • 2008 年 1 月 3 日:初始发布
© . All rights reserved.