0

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?

Velcro
  • 546
  • 1
  • 8
  • 27
VincentC
  • 245
  • 4
  • 14

1 Answers1

2

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);
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you very much Hans for the explanation! I did however already try adding the WM_REFLECT and it still doesn't work... Is there anything else that could be wrong? – VincentC Jan 09 '13 at 18:47
  • I tested it before I posted, just to make sure. You do need to get rid of the A versions of these messages, Winforms is pure Unicode. – Hans Passant Jan 09 '13 at 19:28
  • Thanks again Hans for telling me about the Unicode stuff! After some more debugging I’m really unsure why this isn’t working for me. I'm using Visual Studio 2012 and tried 32 bit & 64bit. I’ve also tried printing out the action codes and I’m not seeing any of the predefined const values (I did get rid of the A ones). So I thought maybe my const values for TVN_... are wrong? I then tried to print out the ptDrag POINT struct and this one is also wrong, it gives me 453628288 for X and 0 for Y. Could it be that some of my structs are defined incorrectly? – VincentC Jan 09 '13 at 21:04
  • I did double check them but did you copy my code and added WM_REFLECT to it? Or did you change more stuff? I've uploaded my .cs file here: https://dl.dropbox.com/u/43593157/TreeviewEx.cs. Maybe you can check if my code works on your pc? – VincentC Jan 09 '13 at 21:05
  • You were using the wrong field. Don't take the shortcut you made, always cast to NMHDR first. I updated the code snippet to show the correct way. – Hans Passant Jan 09 '13 at 21:35
  • Thanks a lot Hans! That indeed has fixed it. I am however even more confused than before. :) If you still have the patience would you mind explaining me this, please? Why do I have to marshal the lParam to a NMHDR struct first? I was under the impression that the .code member of the struct was used for [common notifications](http://msdn.microsoft.com/en-us/library/windows/desktop/bb775514(v=vs.85).aspx) and not TVN_SELCHANGED. Now there seems to be no reason to cast the lParam to a [NMTREEVIEW](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773411(v=vs.85).aspx) anymore. – VincentC Jan 09 '13 at 22:29
  • 1
    There are lots of TVN_XXX messages, not all of them use a NMTREEVIEW. Like TVN_BEGINLABELEDIT, it uses a NMTVDISPINFO. So first find out what TVN message you got by casting to NMHDR. If you recognize the NMHDR.code *then* cast to the correct structure type. Core mistake you made was never looking at the code field. The action field encodes something entirely different (TVC_BYKEYBOARD etc). – Hans Passant Jan 09 '13 at 22:36