Catch a range of characters with keybindings in xaml - c#

I'm developing a feature where a user can press 1 trough 9 or 'a' through 'z' that executes a command in a list. I built support for the numbers, but I don't really like the way I made it.
<Grid.InputBindings>
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D0" CommandParameter="0" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D1" CommandParameter="1" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D2" CommandParameter="2" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D3" CommandParameter="3" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D4" CommandParameter="4" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D5" CommandParameter="5" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D6" CommandParameter="6" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D7" CommandParameter="7" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D8" CommandParameter="8" />
<KeyBinding Command="{Binding ShortcutCharacterCommand}" Key="D9" CommandParameter="9" />
</Grid.InputBindings>
If I implement the remaining characters in the same manner, I'll have a huge list of bindings. Not only to support the letters, but also the numpad. I'd rather test for ranges of characters like I did with a Winform control in another part of our application with the code:
if (e.KeyValue >= '1' && e.KeyValue <= '9' ||
e.KeyValue >= 'A' && e.KeyValue <= 'Z')
{
FavoriteShortcutKeyPressedCallBack.Raise(e.KeyValue);
}
I really think it is possible but I can't seem to work out a solution or find one on the internet that adheres to the MVVM pattern.
So basically my question is, how can this be done in WPF/MVVM in a more generic, elegant manner?
How I solved it
I took the suggestion from the answer of mm8 to use EventToCommandBinding. This resulted in the following code in the XAML:
<i:Interaction.Behaviors>
<behaviors:EventToCommandBehavior Event="PreviewTextInput"
Command="{Binding TextInputCommand}"
PassArguments="True" />
</i:Interaction.Behaviors>
The ViewModel has a TextInputCommand that reads the text from the EventArgs and selects the corresponding item.
public RelayCommand<TextCompositionEventArgs> TextInputCommand { get; set; }
private void HandleTextInputCommand(TextCompositionEventArgs args)
{
SelectItemBoundToShortcut(args.Text);
}
At first I used the KeyDown event as user1672994 suggested in the comments. But found out that I had to account for different keyboard layouts and check separately for the numpad characters. Using the PreviewTextInput event just sends the typed text, which is exactly what I need.

You could handle the PreviewKeyDown event. Either in the code-behind of the view, from where you then invoke the command of the view model:
private void Grid_PreviewKeyDown(object sender, KeyEventArgs e)
{
var viewModel = DataContext as YourViewModel;
if (viewModel != null)
{
switch (e.Key)
{
case Key.D0:
viewModel.ShortcutCharacterCommand.Execute("0");
break;
case Key.D1:
viewModel.ShortcutCharacterCommand.Execute("1");
break;
//...
}
}
}
Or by using an interaction trigger as explained here:
MVVM Passing EventArgs As Command Parameter
You may also wrap the functionality defined in the event handler in an attached behaviour.

Related

How do I get the value of a barcode with a barcode reader from my viewmodel without needing a textbox? wpf c #

