Page 1 of 1

Catch mouse events from Listbox

Posted: Wed Dec 11, 2013 3:56 pm
by AMitchneck
I'm trying to make a listbox control scroll when a user clicks and drags anywhere on the control. Everything works (I used buttons to simulate mouse movement) except for some reason the listbox control never raises the mousedown, mousemove, or mouseup events. Is there something else I can use to capture mouse events or how can I get the listbox to fire the events?

Listbox control code:

Code: Select all

ListDrag listDrag = new ListDrag();
void Screen_Opened(System.Object sender, System.EventArgs e)
{
	this.m_List.MouseMove += new System.Windows.Forms.MouseEventHandler(List_MouseMove);
}
void List_MouseDown(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
	if (e.Button == MouseButtons.Left)
		listDrag.BeginDrag(this.m_List, e.Y);
}
void List_MouseMove(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
	if (listDrag.DragActive)
		listDrag.DoDrag(this.m_List, e.Y);
}
void List_MouseUp(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
	if (listDrag.DragActive)
	{
		listDrag.DoDrag(this.m_List, e.Y);
		listDrag.EndDrag();
	}
}

private class ListDrag
{
	#region Externals
	[DllImport("coredll.dll")]
	[return: MarshalAs(UnmanagedType.Bool)]
	private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);

	private const int SB_HORZ = 0;
	private const int SB_VERT = 1;
	private const int SIF_RANGE = 0x1;
	private const int SIF_PAGE = 0x2;
	private const int SIF_POS = 0x4;
	private const int SIF_DISABLENOSCROLL = 0x8;
	private const int SIF_TRACKPOS = 0x10;
	private const int SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS;

	private struct SCROLLINFO
	{
		public uint cbSize;
		public uint fMask;
		public int nMin;
		public int nMax;
		public uint nPage;
		public int nPos;
		public int nTrackPos;
	}

	private SCROLLINFO GetInfo(IntPtr Handle)
	{
		SCROLLINFO si = new SCROLLINFO();
		si.cbSize = (uint)Marshal.SizeOf(si);
		si.fMask = (uint)SIF_ALL;
		if (GetScrollInfo(Handle, SB_VERT, ref si))
		{
			if (si.nPage == 0)
			{
				si.nMin = 0;
				si.nMax = 0;
				si.nPos = 0;
			}
			else
			{
				int size = (int)si.nPage - 1;
				if (size < (si.nMax - si.nMin))
					si.nMax -= size;
				else
					si.nMax = si.nMin;
			}
		}
		else
		{
			si.nMin = 0;
			si.nMax = 0;
			si.nPos = 0;
			si.nPage = 0;
		}
		return si;
	}
	#endregion

	private bool Drag;
	private int dragY;
	private int dragPos;

	public ListDrag()
	{
		Drag = false;
	}

	public bool DragActive
	{
		get { return Drag; }
	}
	public bool BeginDrag(Neo.ApplicationFramework.Controls.Controls.ListBox listBox, int Y)
	{
		if (Drag) return false;

		dragY = Y;
		dragPos = GetInfo(listBox.Handle).nPos;
		Drag = true;

		return true;
	}
	public bool DoDrag(Neo.ApplicationFramework.Controls.Controls.ListBox listBox, int Y)
	{
		if (!Drag) return false;

		SCROLLINFO si = GetInfo(listBox.Handle);
		if (si.nPage == 0) return true;

		int startPos = GetPos(listBox, (int)si.nPage, dragY);
		int endPos = GetPos(listBox, (int)si.nPage, Y);

		int pos = dragPos + startPos - endPos;
		if (pos < si.nMin)
			pos = si.nMin;
		else if (pos > si.nMax)
			pos = si.nMax;
		listBox.TopIndex = pos;
                
		return true;
	}
	public void EndDrag()
	{
		Drag = false;
	}

	private int GetPos(Neo.ApplicationFramework.Controls.Controls.ListBox listBox, int nPage, int Y)
	{
		int pos;
		pos = Y - listBox.ClientRectangle.Y;
		pos *= nPage;
		pos /= listBox.ClientRectangle.Height;
		if (Y < listBox.ClientRectangle.Y) pos--;
		return pos;
	}
}

Re: Catch mouse events from Listbox

Posted: Wed Dec 18, 2013 3:21 pm
by AMitchneck
I discovered a solution to my problem...

It seems .NETCF does not directly support mouse events for the ListBox control. The only way to get the event is to capture the windows message directly.
Note: This code sample hooks into the windows message handler and must be unhooked before the window closes. The sample code assumes the name of the listbox in iX Dev. is "listbox".

Code: Select all

MsgProc msgProc;
void Screen_Opened(System.Object sender, System.EventArgs e)
{
	msgProc = new MsgProc(this.m_listbox); // hook into windows message handler
}
void Screen_Closing(System.Object sender, System.ComponentModel.CancelEventArgs e)
{
	// unhook from windows message handler
	msgProc.Dispose();
}

