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);
}