Вопрос

I’m trying to catch TVN_SELCHANGING message from a TreeView. I know there is also the BeforeSelect event but I’d like to understand why I’m not able to catch the message…

I’ve read on msdn the TVN_SELCHANG(ED)(ING) LParam is a pointer to a NMTREEVIEW structure. Also that the code is sent in the form of a WM_NOTIFY message.

So I’ve tried to implement it: (this helped me)

public partial class TreeviewEx : TreeView
{
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct TVITEM
    {
        public uint mask;
        public IntPtr hItem;
        public uint state;
        public uint stateMask;
        public IntPtr pszText;
        public int cchTextMax;
        public int iImage;
        public int iSelectedImage;
        public int cChildren;
        public IntPtr lParam;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct NMTREEVIEW
    {
        public NMHDR hdr;
        public int action;
        public TVITEM itemOld;
        public TVITEM itemNew;
        public POINT ptDrag;
    }

    private const int TVN_FIRST = -400;
    private const int TVN_SELCHANGINGA = (TVN_FIRST - 1);
    private const int TVN_SELCHANGINGW = (TVN_FIRST - 50);
    private const int TVN_SELCHANGEDA = (TVN_FIRST - 2);
    private const int TVN_SELCHANGEDW = (TVN_FIRST - 51);

    private const int WM_NOTIFY = 0x004e;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_NOTIFY)
        {
            var notify = (NMTREEVIEW)Marshal.PtrToStructure(m.LParam, typeof(NMTREEVIEW));
            if (notify.action == TVN_SELCHANGINGA)
            {
                MessageBox.Show("changing");
            }
        }
        base.WndProc(ref m);
    }

I've tried all actions, but none of them seem to work. What am I doing wrong?

Это было полезно?

Решение

Right, this doesn't work. Lots of history behind it, the native Windows controls were designed to be used in C programs. Using Petzold's "Programming Windows" style coding where you put the custom logic for a window in the window procedure. And just used a control like TreeView as-is. Accordingly, these controls send their notification messages to the parent window. Because that's where you put your code.

That's not very compatible with the way modern GUI code is written. Particularly the notion of inheriting a control to give it new behavior. Like you did with your TreeViewEx class. You really want to get these notifications in your own class first. So you can do interesting things with OnBeforeSelect() to customize the behavior of the control. Now having this message sent to the parent is rather a big problem, a control should never be aware of its parent's implementation.

Winforms fixes this problem, it reflects the message from the parent window back to the original window. Altering the message, necessary so it is completely clear that it is a reflected message. Which it does by adding a constant to the message number, WM_REFLECT, a value that you can hardcode to 0x2000. So fix it like this:

private const int WM_REFLECT = 0x2000;

protected override void WndProc(ref Message m) {
    if (m.Msg == WM_REFLECT + WM_NOTIFY) {
        var nmhdr = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
        if (nmhdr.code == TVN_SELCHANGINGW) {
           var notify = (NMTREEVIEW)Marshal.PtrToStructure(m.LParam, typeof(NMTREEVIEW));
           // etc..
        }
    }
    base.WndProc(ref m);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top