TVN_SELCHANGING Message in c# - c#

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

Related

How to force ComboBox to scroll to top?

I have a winforms ToolStripComboBox with a ComboBox property. By default, it seems to auto-scroll to the selected index. See screenshot below:
On form load, I'm setting SelectedIndex to 1, which is what needs to happen. But I want the first item in the list (SelectedIndex 0) to be visible, or in other words auto-scroll to the very top. I can't find any way to force the combobox to scroll to the top by default, or to do so programmatically. There is an AutoScrollOffset property on ComboBox which I have experimented with, but it seems to do nothing, no matter what I set it to.
As seen in my screenshot above, I want to force the combobox (either via property or method call) to appear like the 2nd pic in which the top item (All - All Categories) is visible, while still leaving index 1 selected.
How can this be done?
When you open the dropdown, a LB_SETTOPINDEX message will be sent to the list which is in the dropdown menu. This message is responsible to setting the top index in the list.
You can handle this message and change its WParam to Intptr.Zero to always use 0 as top index.
Native Methods
Here is a class which contains native methods, structures and constants to manipulate the combo box for this purpose:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO
{
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public int stateButton;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; public int Top; public int Right; public int Bottom;
}
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
public class ListBoxHelper : NativeWindow
{
private const int LB_SETTOPINDEX = 0x0197;
public ListBoxHelper(IntPtr hwnd) { this.AssignHandle(hwnd); }
protected override void WndProc(ref Message m)
{
if (m.Msg == LB_SETTOPINDEX)
m.WParam = IntPtr.Zero;
base.WndProc(ref m);
}
}
}
ComboBox
Here is a ComboBox which its dropdown always opens showing item 0 as top item:
public class MyComboBox : ComboBox
{
NativeMethods.ListBoxHelper listBoxHelper;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
var info = new NativeMethods.COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
NativeMethods.GetComboBoxInfo(this.Handle, ref info);
listBoxHelper = new NativeMethods.ListBoxHelper(info.hwndList);
}
}
ToolStripComboBox
ToolStripComboBox hosts a ComboBox inside. So the solution is similar:
public class MyToolStripComboBox : ToolStripComboBox
{
public MyToolStripComboBox()
{
this.Control.HandleCreated += Control_HandleCreated;
}
NativeMethods.ListBoxHelper listBoxHelper;
private void Control_HandleCreated(object sender, EventArgs e)
{
base.OnVisibleChanged(e);
var info = new NativeMethods.COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
NativeMethods.GetComboBoxInfo(this.Control.Handle, ref info);
listBoxHelper = new NativeMethods.ListBoxHelper(info.hwndList);
}
}

How to create and fire a Gdk.EventKey

I have a Gtk.Window which overrides OnKeyPressEvent
protected override bool OnKeyPressEvent(Gdk.EventKey evt)
{
Console.WriteLine("Captured key " + evt.Key);
return base.OnKeyPressEvent(evt);
}
So I'm able to catch "real" keyboard actions.
Now I'd like to create something like a virtual keyboard (constists of simple buttons). But I didn't find any information about firing own key events.
Can someone tell me how I can trigger a key press programmatically with GTK# so that the action is handled in my OnKeyPressEvent?
Finally I found a solution for that.
If you know an other solution (maybe without native code), please tell me :)
[StructLayout(LayoutKind.Sequential)]
public struct EventKeyStruct
{
public EventType type;
public IntPtr window;
public sbyte send_event;
public uint time;
public uint state;
public uint keyval;
public uint length;
public string str;
public ushort hardware_keycode;
public byte group;
public uint is_modifier;
}
public static void SendKeyEvent(Gtk.Widget widget, Gdk.Key key)
{
uint keyval = (uint)key;
Gdk.Window window = widget.GdkWindow;
Gdk.KeymapKey[] keymap = Gdk.Keymap.Default.GetEntriesForKeyval(keyval);
EventKeyStruct native = new EventKeyStruct();
native.type = Gdk.EventType.KeyPress;
native.window = window.Handle;
native.send_event = 1;
native.state = (uint)Gdk.EventMask.KeyPressMask;
native.keyval = keyval;
native.length = 0;
native.str = null;
native.hardware_keycode = (ushort)keymap[0].Keycode;
native.group = (byte)keymap[0].Group;
IntPtr ptr = GLib.Marshaller.StructureToPtrAlloc(native);
try
{
EventKey evnt = new EventKey(ptr);
EventHelper.Put(evnt);
}
finally
{
//GLib.Marshaller.Free(ptr); //comment because otherwise it crashes here?
}
}