I want to obtain the barcode returned by a barcode reader without the need for the focus to be in a textbox, which only needs to be open one view.
For now what I'm doing is this in my Vista
<UserControl x:Class="Capa_Presentacion_WPF.Views.AutoServicio.AutoServicio"
xmlns:local="clr-namespace:Capa_Presentacion_WPF.Views.AutoServicio"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local11="clr-namespace:Capa_Entidades.Models;assembly=Capa_Entidades"
mc:Ignorable="d"
d:DesignHeight="768" d:DesignWidth="1130" Background="Lavender"
Loaded="UserControl_Loaded"
KeyDown="UserControl_KeyDown">
<UserControl.InputBindings>
<KeyBinding Command="{Binding Path=EnterCommand}" Key="Enter"/>
<KeyBinding Command="{Binding Path=KeyF1Command}" Key="F1" />
<KeyBinding Command="{Binding Path=KeyF2Command}" Key="F2" />
<KeyBinding Command="{Binding Path=KeyF3Command}" Key="F3" />
<KeyBinding Command="{Binding Path=KeyF4Command}" Key="F4" />
<KeyBinding Command="{Binding Path=KeyF12Command}" Key="F12" />
</UserControl.InputBindings>
</UserControl>
The first ** <KeyBinding ** refers to me when it detects the "enter", since the codes that are read with the reader come with an enter to the last one. I was thinking of using that, but the problem there is how do I get the code before the "enter"?
The other thing that this is is that I program in a textbox, but that's what I don't want, I just want to get the code and to execute a function when reading the barcode, without having to be in a textbox or any other control.
Currently I associate it to my textbox as follows:
<TextBox Background="White" Margin="0,0,10,10" Name="textBoxBuscador"
Text="{Binding Buscador, UpdateSourceTrigger=PropertyChanged}" MaxLines="1" Focusable="True">
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=BindKeyCommand}" Key="Enter" />
<KeyBinding Command="{Binding Path=KeyF1Command}" Key="F1" />
<KeyBinding Command="{Binding Path=KeyF2Command}" Key="F2" />
<KeyBinding Command="{Binding Path=KeyF3Command}" Key="F3" />
<KeyBinding Command="{Binding Path=KeyF4Command}" Key="F4" />
<KeyBinding Command="{Binding Path=KeyF12Command}" Key="F12" />
</TextBox.InputBindings>
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeTextIsFocused,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<i:Interaction.Behaviors>
<local11:FocusChangedBehavior IsFocused="{Binding SomeTextIsFocused}" />
</i:Interaction.Behaviors>
</TextBox>
I bind a string object called Buscador, and inside the <Binding it is calling a command when pressing the "enter" key that is in my viewmodel
This is my ViewModel:
private void Enter()
{
try
{
int c = this.DataPlatosBusqueda.Where(p => p.cbarplato.Contains(Buscador.ToUpper())).Count();
if (c > 0)
{
var id = this.DataPlatosBusqueda.Where(p => p.cbarplato.Contains(Buscador.ToUpper())).FirstOrDefault().idplato.ToString();
if (id != null)
{
PasarPlatos(id);
SomeTextIsFocused = true;
}
this.Buscador = "";
SomeTextIsFocused = true;
}
this.Buscador = "";
SomeTextIsFocused = true;
}
catch (Exception ex)
{
globales.Mensaje("Error:", "Opcion Enter: " + ex.Message.ToString(), 3);
}
}
Broadly speaking, barcode readers fall into two groups.
There are those that are intended for remote stocktaking and the like. These have internal memory and often store data internally for later download. They can often send the data via wi fi to a url. Essentially posting got one of these. Got one of these... as you scan.
The other category is purely an input device like a keyboard. And like a keyboard, you need something to take that input. The simplest approach is a textbox but you can also handle key input at say a window level if that has focus.
A window level key handler would look something like:
private void Window_KeyHandler(object sender, KeyEventArgs e)
{
var keypressed = e.Key;
// do something with keypressed
}
And you can hook it in a window:
this.PreviewKeyDown += new KeyEventHandler(Window_KeyHandler);
There is also a previewtextinput event.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewtextinput?view=netcore-3.1
There are a number of potential issues you could hit with this implementation. One of which being mis reads where it doesn't read the data right and drops a number, reads it as a different one or some such.
Which is why it is usual to simply have a textbox and take the input there.
If the scanner you have is a regular cheap to mid range one then I recommend you reconsider your approach.
This is how apps I've worked on do this and it's useful even when someone is scanning many items very quickly as they come off a conveyor.
If this is an expensive scanner than you could well have the option to post your data direct to a url. You could build a simple web api site and take input there. Your view and wpf app would then be more about seeing what had been input.

Implement ctrl+shift+MouseOver on a button in mvvm