Code: Select all

using System.Runtime.InteropServices;
private class MsgProc : IDisposable
{
	#region Externals
	[DllImport("coredll.dll")]
	[return: MarshalAs(UnmanagedType.Bool)]
	private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);
	[DllImport("coredll.dll")]
	private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
	[DllImport("coredll.dll")]
	private static extern int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, uint msg, uint wParam, int lParam);
	
	private const int GWL_WNDPROC = -4;
	private delegate int WndProc(IntPtr hwnd, uint msg, uint wParam, int lParam);

	private const int SB_HORZ = 0;
	private const int SB_VERT = 1;
	private const int SIF_RANGE = 0x1;
	private const int SIF_PAGE = 0x2;
	private const int SIF_POS = 0x4;
	private const int SIF_DISABLENOSCROLL = 0x8;
	private const int SIF_TRACKPOS = 0x10;
	private const int SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS;

	private struct SCROLLINFO
	{
		public uint cbSize;
		public uint fMask;
		public int nMin;
		public int nMax;
		public uint nPage;
		public int nPos;
		public int nTrackPos;
	}

	private SCROLLINFO GetInfo(IntPtr Handle)
	{
		SCROLLINFO si = new SCROLLINFO();
		si.cbSize = (uint)Marshal.SizeOf(si);
		si.fMask = (uint)SIF_ALL;
		if (GetScrollInfo(Handle, SB_VERT, ref si))
		{
			if (si.nPage == 0)
			{
				si.nMin = 0;
				si.nMax = 0;
				si.nPos = 0;
			}
			else
			{
				int size = (int)si.nPage - 1;
				if (size < (si.nMax - si.nMin))
					si.nMax -= size;
				else
					si.nMax = si.nMin;
			}
		}
		else
		{
			si.nMin = 0;
			si.nMax = 0;
			si.nPos = 0;
			si.nPage = 0;
		}
		return si;
	}
	#endregion
	
	private IntPtr ListDefProc = IntPtr.Zero;
	private Neo.ApplicationFramework.Controls.Controls.ListBox list;
	private WndProc callback = null;
	private bool dragActive;
	private int dragY;
	private int dragPos;
			
	public MsgProc(Neo.ApplicationFramework.Controls.Controls.ListBox listBox)
	{
		dragActive = false;
		list = listBox;
		callback = new WndProc(ListWinProc);
		ListDefProc = SetWindowLong(list.Handle, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(callback));
	}
	~MsgProc()
	{
		Dispose();
	}
	public void Dispose()
	{
		if (ListDefProc.ToInt32() != 0)
		{
			SetWindowLong(list.Handle, GWL_WNDPROC, ListDefProc);
			ListDefProc = IntPtr.Zero;
			dragActive = false;
		}
	}
	
	private int ListWinProc(IntPtr hwnd, uint msg, uint wParam, int lParam)
	{
		int result = CallWindowProc(ListDefProc, hwnd, msg, wParam, lParam);
		
		try
		{
			if (hwnd == list.Handle)
			{
				short Y = (short)(lParam >> 16);
				switch (msg)
				{
					case 0x0201: // WM_LBUTTONDOWN
						if (!dragActive)
						{
							dragY = (int)Y;
							dragPos = list.TopIndex;
							dragActive = true;
						}
						break;
					case 0x0202: // WM_LBUTTONUP
						if (dragActive)
						{
							DoDrag((int)Y);
							dragActive = false;
						}
						break;
					case 0x0200: // WM_MOUSEMOVE
						if (dragActive)
						{
							DoDrag((int)Y);
						}
						break;
				}
			}
		}
		catch { }
		
		return result;
	}
	
	private void DoDrag(int Y)
	{
		SCROLLINFO si = GetInfo(list.Handle);
		if (si.nPage == 0) return;
		
		int pos = dragPos + GetPos((int)si.nPage, dragY) - GetPos((int)si.nPage, Y);
		if (pos < si.nMin)
			pos = si.nMin;
		else if (pos > si.nMax)
			pos = si.nMax;
		list.TopIndex = pos;
	}
	private int GetPos(int nPage, int Y)
	{
		int delta = Y - list.ClientRectangle.Y;
		int pos = (delta * nPage) / list.ClientRectangle.Height;
		if (delta < 0) pos--;
		return pos;
	}
}

Re: Catch mouse events from Listbox

Posted: Tue Aug 12, 2014 8:28 am
by robkwan
Nice trick. But my project is unable to run/simulate on the iX IDE because it throws exception during project opens, due to the WinCE stuffs. The project has multiple screens. I can ignore the screen with the WinCE specific code but want to test other screen from the IDE.
Is there a CF version directive in iX that can be used for conditional compilation?

Re: Catch mouse events from Listbox

Posted: Tue Aug 12, 2014 8:46 am
by AMitchneck
Sadly, I don't know of any flag in iX dev. to indicate debug/run version that can be used to change dll references. I'd also be interested to know if one exists as some script modules in my code reference coredll.dll thus disabling it from running anywhere but on the panel itself.

