Hi there,
I went down a very similar route to you, my aim was to capture mouse clicks rather than drags though there's no essential difference.
I was using the C# libvlc bindings as opposed to the ActiveX control, which may be a better choice for you, avoiding the extra layer of unnecessary COM DLL hell, but from the point of view of the running application again there is no difference.
Basically VLC creates child windows within the control on which it draws its DirectX YUV surfaces or whatever, to hook mouse input you simply need to get at those windows messages.
As you mentioned this can be done using a _Local_ mouse hook (since Global or thread injecting hooks aren't supported directly in Managed Code without incurring various oversized cans of worms). I consider any use of the Windows Unmanaged API to be hacky but system hooks are considered hacky even from a C++ standpoint since they involve DLL injection.
Anyway I implemented this myself and it worked perfectly fine running on a windows form. However I was writing a web browser plugin for Internet Explorer and when running on the page the hooks simply did not work - it turns out that this is due to the enhanced security model of Internet Explorer 7 on Windows XP and the entirety of Windows Vista - basically mouse hooks would throw up one of those Allow/Deny dialogs and probably wouldn't work. Nevertheless it was only after juggling around with mouse coordinates on multiple monitors that I decided to abandon that method.
The "proper" way (from a Windows API standpoint) is to simply add a listener to the message queue of the windows whose messages you want to capture. This can be done rather simply by creating a class inheriting from the NativeWindow class and overriding the WndProc function to intercept the messages you are interested in. Then attach instances of this class to all the handles of the VLC control, straightforward enough using EnumChildWindows or whatever it is in .NET (the fact that you're using VLC means you're going unmanaged anyway).
For reasons buried somewhere deep in MSDN you will have to manually translate the WM_SETCURSOR messages into clicks and drags yourself since the WM_?BUTTON* ones don't seem to percolate through. Remember kids, a click is a button down followed by a button up, both within your target rectangle, without interruptions or a loss of context...
Working code, right. The following is the contents of my InnerVlcWindow.cs which is used in the C# libvlc bindings (which again I strongly recommend you switch to to avoid further grief). I make no guarantees about the quality or sanity of this code since it was the 2^(n+1)th revision and I was getting rather impatient.
Code: Select all
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace VLanControl
{
public delegate void iMouseEventHandler();
public partial class InnerVlcWindow : UserControl
{
private class SubclassHWND : NativeWindow
{
public SubclassHWND(iMouseEventHandler handleRClick, iMouseEventHandler handleDBLClick)
{
this.onRClick = handleRClick;
this.onDBLClick = handleDBLClick;
}
private iMouseEventHandler onRClick;
private iMouseEventHandler onDBLClick;
private const int WM_RBUTTONUP = 0x205;
private const int WM_LBUTTONUP = 0x202;
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_MOUSEMOVE = 0x200;
[DllImport("user32.dll")]
private static extern int GetDoubleClickTime();
private UInt32 LastClick = UInt32.MinValue;
private UInt32 CurClick = UInt32.MinValue;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_PARENTNOTIFY:
case WM_MOUSEACTIVATE:
case WM_NCHITTEST:
break;
case WM_SETCURSOR:
int MsgMouse = (m.LParam.ToInt32() >> 16);
switch (MsgMouse)
{
case WM_RBUTTONUP:
LastClick = UInt32.MinValue;
onRClick.Invoke();
break;
case WM_LBUTTONDOWN:
break;
case WM_LBUTTONUP:
CurClick = (UInt32) System.Environment.TickCount;
if ((CurClick - LastClick) <= (UInt32) GetDoubleClickTime())
{
LastClick = UInt32.MinValue;
onDBLClick.Invoke();
}
else
{
LastClick = CurClick;
}
break;
default:
LastClick = UInt32.MinValue;
break;
}
break;
default:
base.WndProc(ref m);
break;
}
}
}
private Dictionary<int, SubclassHWND> messageHandlers = new Dictionary<int, SubclassHWND>();
private const int WM_NCHITTEST = 0x84;
private const int WM_SETCURSOR = 0x20;
private const int WM_DESTROY = 0x2;
private const int WM_PAINT = 0xF;
private const int WM_MOUSEACTIVATE = 0x21;
private const int WM_PARENTNOTIFY = 0x210;
[DllImport("user32.dll")]
private static extern int EnableWindow(int hwnd, int fEnable);
[DllImport("User32.dll")]
public static extern Boolean EnumChildWindows(int hWndParent,Delegate lpEnumFunc,int lParam);
public delegate int Callback(int hWnd,int lParam);
[DllImport("User32", CharSet = CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int Index);
[DllImport("User32", CharSet = CharSet.Auto)]
private static extern int SetWindowLong(IntPtr hWnd, int Index, int Value);
private const int GWL_EXSTYLE = -20;
private const int WS_EX_NOPARENTNOTIFY = 0x4;
public iMouseEventHandler OnRClick;
public iMouseEventHandler OnDBClick;
public InnerVlcWindow()
{
InitializeComponent();
//this.Enabled = false;
checkChildren();
}
public int EnumChildGetValue(int hWnd,int lParam)
{
if (!(messageHandlers.ContainsKey(hWnd)))
{
//EnableWindow(hWnd, 0);
SubclassHWND curHandler = new SubclassHWND(new iMouseEventHandler(
delegate() { if (OnRClick != null) OnRClick.Invoke(); }
), new iMouseEventHandler(
delegate() { if (OnDBClick != null) OnDBClick.Invoke(); }
));
IntPtr pthWnd = new IntPtr(hWnd);
curHandler.AssignHandle(pthWnd);
messageHandlers.Add(hWnd, curHandler);
Debug.Print("Added for {0}", hWnd);
int exStyle = GetWindowLong(pthWnd, GWL_EXSTYLE);
exStyle |= WS_EX_NOPARENTNOTIFY;
SetWindowLong(pthWnd, GWL_EXSTYLE, exStyle);
}
return 1;
}
private void checkChildren() {
int[] tmpHandles = new int[messageHandlers.Count];
messageHandlers.Keys.CopyTo(tmpHandles, 0);
foreach (int curHandle in tmpHandles)
{
EnumChildWindows(curHandle, new Callback(EnumChildGetValue), 0);
}
EnumChildWindows(this.Handle.ToInt32(),new Callback(EnumChildGetValue),0);
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
//Debug.Print(m.ToString());
if (m.Msg == WM_PAINT)
{
checkChildren();
}
base.WndProc(ref m);
}
}
}