0
votes

I am using a number of 3rd party ActiveX controls on my form. My application has multiple forms, and say these ActiveX controls are on myAxHostingForm. Moving the mouse over some of the controls gives myAxHostingForm focus. I want to stop this.

I have tried an empty event handler

For Each c In Me.ChildControls(Of AxHost)() ' custom extension method returning controls of type provided
    AddHandler c.MouseMove,
        Sub(s As Object, m As MouseEventArgs)
        End Sub
Next

I get the following exception:

System.NotSupportedException was caught
  HResult=-2146233067
  Message=Event MouseMove is not valid on this ActiveX control.
  Source=System.Windows.Forms
  StackTrace:
       at System.Windows.Forms.AxHost.add_MouseMove(MouseEventHandler value)
       at <my source code file>

I'm hoping someone who knows something about ActiveX hosting in .NET can help make sense of this error, and possibly solve this annoying problem.

Edit: Trying @Hans approach,

<ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000114-0000-0000-C000-000000000046")>
Interface IOleWindow
    <PreserveSig>
    Function GetWindow(ByRef hwnd As IntPtr) As Int32
    Sub ContextSensitiveHelp(ByVal fEnterMode As Int32)
End Interface

Class ActiveXWindow
    Inherits NativeWindow
    Protected Overrides Sub WndProc(ByRef m As Message)
        System.Diagnostics.Debug.WriteLine(m)
        If (m.Msg = &H200) Then Return
        MyBase.WndProc(m)
    End Sub
End Class

this is inside my form load:

Dim itf = CType(CCDimage1.GetOcx, IOleWindow)
Dim hWnd As IntPtr
Dim hr As Integer = itf.GetWindow(hWnd)
If hr <> 0 Or hWnd = IntPtr.Zero Then Throw New Exception("Could not find handle for DataRay window")
Dim wrapper = New ActiveXWindow()
wrapper.AssignHandle(hWnd)

I get the exception on the first line:

System.InvalidCastException was caught
  HResult=-2147467262
  Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'Instruments.IOleWindow'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{00000114-0000-0000-C000-000000000046}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
  Source=Instruments
3

3 Answers

3
votes

Here's another approach you can try using IMessageFilter:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        this.Load += Form1_Load;
    }

    void Form1_Load(object sender, EventArgs e)
    {
        // list out your 3rd party ActiveX controls here:
        Control[] controls = new Control[] { 
            this.axWindowsMediaPlayer1, 
            this.axWindowsMediaPlayer2 
        };
        Application.AddMessageFilter(new MouseMoveFilter(controls));
    }
}

public class MouseMoveFilter : IMessageFilter
{
    private const int WM_MOUSEMOVE = 0x200;

    private List<IntPtr> ControlHandles = new List<IntPtr>();

    public MouseMoveFilter(Control[] controls)
    {
        foreach(Control ctl in controls)
        {
            this.ControlHandles.Add(ctl.Handle);
        }
    }

    public bool PreFilterMessage(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_MOUSEMOVE:
                if (this.ControlHandles.Contains(m.HWnd))
                {
                    return true;
                }
                break;
        }
        return false;
    }
}
1
votes

Yeah, that doesn't work. ActiveX controls create their own window and can party on it as they see fit. Messing with you own app as well, it is rather an excellent malware injection vector. They are supposed to play nice and work with the host but corners are cut frequently.

The only thing you can do is sub-class their window so you'll get a crack at their messages first. First thing you need is to obtain their window handle, that requires using IOleWindow::GetWindow() method. You'll need this declaration:

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000114-0000-0000-C000-000000000046")]
    interface IOleWindow {
        [PreserveSig]
        int GetWindow(out IntPtr hwnd);
        void ContextSensitiveHelp(int fEnterMode);
    }

Then you need to derive your own class from NativeWindow so you can override the WndProc() method and detect the messages. A simple one that filters WM_MOUSEMOVE:

    class ActiveXWindow : NativeWindow {
        protected override void WndProc(ref Message m) {
            System.Diagnostics.Debug.WriteLine(m);
            if (m.Msg == 0x200) return;
            base.WndProc(ref m);
        }
    }

Then you need to put this in place. You have to wait until the native window is created, the form's Load event is normally the right time for that. I used Windows Media Player to test the code:

    private void Form1_Load(object sender, EventArgs e) {
        var itf = (IOleWindow)axWindowsMediaPlayer1.GetOcx();
        IntPtr hWnd;
        int hr = itf.GetWindow(out hWnd);
        if (hr != 0 || hWnd == IntPtr.Zero) throw new Exception("Oh, no");
        var wrapper = new ActiveXWindow();
        wrapper.AssignHandle(hWnd);
    }
1
votes

The ActiveX controls on the native window have handles which are different from the ones seen at Form_Load. So I wasn't able to ignore mouse movement on them explicitly. However, they are the only controls I want to ignore mouse movement for in my application, and they all share the characteristic of Control.FromHandle(m.HWnd) Is Nothing = true. Thanks @Idle_Mind for leading me to this approach.

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Application.AddMessageFilter(New MouseMoveFilter())
End Sub

...

Public Class MouseMoveFilter
    Implements IMessageFilter
    Private Const WM_MOUSEMOVE As Int32 = &H200
    Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
        Select Case m.Msg
            Case WM_MOUSEMOVE
                Return Control.FromHandle(m.HWnd) Is Nothing 
        End Select
        Return False
    End Function
End Class