Is there anyway to set global hotkey for a mouse button?

83 views Asked by At

I want to see if the user pressed for example mouse button left + ctrl key and call an event for it.

I've tried this and it didn't work:

[DllImport("user32.dll")]
static extern short GetAsyncKeyState(int VirtualKeyPressed);

while (true)
{
  if (GetAsyncKeyState(0x01)>0)
  {
     DoJob();
  }
  else
  {
  }
}

the problem with this code was it would capture it the first time but it wouldnt the second time (this was runing in a while loop)

2

There are 2 answers

0
egeer On

I believe that you can do what you are trying to accomplish with a Window.InputBinding and a MouseBinding attached to something like Gesture="Ctrl+LeftClick".

If you take this approach, you can avoid using user32.dll and having to handle detecting the input in the code-behind.

Here is a simple example of binding to a command to an input for an entire Window:

    <Window.InputBindings>
        <MouseBinding Command="{Binding CtrlLeftClickCommand}"
                      Gesture="Ctrl+LeftClick">
        </MouseBinding>
    </Window.InputBindings>

ViewModel

public class ViewModel
{
    public ICommand CtrlLeftClickCommand { get; set; } = new CtrlLeftClickCommand();
    // etc...
}

ICommand implementation, just in case you aren't familiar with this approach. If you wanted, you can also pass some sort of parameter via binding as well.

public class CtrlLeftClickCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter) => true;
    public void Execute(object parameter)
    {
        DoJob();
    }
    private void DoJob() => MessageBox.Show("Job Done!");
}
0
BionicCode On

In WPF you would define either a class level KeyBinding, MouseBinding, CommandBinding or a routed event class handler (e.g. for the PreviewMouseLeftButtonUp routed event). If you register it with the application's visual root type Window, you have a global input gesture.

The following examples register an application wide gesture Ctrl + LMousebutton with starting a process (for demonstration purpose).

Solution 1

You can define and handle a class level CommandBinding (recommended) or InputBinding.

Normally you would add the CommandBinding to the defining UIElement.Commandings collection to register the binding with the instance.
But because the command must be global, we must register it with a type instead of an instance. This is because an application can have multiple Window instances where each defines the root of its own visual tree. Now, instead of having to register a CommandBinding with every instance explicitly, we register the binding once for the Window type with the help of WPF's CommandManager helper class:

MainWindow.xaml.cs

partial class MainWindow : Window
{
  // Define as public so that the command can be used directly e.g., with a Button
  public static RoutedCommand StartProcessCommand { get; }

  public static MainWindow()
  {
    // Initialize a routed command with associated input gestures.
    // In this case the input gesture is a MouseGesture with a pressed modifier key (Ctrl).
    // Alternative track the CTRL key and create a KeyGesture instead.
    // Then, when executing the command, 
    // we can check if 'Mouse.LeftButton == MouseButtonState.Pressed' 
    var startProcessHotKey = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
    MainWindow.StartProcessCommand = new RoutedCommand(nameof(MainWindow.StartProcessCommand), typeof(MainWindow));
    _ = MainWindow.StartProcessCommand.InputGestures.Add(startProcessHotKey);
  }
   
  public MainWindow()
  {
    InitializeComponent();

    // Create a CommandBinding to register the input event handlers
    var startProcessCommandBinding = new CommandBinding(
      MainWindow.StartProcessCommand, 
      ExecutedStartProcessCommand, 
      CanExecuteStartProcessCommand);

    // Register the command with every Window of this application.
    // The Window is always the root of a visual tree.
    CommandManager.RegisterClassCommandBinding(typeof(Window), startProcessCommandBinding);
  }

  private void CanExecuteStartProcessCommand(object sender, CanExecuteRoutedEventArgs e) 
    => e.CanExecute = true;

  private void ExecutedStartProcessCommand(object sender, ExecutedRoutedEventArgs e)
  {
    // TODO::Execute StartProcessCommand
  }
}

MainWindow.xaml
Ctrl + LMouseButton will execute the MainWindow.StartProcessCommand from every Window instance of the current application.
In addition, the StartProcessCommand can also be invoked explicitly e.g. by a Button:

<Window>
  <Button Command="{x:Static MainWindow.StartProcessCommand}" />
</Window>

Solution 2

You can register a class handler for the mouse down event and check if the relevant keys are pressed. You do this with the help of WPF'S EventManager helper class.
The code is significantly shorter, but you lose functionality or flexibility, like global commanding and routing of the explicit command gesture that allows every element to handle it (without havig to explicitly check for the exact input gesture).

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();

    EventManager.RegisterClassHandler(
      typeof(Window), 
      PreviewMouseLeftButtonUpEvent, 
      new RoutedEventHandler(OnGlobalPreviewMouseLeftButtonUp));
  }

  private void OnGlobalPreviewMouseLeftButtonUp(object sender, RoutedEventArgs e)
  {
    if (Keyboard.IsKeyDown(Key.LeftCtrl))
    {
      // TODO::Execute global input gesture
    }
  }
}