I want to handle hotkeys in my application. Writing a keybinding requires a command, which is fine, but it's not clear to me what is the minimum amount of work needed to implement that command. All the examples I seem to find are over-engineered, unclear or assume I'm using the MVVM pattern which I am not.
So what are the basics to getting a keybinding to work?
Thanks!
The minimum amount of work needed to implement a command is simply a class that implements ICommand. RoutedCommand is a simplistic implementation that provides the basic functionality.
Once you have that command set up, the KeyBinding is quite simple. You simply provide a Key, and optional Modifiers for that key. A number of common commands have been included in .NET. For example, you can bind the Copy command to Ctrl+C using this mark-up:
<Window.InputBindings>
<KeyBinding Command="ApplicationCommands.Copy" Key="C" Modifiers="Ctrl"/>
</Window.InputBindings>
You can check out ApplicationCommands, ComponentCommands, and NavigationCommands for some other built-in commands.
The easiest way to make a Keybinding I know of is doing something like this
in XAML
<Window.CommandBindings>
<CommandBinding Command="MyCommand"
CanExecute="MyCommandCanExecute"
Executed="MyCommandExecuted" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="MyCommand" Key="M" Modifiers="Ctrl"/>
</Window.InputBindings>
in code behind
private void MyCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void MyCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Executed!");
e.Handled = true;
}
It's pretty readable in my opinion, but if you have any questions leave a comment!
Related
I checked a lot of of answers here about shortcuts bindings, but did not find the simple way to execute some method from the C# code.
First, in the following classic example I don't understand what actually we are binding. What the meaning of Command attribute? What is the ApplicationCommands? Where is ApplicationCommands.Open has been declared?
<Window.InputBindings>
<KeyBinding Command="ApplicationCommands.Open"
Gesture="CTRL+R" />
</Window.InputBindings>
In the one of answer of question similar to my one, "XAML is the markup language, so we could not call the method from there" has been told. OK, in this case, WHY we can call the method OnClickBtn1 from the code below?
<Button
x:Name="SomeButton"
Click="OnClickBtn1"/>
Finally, my problem. All I need is to execute OnClickBtn1 method by Ctrl+H (for example) shortcut same as effect when the button Btn1 clicked. I understand that following code is not enough.
XAML:
<Window x:Name="MainDisplay"
<!-- ... --->
>
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+H" Command="{Binding OnClickBtn1}" />
</Window.InputBindings>
<!-- ... -->
<Button x:Name="Btn1"
Width="70"
Content="Button"
Click="OnClickBtn1"/>
C#:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void OnClickBtn1(object sender, RoutedEventArgs e) {
System.Diagnostics.Debug.WriteLine("Btn1 has been clicked or Ctrl+H had been inputed");
}
}
I am new to WPF and I see the best pattern call MVVM. I have try to deep in it and I see that the command can only execute on a button or menuitem, etc. But I have a doubt how to execute the ViewModel command when I'm focusing on a textbox and hit the enter key when I finish my editing.
I have google this but I got nothing from all that answer. So hope all of you help me. How to execute command when hit the enter key in textbox?
In my opinion the easiest way is to use a KeyBinding, which allows you to bind a KeyGesture to an ICommand implementation.
In your case, you can write in your XAML something like this:
<TextBox AcceptsReturn="False">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding YourCommand}" />
</TextBox.InputBindings>
</TextBox>
So when your TextBox is focused and you press Enter, YourCommand will be executed.
I hope it can help you.
You can achieve your requirement using behaviors in WPF.
In XAML,
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox Text="MyText">
<i:Interaction.Behaviors>
<i:BehaviorCollection>
<EventToCommand EventName="TextChanged" Command="{Binding ViewModelCommand}">
**// You can provide other events to be triggered in the EventName property based on your requirement like "Focused" or "UnFocused".Focused event will be fired if you enter into edit mode and UnFocused event will be triggered if you press enter key.**
<i:BehaviorCollection>
</i:Interaction.Behaviors>
</TextBox>
In ViewModel.cs,
Public class ViewModel
{
private Command viewCommand;
public ViewModel()
{
viewCommand = new Command(CommandMethod);
}
public Command ViewModelCommand
{
get { return viewCommand }
set { viewCommand = value}
}
private void CommandMethod()
{
//This method will hit if you modify enter/delete text in the TextBox
}
}
I know there are a handful of related questions but none of those helped me finding the issue.
Most answers suggest to implement CanExecuteChanged as shown in this answer. Well, that's not the solution to my problem. I've got an implementation of RelayCommand similar to Josh Smith's implemenation. (Similar, because our implementation adds more details but the core implementation is the same.)
While searching the Internet I also learned that if there is no focused element, the routing will stop at the ContextMenu and wouldn't reach the MenuItem. A solution that would help in that case is shown here.
However, I checked with Snoop if there really isn't any focused element and learned this is not the issue. And the fix didn't help anyway.
Besides, I simulated that issue in a test project and was able to fix it. So the fix generally works, it's just not helping me. I think there's still a chance, however, that I have to adapt the fix slightly to get it working. I tried MyControl instead of ContextMenu as AncestorType and I tried PlacementTarget.Tag instead of just PlacementTarget as Path but I wouldn't know what else to try to get it working (assuming that this is the bug).
Funny enough, it even doesn't work when I call CommandManager.InvalidateRequerySuggested() manually. I added a command that is raised on ContextMenuOpening. I thought that this would force the CanExecute to be executed but it seems I'm mistaken.
So, I'm now looking for further reasons why a CanExecute handler isn't raised when a ContextMenu is opened and how I would fix that.
Here's my XAML code (including EventTrigger for ContextMenuOpening):
<MyControl>
<MyControl.ContextMenu>
<ContextMenu>
<MenuItem Header="..."
Command="{Binding MyCommand}"
CommandParameter="{Binding}"
CommandTarget="{Binding Path=PlacementTarget,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</MyControl.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding OnContextMenuOpening}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MyControl>
Here's the definition of MyCommand and the (Can)Execute handlers:
internal static readonly ICommandEx MyCommand =
new RelayCommand(OnMyCommand, OnCanMyCommand);
private static void OnMyCommand(object parameter) { ... }
private static bool OnCanMyCommand(object parameter) { ... }
Here's my OnContextMenuOpening handler where I tried to force MyCommand's CanExecute to be raised:
private static void OnContextMenuOpening(object parameter)
{
CommandManager.InvalidateRequerySuggested();
}
You are incorrectly listening on OnContextMenuOpening on the ContextMenu control. It will never fire! Instead, listen on this very event on your MyControl control.
I have keyboard shortcuts declared in my xaml using KeyBindings.
I would like to ignore repetitions due to key holding in few of them.
I have found only solutions using events and checking "IsRepetition", which doesnt really fit in my declaration of the keybindings.
Of course I could do it in the Command definition itself and measure a time difference between 2 last executes, but this gives me no way to differentiate multiple presses and 1 key holding.
What would be the best way to execute only on the first press and ignore the rest if the key is hold?
You are trying to change a behavior of the button. Better to use code for that.
The easiest way is to attach a preview event to the window like that:
<Window
...
PreviewKeyDown="HandlePreviewKeyDown">
Then in code handle it like that:
private void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat)
{
e.Handled = true;
}
}
Sadly this would disable any repeat behavior, even in a textbox hosted by the form. This is an interesting question. If I find a more elegant way of doing this, I will add to the answer.
EDIT:
OK there are two ways to define Key Binding.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.InputBindings>
<KeyBinding x:Name="altD" Gesture="Alt+D" Command="{Binding ClickCommand}"/>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="_Click" Command="{Binding ClickCommand}" />
<TextBox Grid.Row="1"/>
</Grid>
</Window>
The above button will generate a click because you implicitely requested the Alt-C gesture via the underscore: _Click content. Then the window has an explicit keybinding to Alt+D.
This code behind should now work for both cases and should not interfere with regular repeat:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.IsRepeat)
{
if (((KeyGesture)altD.Gesture).Matches(this, e))
{
e.Handled = true;
}
else if (e.Key == Key.System)
{
string sysKey = e.SystemKey.ToString();
//We only care about a single character here: _{character}
if (sysKey.Length == 1 && AccessKeyManager.IsKeyRegistered(null, sysKey))
{
e.Handled = true;
}
}
}
}
I would say if you create a very simple state machine of sorts that would take action on the KeyBinding on a KeyDown event and would ignore all other input until a KeyUp event is fired to give the KeyBinding a "one-shot" behavior.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.keyup.aspx
Use the keyUp method instead of KeyDown.
I use standard Cut, Copy and Paste commands (which is part of ApplicationCommands class). Is it possible to redefine CanExecute method?
Here is my code:
XAML:
<Window.CommandBindings>
<CommandBinding Command="Copy"
CanExecute="CopyCanExecute" Executed="CopyExecuted"/>
</Window.CommandBindings>
<StackPanel>
<TextBox Name="txt"></TextBox>
<Button Command="Copy" CommandTarget="{Binding ElementName=txt}">copy</Button>
</StackPanel>
Code-behind:
private void CopyCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = false;
}
private void CopyExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Copy Executed");
}
The button still behave like its command is standard Copy command.
You do this via a CommandBinding. The local CommandBinding can specify a CanExecuteHandler.
For details and a working example, see this blog post.
The copy command will not work when the focus is on a textbox where the commands have already been handled, but it will work on elements like CheckBox etc.
In the CanExecute handler you might need to add `e.Handled = true; also, so that it doesnt go and execute the standard Copy.CanExecute()
You can set the commandbinding to the textbox directly instead of to the window.