在派生的 C# 用户控件中处理窗口通知


如何处理

从 .NET 树视图控件派生的 C# 类中列出的任何树视图通知?

例如,我尝试处理点击通知,如下所示:

class ExtendedTreeView : TreeView
{
    private const Int32 NM_FIRST = (Int32)(0U - 0U);
    private const Int32 NM_CLICK = unchecked((Int32)((UInt32)NM_FIRST - 2U));
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == NM_CLICK)
        {
            MessageBox.Show("NM_CLICK");
        }
        base.WndProc(ref m);
    }
}

但消息框永远不会显示。这是我第一次尝试使用 Win32 API 来修改 .NET 控件的行为,所以我不知道出了什么问题。

这是处理这些通知的正确方法吗?

仅供参考:我知道 .NET 树视图控件有一个单击事件。这只是第一次测试。稍后我想启用TVS_EX_MULTISELECT样式。由于 .NET TreeView 控件在启用 TVS_EX_MULTISELECT 时不会触发任何AfterSelect事件,因此我想调查TVN_SELCHANGED的行为,并在以后TVN_ITEMCHANGED通知。

事情没那么简单。 查看 MSDN 文章,NM_CLICK通知作为WM_NOTIFY消息传递。 并将其发送到树视图的父级。 Winforms具有适当的管道以将其回显到原始控件,以允许消息由派生自TreeView的类处理并自定义事件处理。 这是通过在消息中添加0x2000,即 Winforms 源代码中WM_REFLECT的值来完成的。

所以代码应该看起来像这样:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class ExtendedTreeView : TreeView {
    protected override void WndProc(ref Message m) {
        if (m.Msg == WM_REFLECT + WM_NOFITY) {
            var notify = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
            if (notify.code == NM_CLICK) {
                MessageBox.Show("yada");
                m.Result = (IntPtr)1;
                return;
            }
        }
        base.WndProc(ref m);
    }
    private const int NM_FIRST = 0;
    private const int NM_CLICK = NM_FIRST - 2;
    private const int WM_REFLECT = 0x2000;
    private const int WM_NOFITY = 0x004e;
    [StructLayout(LayoutKind.Sequential)]
    private struct NMHDR {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }
}

请注意,TreeView 已经完成了所有这些操作,这就是生成 NodeMouseClick、Click 和 MouseClick 事件的方式。 执行此操作的代码还可以解决本机控件中的一些怪癖,因此在提交使用它之前,请确保确实需要它。 如果您想知道发生了什么,请查看参考源。

通知将发送到控件的父级:

通知树视图控件的父窗口用户已在控件内单击鼠标左键。

这是通过WM_NOITIFY消息完成的。幸运的是,作者还包括一种称为反射的机制,以允许树视图的子类也接收通知。该消息&H2000 | WM_NOTIFY,您可以将其完全视为WM_NOTIFY

另请注意,NM_CLICK不是消息,而是包装在NMHDR结构中的通知

此通知代码以WM_NOTIFY消息的形式发送。

MSDN 中提到了 2 件重要的事情:1) msg.lparam 是指向 NMHDR 结构的指针2) 通知发送到家长控制

所以工作代码是(编译为控制台应用程序 - 它将在那里打印消息):

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class MyTreeView : TreeView
{
    public TreeView RealTreeView;
    public MyTreeView()
    {
        RealTreeView = new TreeView();
        RealTreeView.Dock = DockStyle.Fill;
        Controls.Add(RealTreeView);
    }
    enum WM
    {
        NOTIFY = 78
    }
    enum NM : uint
    {
        FIRST = 0,
        NM_CLICK = unchecked(FIRST - 2),
        NM_CUSTOMDRAW = unchecked(FIRST - 12),
        NM_DBLCLK = unchecked(FIRST - 3),
        NM_KILLFOCUS = unchecked(FIRST - 8),
        NM_RCLICK = unchecked(FIRST - 5),
        NM_RDBLCLK = unchecked(FIRST - 6),
        NM_RETURN = unchecked(FIRST - 4),
        NM_SETCURSOR = unchecked(FIRST - 17),
        NM_SETFOCUS = unchecked(FIRST - 7)
    }
    [StructLayout(LayoutKind.Sequential)]
    struct NMHDR {
        public IntPtr hwndFrom;
        public UIntPtr idFrom;
        public uint code;
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == (int)WM.NOTIFY)
        {
            uint code;
            unsafe
            {
                var nmhdr = (NMHDR*)m.LParam.ToPointer();
                code = nmhdr->code;
            }
            NM nmCode = (NM)code;
            Console.WriteLine("WM_NOTIFY " + nmCode);
        }
    }
}
public class MyGuiClass
{
    public static void Main()
    {
        Form f = new Form();
        var tv = new MyTreeView();
        tv.RealTreeView.Nodes.Add("zero").Nodes.Add("sub-zero");
        tv.RealTreeView.Nodes.Add("one");
        tv.RealTreeView.Nodes.Add("two");
        tv.RealTreeView.Nodes.Add("three");
        tv.Dock = DockStyle.Fill;
        f.Controls.Add(tv);
        Application.Run(f);
    }
}

编辑:当然不要忘记使用/unsafe 进行编译。

最新更新