Middle button click to scroll

How do I achieve this in a WinForms container control when the scroll bars are visible?
Highlighted here (Google Chrome browser):
EDIT: This cursor is the only one that is visible on a screenshot. I hope it's clear what i mean.
EDIT:
Tried this on my control. Does not work.
const int WM_MBUTTONDOWN = 0x207;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MBUTTONDOWN)
DefWndProc(ref m);
else
base.WndProc(ref m);
}
Here's what I have so far. It exits "reader mode" if I release the middle button, and I haven't implemented scrolling within the control (I used a textbox), but it may give you something to start with.
[DllImport("comctl32.dll", SetLastError=true, EntryPoint="#383")]
static extern void DoReaderMode(ref READERMODEINFO prmi);
public delegate bool TranslateDispatchCallbackDelegate(ref MSG lpmsg);
public delegate bool ReaderScrollCallbackDelegate(ref READERMODEINFO prmi, int dx, int dy);
[StructLayout(LayoutKind.Sequential)]
public struct READERMODEINFO
{
public int cbSize;
public IntPtr hwnd;
public int fFlags;
public IntPtr prc;
public ReaderScrollCallbackDelegate pfnScroll;
public TranslateDispatchCallbackDelegate fFlags2;
public IntPtr lParam;
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public UInt32 message;
public IntPtr wParam;
public IntPtr lParam;
public UInt32 time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left, top, right, bottom;
}
private bool TranslateDispatchCallback(ref MSG lpMsg)
{
return false;
}
private bool ReaderScrollCallback(ref READERMODEINFO prmi, int dx, int dy)
{
// TODO: Scroll around within your control here
return false;
}
private void EnterReaderMode()
{
READERMODEINFO readerInfo = new READERMODEINFO
{
hwnd = this.textBox1.Handle,
fFlags = 0x00,
prc = IntPtr.Zero,
lParam = IntPtr.Zero,
fFlags2 = new TranslateDispatchCallbackDelegate(this.TranslateDispatchCallback),
pfnScroll = new ReaderScrollCallbackDelegate(this.ReaderScrollCallback)
};
readerInfo.cbSize = Marshal.SizeOf(readerInfo);
DoReaderMode(ref readerInfo);
}
private void textBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Middle)
{
EnterReaderMode();
}
}
The RichTextBox control does it by default when you press the mouse wheel button.
Edit: Sorry I misunderstood and thought you were asking about doing it within a textbox not a container control

Global Hotkey in Mono and Gtk#

