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

时间:2012-05-16 11:59:42

标签: c# winapi user-controls treeview

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

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

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 TreeView控件有一个click事件。这只是第一次测试。后来我想启用TVS_EX_MULTISELECT样式。由于.NET TreeView控件在启用AfterSelect时不会触发任何TVS_EX_MULTISELECT事件,因此我想稍后调查TVN_SELCHANGEDTVN_ITEMCHANGED通知的行为。

3 个答案:

答案 0 :(得分:4)

不是那么简单。检查MSDN article,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事件的生成方式。执行此操作的代码也适用于本机控件中的一些怪癖,因此请确保在提交使用它之前确实需要它。如果您想知道发生了什么,请查看参考源。

答案 1 :(得分:2)

通知发送给控件的父级:

  

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

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

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

  

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

答案 2 :(得分:0)

MSDN中提到了两件重要的事情: 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编译。