Home > OS >  Is it possible to bind a key to the Click event of a button?
Is it possible to bind a key to the Click event of a button?

Time:11-29

I am building a calculator App on WPF and I would like to bind the NumPad Keys to their respective buttons since up until now the only way of using the App is by clicking with the mouse on every button and it is not ideal.

This is the first time working with WPF so I've been searching for an answer but couldn't find anything useful.

I did try adding this to my code but it does nothing when I press "A". It also doesn't work if I add a modifier like "Shift".

<Window ...>
    <Window.InputBindings>
        <KeyBinding Key="A" Command="{Binding Button_Click}"/>
    </Window.InputBindings>
    ...
</Window>

CodePudding user response:

That's not quite how commands work. If you want to use commands to execute custom behavior I highly recommend you install the CommunityToolkit.Mvvm NuGet package, which will grant you access to RelayCommand.

Typically, with MVVM, ViewModels are set as the DataContext for views, but you don't have to do all that if you just want to use commands to bind to code behind behavior.

In your Window code-behind, add a property of type RelayCommand and initializing it in your constructor binding it to a function like so:

public partial class MainWindow : Window
{
    public RelayCommand ClickCommand { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        ClickCommand = new RelayCommand(ButtonClickAction);
    }

    private void ButtonClickAction()
    {
        MessageBox.Show("The Button was clicked!");
    }
}

Then in your XAML, you can bind to that command as long as the DataContext you're binding to is the Window class.

Here's an example with both the Button AND the KeyBinding bound to the same behavior:

<Window ...>
    <Window.InputBindings>
        <KeyBinding Key="A" Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ClickCommand}" />
    </Window.InputBindings>
    <Grid>
        <Button Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ClickCommand}"
                Content="Click Me (A)" Width="100" Height="50"/>
    </Grid>
</Window>

Note the RelativeSource is directing the binding that the location of the ClickCommand RelayCommand should be found in an ancestor of type Window since it's the Window's where we defined the command.

CodePudding user response:

Despite the solution already given, I would like to propose another implementation that does not depend on the Code Behind of the Window.

using Simplified;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Markup;

namespace Helpers
{
    /// <summary>Static class with various helper members</summary>
    public static partial class WinHelper
    {
        public static RelayCommand RaiseClickCommand = new RelayCommand(
            obj =>
            {
                switch (obj)
                {
                    case FrameworkElement element:
                        RoutedEvent? clickEvent = element switch
                        {
                            ButtonBase => ButtonBase.ClickEvent,
                            MenuItem => MenuItem.ClickEvent,
                            _ => null
                        };
                        if (clickEvent is not null)
                        {
                            element.RaiseEvent(new RoutedEventArgs(clickEvent));
                        }
                        break;
                    case Hyperlink link:
                        link.RaiseEvent(new RoutedEventArgs(Hyperlink.ClickEvent));
                        break;
                    default: break;
                }
            },
            obj => obj is ButtonBase or MenuItem or Hyperlink
        );
    }

    /// <summary>For ease of use in XAML</summary>
    [MarkupExtensionReturnType(typeof(RelayCommand))]
    public class RaiseClickExtension : MarkupExtension
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return WinHelper.RaiseClickCommand;
        }
    }
}

An example of its use:

    <Window.InputBindings>
        <KeyBinding Key="A" Command="{helpers:RaiseClick}" CommandParameter="{Binding ElementName=btnA}"/>
        <KeyBinding Key="B" Command="{helpers:RaiseClick}" CommandParameter="{Binding ElementName=btnB}"/>
    </Window.InputBindings>
    <UniformGrid >
        <Button x:Name="btnA" Content="Clik Me #A" Click="OnClickMeA"/>
        <Button x:Name="btnB" Content="Clik Me #B" Click="OnClickMeB"/>
        <x:Code>
            <![CDATA[
        private void OnClickMeA(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Click A");
        }
        private void OnClickMeB(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Click B");
        }
            ]]>
        </x:Code>
    </UniformGrid>

This solution can be used for buttons of any type (Button, CheckBox, etc.), MenuItem and Hyperlink.

  • Related