I'm trying to get a global hotkey working in Linux using Mono. I found the signatures of XGrabKey and XUngrabKey, but I can't seem to get them working. Whenever I try to invoke XGrabKey, the application crashes with a SIGSEGV.
This is what I have so far:
using System;
using Gtk;
using System.Runtime.InteropServices;
namespace GTKTest
{
class MainClass
{
const int GrabModeAsync = 1;
public static void Main(string[] args)
{
Application.Init();
MainWindow win = new MainWindow();
win.Show();
// Crashes here
XGrabKey(
win.Display.Handle,
(int)Gdk.Key.A,
(uint)KeyMasks.ShiftMask,
win.Handle,
true,
GrabModeAsync,
GrabModeAsync);
Application.Run();
XUngrabKey(
win.Display.Handle,
(int)Gdk.Key.A,
(uint)KeyMasks.ShiftMask,
win.Handle);
}
[DllImport("libX11")]
internal static extern int XGrabKey(
IntPtr display,
int keycode,
uint modifiers,
IntPtr grab_window,
bool owner_events,
int pointer_mode,
int keyboard_mode);
[DllImport("libX11")]
internal static extern int XUngrabKey(
IntPtr display,
int keycode,
uint modifiers,
IntPtr grab_window);
}
public enum KeyMasks
{
ShiftMask = (1 << 0),
LockMask = (1 << 1),
ControlMask = (1 << 2),
Mod1Mask = (1 << 3),
Mod2Mask = (1 << 4),
Mod3Mask = (1 << 5),
Mod4Mask = (1 << 6),
Mod5Mask = (1 << 7)
}
}
Does anyone have a working example of XGrabKey?
Thanks!
Well, I finally found a working solution in managed code. The SIGSEGV was happening because I was confusing the handles of the unmanaged Gdk objects with the handles of their X11 counterparts. Thanks to Paul's answer, I was able to find an unmanaged example of global hotkeys and familiarized myself with how it worked. Then I wrote my own unmanaged test program to find out what I needed to do without having to deal with any managed idiosyncrasies. After that was successful, I created a managed solution.
Here is the managed solution:
public class X11Hotkey
{
private const int KeyPress = 2;
private const int GrabModeAsync = 1;
private Gdk.Key key;
private Gdk.ModifierType modifiers;
private int keycode;
public X11Hotkey(Gdk.Key key, Gdk.ModifierType modifiers)
{
this.key = key;
this.modifiers = modifiers;
Gdk.Window rootWin = Gdk.Global.DefaultRootWindow;
IntPtr xDisplay = GetXDisplay(rootWin);
this.keycode = XKeysymToKeycode(xDisplay, (int)this.key);
rootWin.AddFilter(new Gdk.FilterFunc(FilterFunction));
}
public event EventHandler Pressed;
public void Register()
{
Gdk.Window rootWin = Gdk.Global.DefaultRootWindow;
IntPtr xDisplay = GetXDisplay(rootWin);
XGrabKey(
xDisplay,
this.keycode,
(uint)this.modifiers,
GetXWindow(rootWin),
false,
GrabModeAsync,
GrabModeAsync);
}
public void Unregister()
{
Gdk.Window rootWin = Gdk.Global.DefaultRootWindow;
IntPtr xDisplay = GetXDisplay(rootWin);
XUngrabKey(
xDisplay,
this.keycode,
(uint)this.modifiers,
GetXWindow(rootWin));
}
private Gdk.FilterReturn FilterFunction(IntPtr xEvent, Gdk.Event evnt)
{
XKeyEvent xKeyEvent = (XKeyEvent)Marshal.PtrToStructure(
xEvent,
typeof(XKeyEvent));
if (xKeyEvent.type == KeyPress)
{
if (xKeyEvent.keycode == this.keycode
&& xKeyEvent.state == (uint)this.modifiers)
{
this.OnPressed(EventArgs.Empty);
}
}
return Gdk.FilterReturn.Continue;
}
protected virtual void OnPressed(EventArgs e)
{
EventHandler handler = this.Pressed;
if (handler != null)
{
handler(this, e);
}
}
private static IntPtr GetXWindow(Gdk.Window window)
{
return gdk_x11_drawable_get_xid(window.Handle);
}
private static IntPtr GetXDisplay(Gdk.Window window)
{
return gdk_x11_drawable_get_xdisplay(
gdk_x11_window_get_drawable_impl(window.Handle));
}
[DllImport("libgtk-x11-2.0")]
private static extern IntPtr gdk_x11_drawable_get_xid(IntPtr gdkWindow);
[DllImport("libgtk-x11-2.0")]
private static extern IntPtr gdk_x11_drawable_get_xdisplay(IntPtr gdkDrawable);
[DllImport("libgtk-x11-2.0")]
private static extern IntPtr gdk_x11_window_get_drawable_impl(IntPtr gdkWindow);
[DllImport("libX11")]
private static extern int XKeysymToKeycode(IntPtr display, int key);
[DllImport("libX11")]
private static extern int XGrabKey(
IntPtr display,
int keycode,
uint modifiers,
IntPtr grab_window,
bool owner_events,
int pointer_mode,
int keyboard_mode);
[DllImport("libX11")]
private static extern int XUngrabKey(
IntPtr display,
int keycode,
uint modifiers,
IntPtr grab_window);
#if BUILD_FOR_32_BIT_X11
[StructLayout(LayoutKind.Sequential)]
internal struct XKeyEvent
{
public short type;
public uint serial;
public short send_event;
public IntPtr display;
public uint window;
public uint root;
public uint subwindow;
public uint time;
public int x, y;
public int x_root, y_root;
public uint state;
public uint keycode;
public short same_screen;
}
#elif BUILD_FOR_64_BIT_X11
[StructLayout(LayoutKind.Sequential)]
internal struct XKeyEvent
{
public int type;
public ulong serial;
public int send_event;
public IntPtr display;
public ulong window;
public ulong root;
public ulong subwindow;
public ulong time;
public int x, y;
public int x_root, y_root;
public uint state;
public uint keycode;
public int same_screen;
}
#endif
}
And here is the test program:
public static void Main (string[] args)
{
Application.Init();
X11Hotkey hotkey = new X11Hotkey(Gdk.Key.A, Gdk.ModifierType.ControlMask);
hotkey.Pressed += HotkeyPressed;;
hotkey.Register();
Application.Run();
hotkey.Unregister();
}
private static void HotkeyPressed(object sender, EventArgs e)
{
Console.WriteLine("Hotkey Pressed!");
}
I'm not sure how the XKeyEvent structure will behave on other systems with different sizes for C ints and longs, so whether this solution will work on all systems remains to be seen.
Edit: It looks like this solution is not going to be architecture-independent as I feared, due to the varying nature of the underlying C type sizes. libgtkhotkey looks promising as way to avoid deploying and compiling custom unmanaged libraries with your managed assemblies.
Note: Now you need to explicity define BUILD_FOR_32_BIT_X11 or BUILD_FOR_64_BIT_X11 depending on the word-size of your OS.
I'm new to this site and it seems that I can't leave a comment on a previous question as I have insufficient reputation. (Sorry I can't even up-vote you!)
Relating to the issue of differing underlying sizes, I think this is solvable by using an IntPtr for the longs. This follows a suggestion in the Mono project documentation, see http://www.mono-project.com/Interop_with_Native_Libraries#Longs. The C types int and Bool should map to C# int.
Regarding the GAPI wrapper, I tried it, but couldn't get it working. If Zach could post any info on how he did it, I'd be grateful.
Also I couldn't get the sample program to work. Like SDX2000, I had to edit the library names, and I added using statements. I had a problem with Application.Init(), which in the end I swapped for creating a Form. But still my register call fails with BadRequest. If anyone who has got this working can update the code to make it more complete I'd be grateful.
Tomboy has some code that knows how to do this, I'd take the code from there.

How I can replace the command of the minimize button?

First, sorry for my bad english :)
Second, I can know when the form is being moved/resized, using this code:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
WINDOWPOS winPos = new WINDOWPOS();
winPos = (WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));
//Here I just need to change the values of the WINDOWPOS structure
Marshal.StructureToPtr(winPos, m.LParam, true);
}
}
The WM_WINDOWPOSCHANGING message is sent also when the user is minimizing or maximizing the window. But how I can know when the user is maximizing/minimizing, not moving/resizing? I tried get the WindowState property, but it didn't work :(
The code of the WINDOWPOS structure is:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
Any help?
You get WM_SYSCOMMAND when the user clicks one of the buttons in the title bar: http://msdn.microsoft.com/en-us/library/ms646360(VS.85).aspx
You can trap the WM_SYSCOMMAND by overriding WndProc(). But it can easily be done as well with an event handler for the Resize event:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
mPrevState = this.WindowState;
}
FormWindowState mPrevState;
protected override void OnResize(EventArgs e) {
base.OnResize(e);
if (mPrevState != this.WindowState) {
mPrevState = this.WindowState;
// Do something
//..
}
}
}

Categories

Resources