Tuesday, November 8, 2022

Fun with HotKeys

We all know we can assign a hot key to a button simply by prefixing the hot character in the content with an underscore. For example the XAML below executes the Underscore command if the user hits ALT+U (assuming the Underscore command can be executed)

<Button Content="_Underscore" Command="{StaticResource Underscore}"/>

While this is convenient it does have some limitations...

  • The hot character must be in the content.
  • It only works when the content is a string.
  • It only works with the ALT modifier
  • The hot character must be unique within the Window or User Control
If you want more control, you can use KeyBindings. The example below shows a binding that applies to the entire window. There is also a MouseBinding class.

<Window.InputBindings>
    <KeyBinding Gesture="ALT+W" Command="{StaticResource WindowScope}"/>
</Window.InputBindings>

Any control can have Input Bindings. So it's possible to have context-sensitive hot keys. That means a hot key can only be used when the specific control has focus. The example below shows how to implement hot keys in various ways.
  • Using the underscore technique
  • On a control with window scope
  • On a control with control scope
  • Without any visible control
Start a new Visual Studio WPF Application project using .Net Framework and C#. Call it HotKeys.

MainWindow.XAML looks like this.

<Window x:Class="HotKeys.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:HotKeys"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainWindow}"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <RoutedCommand x:Key="Underscore"/>
        <RoutedCommand x:Key="WindowScope"/>
        <RoutedCommand x:Key="SearchTitle"/>
        <RoutedCommand x:Key="SearchTerm"/>
        <RoutedCommand x:Key="All"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource Underscore}" Executed="Underscore_Executed"/>
        <CommandBinding Command="{StaticResource WindowScope}" Executed="Window_Executed"/>
        <CommandBinding Command="{StaticResource SearchTitle}" Executed="SearchTitle_Executed" CanExecute="SearchTitle_CanExecute"/>
        <CommandBinding Command="{StaticResource SearchTerm}" Executed="SearchTerm_Executed" CanExecute="SearchTerm_CanExecute"/>
        <CommandBinding Command="{StaticResource All}" Executed="All_Executed"/>
    </Window.CommandBindings>
    <Window.InputBindings>
        <KeyBinding Gesture="ALT+W" Command="{StaticResource WindowScope}"/>
        <KeyBinding Gesture="CTRL+A" Command="{StaticResource All}"/>
    </Window.InputBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="auto"/>
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="0" Content="_Underscore" Command="{StaticResource Underscore}"/>
        <Button Grid.Row="1" Grid.Column="0" Content="Window Scope" Command="{StaticResource WindowScope}"/>
        <StackPanel Grid.Row="2" Grid.Column="0" Orientation="Vertical">
            <TextBlock Text="Search Titles"/>
            <TextBox Text="{Binding SearchTitle, UpdateSourceTrigger=PropertyChanged}">
                <TextBox.InputBindings>
                    <KeyBinding Gesture="ALT+S" Command="{StaticResource SearchTitle}"/>
                </TextBox.InputBindings>
            </TextBox>
        </StackPanel>
        <StackPanel Grid.Row="2" Grid.Column="1" Orientation="Vertical">
            <TextBlock Text="Search Terms"/>
            <TextBox Text="{Binding SearchTerm, UpdateSourceTrigger=PropertyChanged}">
                <TextBox.InputBindings>
                    <KeyBinding Gesture="ALT+S" Command="{StaticResource SearchTerm}"/>
                </TextBox.InputBindings>
            </TextBox>
        </StackPanel>
        <TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Text="Hit ALT+U, ALT+W, ALT+S, or CTRL+A"/>
    </Grid>
</Window>
 
and the C# looks like this

using System.Windows;
using System.Windows.Input;
 
namespace HotKeys
{
    public partial class MainWindow : Window
    {
        public string SearchTitle { get; set; }
        public string SearchTerm { get; set; }
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Underscore_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("You underscored");
        }
 
        private void Window_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("You windowed");
        }
 
        private void SearchTitle_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"You searched for title '{SearchTitle}'");
        }
 
        private void SearchTerm_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"You searched for term '{SearchTerm}'");
 
        }
 
        private void SearchTitle_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = !string.IsNullOrEmpty(SearchTitle);
        }
 
        private void SearchTerm_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = !string.IsNullOrEmpty(SearchTerm);
        }
 
        private void All_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("You hit CTRL+A");
        }
    }
}
 
When you run the application try triggering commands by hitting ALT+U or ALT+W.


Now enter a search title and hit ALT+S. Note how the hot key knows which text box you are in and executes the correct command.


If you hit CTRL+A while in a text box the application might not do what you expect. Instead of executing the bound command, it highlights the text. This is because CTRL+A has a special meaning in text boxes.


However, if you move focus to a button and hit CTRL+A, the bound command is executed. This highlights the importance of choosing your key bindings carefully. If you want consistency across all your screens across all your applications, this requires some planning.



No comments:

Post a Comment