-1

I'm trying to catch WM_LBUTTONDOWN WM_MOUSEMOVE WM_LBUTTONUP on a WPF UserControl hosted in a ElementHost.

Here's my current code which is not working at all :

 public CoreUI()
{
    InitializeComponent();

    var s = HwndSource.FromVisual(this) as HwndSource;
    s?.AddHook(WndProcHook);
}

   public const int WM_LBUTTONDOWN = 0x201;
   public const int WM_LBUTTONUP = 0x202;
   public const int WM_MOUSEMOVE = 0x200;

static IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {

   switch (m.Msg)
        {

            case WM_LBUTTONDOWN:
            MessageBox.Show("LBUTTONDOWN");
            handled = true;
            break;

            case WM_MOUSEMOVE:
            MessageBox.Show("MOUSEMOVE");
            handled = true;
            break;


            case WM_LBUTTONUP:
            MessageBox.Show("LBUTTONUP");
            handled = true;
            break;

        }
        return IntPtr.Zero;
     }

It's not working , How can I make it work?

  • @AlwaysLearning did you even read the question ? I'm saying it doesn't work at all on usercontrol! –  Sep 01 '19 at 22:00
  • @RossetaGotStoned _"did you even read the question"_ - did you even read the duplicate? For some thing your code doesn't match the other and I can see a possible bug in your code with `s?.AddHook`. You don't seem to be handling this condition properly. Besides, that might not be the best place to perform that action. This isn't WinForms –  Sep 01 '19 at 23:10
  • @RossetaGotStoned The StackOverflow community is here to help you with your programming difficulties. I believe the second link I posted contains the answer to your problem. This should have been one of the suggestions presented to you before submitting your question. – AlwaysLearning Sep 02 '19 at 00:13
  • @MickyD `s?.AddHook` works fine and the KillingByCoding answer was correct. –  Sep 02 '19 at 08:05
  • hence the _"...that might not be the best place to perform that action..."_. Good to hear you got it going –  Sep 02 '19 at 10:19

1 Answers1

1

Before reading this answer consider the following points:

  • In System.Windows.Forms applications every Control is a window with its own window handle (i.e.: Control.Handle)
  • In WPF applications only the top-level window(s) get a window handle, i.e.: Application.Current.MainWindow.
  • WPF user controls are responsible for painting their own content inside their parent window's buffer but they never actually get their own window handle.
  • This is why new WindowInteropHelper(this).Handle and its ilk only works in WPF applications when invoked from a top-level window's code, and only after the window has been completely initialized.

The following code demonstrates how to hook the main window's WndProc from a UserControl...

CoreUI.xaml:

<UserControl x:Class="StackOverflow.CoreUI"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:StackOverflow"
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="100">
    <Grid>
        <TextBox Text="{Binding Text}" />
    </Grid>
</UserControl>

CoreUI.xaml.cs:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;

namespace StackOverflow
{
    public class LogEventArgs : EventArgs {
        public string Message { get; set; }
    }

    public delegate void LogEventHandler(object sender, LogEventArgs e);

    public partial class CoreUI : UserControl
    {
        public string Text { get; set; } = "Hello, world!";
        public LogEventHandler OnLog;

        public CoreUI()
        {
            InitializeComponent();
            DataContext = this;
        }

        public void HookWndProc(Window window)
        {
            var source = HwndSource.FromVisual(window) as HwndSource;
            source.AddHook(new HwndSourceHook(WndProc));
        }

        protected IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            MouseButtonEventArgs args;

            const int WM_LBUTTONDOWN = 0x0201;
            const int WM_LBUTTONUP = 0x0202;
            const int WM_MOUSEMOVE = 0x0200;
            ////Not required...
            //// REF: https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest
            //const int WM_NCHITTEST = 0x0084;
            //const int HTCLIENT = 0x0001;

            switch (msg)
            {
                ////Not required...
                //case WM_NCHITTEST:
                //    {
                //        handled = true;
                //        return new IntPtr(HTCLIENT);
                //    }
                case WM_LBUTTONDOWN:
                    {
                        OnLog?.Invoke(this, new LogEventArgs() { Message = "LBUTTONDOWN" });
                        handled = true;
                        break;
                    }

                case WM_LBUTTONUP:
                    {
                        OnLog?.Invoke(this, new LogEventArgs() { Message = "LBUTTONUP" });
                        handled = true;
                        break;
                    }

                case WM_MOUSEMOVE:
                    {
                        OnLog?.Invoke(this, new LogEventArgs() { Message = "MOUSEMOVE" });
                        handled = true;
                        break;
                    }
            }
            return IntPtr.Zero;
        }
    }
}

MainWindow.xaml:

<Window x:Class="StackOverflow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StackOverflow"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:CoreUI x:Name="coreUI" Height="30" VerticalAlignment="Top"/>
        <TextBox x:Name="textBox" Margin="0,30,0,0" TextWrapping="Wrap" Text="TextBox"/>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.Windows;

namespace StackOverflow
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            textBox.Clear();
            coreUI.OnLog += new LogEventHandler(OnLogHandler);
        }

        protected void OnLogHandler(object sender, LogEventArgs e)
        {
            textBox.AppendText(e.Message + "\r\n");
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            coreUI.HookWndProc(App.Current.MainWindow);
        }
    }
}

Note the use of OnSourceInitialized(). The main window will not have been allocated a window handle yet if you try this from OnInitialized().

The UserControl (CoreUI) receives WM_LBUTTONDOWN, WM_LBUTTONUP and WM_MOUSEMOVE events for the entire application window. If your UserControl does not fill the entire application window then it will be your control's responsibility to filter these events down to itself by checking the mouse coordinates against its own bounds.

AlwaysLearning
  • 7,915
  • 5
  • 27
  • 35