At least for testing purposes, to enable the listbox scrolling code on a non-WinCE computer you can change the [DllImport("coredll.dll")] references to [DllImport("user32.dll")].

Re: Catch mouse events from Listbox

Posted: Tue Aug 12, 2014 9:03 am
by AMitchneck
Just an FYI - I found my listbox scrolling code stopped working when the panel firmware was updated. My solution was to disable the listbox built-in mouse control and make my code solely control mouse events.

Code: Select all

using System.Runtime.InteropServices;
private class MsgProc : IDisposable
{
	#region Externals
	[DllImport("coredll.dll")]
	[return: MarshalAs(UnmanagedType.Bool)]
	private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);
	[DllImport("coredll.dll")]
	private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
	[DllImport("coredll.dll")]
	private static extern int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, uint msg, uint wParam, int lParam);
	
	private const int GWL_WNDPROC = -4;
	private delegate int WndProc(IntPtr hwnd, uint msg, uint wParam, int lParam);

	private const int SB_HORZ = 0;
	private const int SB_VERT = 1;
	private const int SIF_RANGE = 0x1;
	private const int SIF_PAGE = 0x2;
	private const int SIF_POS = 0x4;
	private const int SIF_DISABLENOSCROLL = 0x8;
	private const int SIF_TRACKPOS = 0x10;
	private const int SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS;

	private struct SCROLLINFO
	{
		public uint cbSize;
		public uint fMask;
		public int nMin;
		public int nMax;
		public uint nPage;
		public int nPos;
		public int nTrackPos;
	}

	private SCROLLINFO GetInfo()
	{
		SCROLLINFO si = new SCROLLINFO();
		si.cbSize = (uint)Marshal.SizeOf(si);
		si.fMask = (uint)SIF_ALL;
		if (GetScrollInfo(list.Handle, SB_VERT, ref si))
		{
			if (si.nPage == 0)
			{
				si.nMin = 0;
				si.nMax = 0;
				si.nPos = 0;
			}
			else
			{
				int size = (int)si.nPage - 1;
				if (size < (si.nMax - si.nMin))
					si.nMax -= size;
				else
					si.nMax = si.nMin;
			}
		}
		else
		{
			si.nMin = 0;
			si.nMax = 0;
			si.nPos = 0;
			si.nPage = 0;
		}
		return si;
	}
	#endregion
	
	private IntPtr ListDefProc = IntPtr.Zero;
	private Neo.ApplicationFramework.Controls.Controls.ListBox list;
	private WndProc callback = null;
	private int dragY;
	private int dragPos;
	
	public MsgProc(Neo.ApplicationFramework.Controls.Controls.ListBox listBox)
	{
		list = listBox;
		list.Capture = false;
		callback = new WndProc(ListWinProc);
		ListDefProc = SetWindowLong(list.Handle, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(callback));
	}
	public void Dispose()
	{
		if (ListDefProc.ToInt32() != 0)
		{
			SetWindowLong(list.Handle, GWL_WNDPROC, ListDefProc);
			ListDefProc = IntPtr.Zero;
			list.Capture = false;
		}
	}
	
	private int ListWinProc(IntPtr hwnd, uint msg, uint wParam, int lParam)
	{
		try
		{
			if (hwnd == list.Handle)
			{
				short Y = (short)(lParam >> 16);
				switch (msg)
				{
					case 0x0201: // WM_LBUTTONDOWN
					case 0x0203: // WM_LBUTTONDBLCLK
						// begin drag
						dragY = (int)Y;
						dragPos = GetInfo().nPos;
						list.Capture = true;
						
						// comment line below if you don't want row to be selected in listbox
						list.SelectedIndex = dragPos;
								
						return 0;
					case 0x0202: // WM_LBUTTONUP
						if (list.Capture)
						{
							// end drag
							DoDrag((int)Y);
							list.Capture = false;
						}
						return 0;
					case 0x0200: // WM_MOUSEMOVE
						if (list.Capture)
						{
							// drag
							DoDrag((int)Y);
						}
						return 0;
				}
			}
		}
		catch { }
		
		return CallWindowProc(ListDefProc, hwnd, msg, wParam, lParam);
	}
	
	private void DoDrag(int Y)
	{
		SCROLLINFO si = GetInfo();
		if (si.nPage == 0) return;
		
		int pos = dragPos + GetPos((int)si.nPage, dragY) - GetPos((int)si.nPage, Y);
		if (pos < si.nMin)
			pos = si.nMin;
		else if (pos > si.nMax)
			pos = si.nMax;
		list.TopIndex = pos;
	}
	private int GetPos(int nPage, int Y)
	{
		int delta = Y - list.ClientRectangle.Y;
		int pos = (delta * nPage) / list.ClientRectangle.Height;
		if (delta < 0) pos--;
		return pos;
	}
}