I am trying to implement ctrl+shift+mouseover on a button in wpf, mvvm.
I am doing something like:
<Button.InputBindings>
<KeyBinding Gesture="Ctrl+Shift" Command="{Binding KeyCommand}"
CommandParameter="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}"
/>
</Button.InputBindings>
or something like:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseOverCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
I am unable to get the required behavior as Mouse Gesture does not support "MouseEnter" event.
I was trying to implement in a way like:
Step 1.- Through keybinding setting a variable in the viewmodel.
Step 2.- Accessing that variable's value on MouseEnter and do whatever in the command.
Any help is appreciated.
You could implement an attached behaviour that hooks up event handlers to the MouseEnter and MouseLeftButtonDown events and check whether the Ctrl and Shift keys are pressed by the time the events are raised, e.g.:
private void OnMouseEnter(object sender, MouseEventArgs e)
{
if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
&& (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
{
TheCommand.Execute(null);
}
}

Multiple KeyGestures for a single RoutedUICommand

Is it possible to have 2 or more KeyGestures for a single RoutedUICommand?
For example: User wants to be able to press Space or Alt+P to play a video.
Currently, if I set both KeyGestures to a RoutedUICommand, it will expect both to be pressed to execute.
private static RoutedUICommand _play = new RoutedUICommand("Play", "Play", typeof(Commands),
new InputGestureCollection
{
new KeyGesture(Key.P, ModifierKeys.Alt, "Alt + P"),
new KeyGesture(Key.Space, ModifierKeys.None, "Space")
});
So, can I set multiple KeyGestures to a single RoutedUICommand? If so, how?
Yes you can add multiple keyBindings for same Command like below,
<UserControl.InputBindings>
<KeyBinding Modifiers="Alt" Key="P" Command="{Binding PlayCommand}" />
<KeyBinding Key="Space" Command="{Binding PlayCommand}" />
</UserControl.InputBindings>

How to simplify InputBindings in XAML?

First, this is my code :
<Window ...>
<Window.Resources>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Space" Command="{Binding KeyboardCommand}" CommandParameter="Space"/>
<KeyBinding Key="OemPeriod" Command="{Binding KeyboardCommand}" CommandParameter="Blank"/>
<KeyBinding Key="D0" Command="{Binding KeyboardCommand}" CommandParameter="Rest"/>
<KeyBinding Key="D1" Command="{Binding KeyboardCommand}" CommandParameter="N1"/>
<KeyBinding Key="D2" Command="{Binding KeyboardCommand}" CommandParameter="N2"/>
<KeyBinding Key="D3" Command="{Binding KeyboardCommand}" CommandParameter="N3"/>
<KeyBinding Key="D4" Command="{Binding KeyboardCommand}" CommandParameter="N4"/>
<KeyBinding Key="D5" Command="{Binding KeyboardCommand}" CommandParameter="N5"/>
<KeyBinding Key="D6" Command="{Binding KeyboardCommand}" CommandParameter="N6"/>
<KeyBinding Key="D7" Command="{Binding KeyboardCommand}" CommandParameter="N7"/>
<KeyBinding Modifiers="Control" Key="Space" Command="{Binding KeyboardCommand}" CommandParameter="Space"/>
<KeyBinding Modifiers="Control" Key="OemPeriod" Command="{Binding KeyboardCommand}" CommandParameter="Blank"/>
<KeyBinding Modifiers="Control" Key="D0" Command="{Binding KeyboardCommand}" CommandParameter="Rest"/>
<KeyBinding Modifiers="Control" Key="D1" Command="{Binding KeyboardCommand}" CommandParameter="N1"/>
<KeyBinding Modifiers="Control" Key="D2" Command="{Binding KeyboardCommand}" CommandParameter="N2"/>
<KeyBinding Modifiers="Control" Key="D3" Command="{Binding KeyboardCommand}" CommandParameter="N3"/>
<KeyBinding Modifiers="Control" Key="D4" Command="{Binding KeyboardCommand}" CommandParameter="N4"/>
<KeyBinding Modifiers="Control" Key="D5" Command="{Binding KeyboardCommand}" CommandParameter="N5"/>
<KeyBinding Modifiers="Control" Key="D6" Command="{Binding KeyboardCommand}" CommandParameter="N6"/>
<KeyBinding Modifiers="Control" Key="D7" Command="{Binding KeyboardCommand}" CommandParameter="N7"/>
...
</Window.InputBindings>
<Grid>
...
</Grid>
There's NO ERROR on those code, so it works just fine. But, it seems that the InputBindings can get a lot of lines if my application has a lot of InputBindings.
So, is it possible to simplify/shorten them (in any way)? It is because my application will need a lot of InputBindings / KeyBinding's Modifier combination, and it feel input it one-by-one will look "not neat". M
Or maybe it is the only way to do (with MVVM)?
Please clarify anything needed :D
Just in case needed, these are the related methods in Command and ViewModel class :
public void Execute(object parameter)
{
Notes note;
if (Enum.TryParse(parameter.ToString(), out note))
{
_vm.AddOrUpdateNote(note, Keyboard.Modifiers);
}
else
{
...
}
}
A part of my ViewModel :
public void AddOrUpdateNote(Notes note, ModifierKeys mKeys)
{
if (mKeys == ModifierKeys.Control)
{
...
}
else if (mKeys == ModifierKeys.Shift)
{
...
}
else
{
...
}
}
So, there's a minor behavior difference depending which Modifier Key is pressed. (Splitting into different methods feels awful for me)
UPDATE :
I've read some explanations on InputGestures. In http://msdn.microsoft.com/en-us/library/ms752308%28v=vs.110%29.aspx it's said this.InputBindings.Add(Blabla) (from xaml.cs i guess), can it be done in the ViewModel? Or is it strictly need to be done via XAML, so, if I have a lot of key combination in my application such in the example above, it still need to be done that "long" way?
Please provide some code samples if possible, so I can understand it better. Thanks
(Not quite sure too how to ask it, so please feel free to clarify)
If your sole objective is to cut down on the number of manual KeyBindings, you can do something like this:
var keys = new List<Key> { Key.Space, Key.OemPeriod, Key.D1, [...]};
foreach(var key in keys)
{
this.InputBindings.Add(new KeyBinding() { Command = MyCommandClass.KeyboardCommand, CommandParameter = key});
]
But I don't like this approach, as it requires you to implement delegation of your command yourself rather than having WPF do it. You will need to switch on the CommandParameter and modifierkey.
The above will absolutely work, but having a single command do all the work seems counterintuitive to me. I would implement several commands (like AddNoteCommand) so that they can each hold their own Execute and CanExecute method.
Yes, you'd have a few extra lines of code per Command, but your command implementation would not have to know HOW you accessed the command. Imagine you want to add these commands as MenuItems, or Buttons in your UI.
There might be something I'm missing (I don't know what the commands do), but I can't really imagine a scenario where I would choose this approach.
That doesn't necessarily make it wrong though ;)
There's actually a trick you can use around attached properties. The syntax for applying an attached property is really just 'syntactical sugar' to call a static function on a particular class from XAML. Knowing that, you can build up a 'Utility' class that has a bunch of methods you can call from XAML using the attached property syntax.
As such, you could do something like this...
public namespace My{
public static class Util {
[AttachedPropertyBrowsableForType(typeof(KeyBinding))]
public static void SetSet(KeyBinding keyBinding, string bindingData){
// Add code here to parse the string and do with it what you will.
// For instance, you can split it on a pipe character to get the modifier,
// parameter and key, then just apply it to the passed-in keyBinding here.
// I'll leave this part as an exercise for you to research.
}
}
}
and you can use it like this...
<Window ...>
<Window.InputBindings>
<KeyBinding my:Util.Set="Control|D1|N1" />
<KeyBinding my:Util.Set="Control|D2|N2" />
<KeyBinding my:Util.Set="Control|D3|N3" />
...
</Window.InputBindings>
</Window>
Notes:
The method name to support attached properties is a static method named Set[PropertyName] on an 'owning' class. When you use it in XAML, it looks like OwningClass.PropertyName="bla". In my example, by calling the class Util and the static method SetSet the 'property name' becomes Set and thus in XAML (where 'my' is the imported xmlns value), it looks like my:Util.Set="bla" which reads more clearly as to my intent.
I prefer the data type string so you can pass in a ton of info in one value, then just parse it as you see fit. But you can use any data type you want. For instance, you could take a CommandInfo class that exposes your Modifiers, Key and Parameter properties, then create a MarkupExtension to 'create' that class and which takes them in as constructor parameters, then set it via the attached property syntax, like so:
<InputBinding my:Util.Set="{my:CommandInfo Control, D1, N1}" ... />
This attached property syntax has proven pretty amazing in simplifying our XAML. As an example, I use these techniques to more easily configure my grid layouts. For instance, instead of this...
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="1", Grid.ColumnSpan="2" />
</Grid>
I can now just do this...
<Grid is:GridUtil.Layout="Auto,Auto,Auto|Auto,*">
<TextBlock is:GridUtil.Cell="1,1s2" />
</Grid>
Notes:
Layout is a comma-separated list of row heights, a pipe character, then comma-separated list of column widths
Cell is a row (and possibly row-span with 'sN'), a comma, then a column (and possibly column-span with 'sN')
Hope this helps!

Detect which Control is Focused?

I want to set two shortkeys for two buttons with one shortkey name.
How can i detect which control is focused in MVVM ?
You can simply add the hotkey to whatever scope is logical for it to exist in. There are many ways to handle hotkeys to WPF, however your basic markup should look something like this:
<Window>
<StackPanel>
<local:MyUserControlA>
<local:MyUserControlA.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SaveACommand}" />
</local:MyUserControlA.InputBindings>
</local:MyUserControlA>
<local:MyUserControlB>
<local:MyUserControlB.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SaveBCommand}" />
</local:MyUserControlB.InputBindings>
</local:MyUserControlB>
</StackPanel>
</Window>
This will run SaveACommand if UserControlA has keyboard focus, or SaveBCommand if UserControlB has keyboard focus.

Categories

Resources