In my ViewModel, how can I detect what key was pressed when entering text in a textBox?
In plain WPF/C# I'm doing it like this...
XAML File
<TextBox x:Name="myInputField" KeyDown="inputField_KeyDown"/>
Codebehind .xaml.cs
private void inputField_KeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.IsKeyDown(Key.Enter)) {
// do something
}
}
EDIT:
FYI -What I'm trying to do is create a shortcut for the enter key.
There are a couple of ways to go about this. The first approach would be more MVVM appropriate where we just detect a change to the value of the Text that is bound to your TextBox:
In XAML:
<TextBox x:Name="myInputField",
Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}" />
In VM
private string myText;
public string MyText
{
get
{
return myText;
}
set
{
if (Set(nameof (MyText), ref myText, value))
{
// the value of the text box changed.. do something here?
}
}
}
Or, to more directly answer the question you asked, if you must rely on detecting a keypress in the textbox, you should take advantage of the EventToCommand that you can hook in with MVVMLight
In XAML:
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...
<TextBox ....
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<cmd:EventToCommand Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.KeyDownCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Edit
In addition, you could also bind to the KeyBinding Command on the textbox:
<TextBox AcceptsReturn="False">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding SearchCommand}"
CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
</TextBox.InputBindings>
And yet another option would be to keep handling the KeyDown event in your view, but in the codebehind call a ViewModel method:
As I understand, you actually do not want to send the Key to ViewModel. You just want to trigger something inside your ViewModel.
EventAggregator might be your solution, in your KeyDown event, you trigger event inside VM without knowing VM and you pass anything you want, there are several ways to do it.
If you are using framework like MVVMLight, Prism they might have own implementations, if you don't, there is a simple tutorial for it. (This is not the only way, you can find different implementations when you search observer pattern)
Inside your if you call Publish method which comes from EventAggregator. And all your Subscribers get that with a parameter you choose.
This way you can communicate with your ViewModel from wherever you want.
Personally I have created a Behavior as follows:
public class KeyUpToCommandBehaviour : Behavior<UIElement>
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(KeyUpToCommandBehaviour), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key", typeof(Key), typeof(KeyUpToCommandBehaviour), new PropertyMetadata(default(Key)));
private RoutedEventHandler _routedEventHandler;
public Key Key
{
get { return (Key) GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
_routedEventHandler = AssociatedObject_KeyUp;
AssociatedObject.AddHandler(UIElement.KeyUpEvent, _routedEventHandler, true);
}
void AssociatedObject_KeyUp(object sender, RoutedEventArgs e)
{
var keyArgs = e as KeyEventArgs;
if (keyArgs == null)
return;
if(keyArgs.Key == Key)
Command?.Execute(null);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(UIElement.KeyUpEvent, _routedEventHandler);
}
}
And then use it as
<TextBox ....
<i:Interaction.Behaviors>
<attachedBehaviors:KeyUpToCommandBehaviour Key="Enter" Command="{Binding OpenFxTradeTargetingWizardCommand}"/>
</i:Interaction.Behaviors>
</TextBox>
Related
First: Not a duplicate of Binding Button click to a method --- it's about button, and Relay command can't pass the arguments I need
Also, not a duplicate of How do you bind a Button Command to a member method? - it's a simple method with no arguments - nothing to do with my question.
Obviously (but just to make sure and avoid trolls) not a duplicate of this either Silverlight MVVM: where did my (object sender, RoutedEventArgs e) go?.
Now after clearing this (sorry, I am just really sick of being marked as "duplicate" by people who didn't understand my question), let's talk about the issue: :D
I am trying to bind a generated slider (using data template) to an event (value changed), I know it's impossible to bind an event and I must use ICommand, but I don't know how to get the event arguments to the command function, this is the xaml relevant code: (without the binding since it doesnt work)
<Slider Grid.Column="1" Grid.Row="1" Height="30" IsSnapToTickEnabled="True" Maximum="100" SmallChange="1" IsMoveToPointEnabled="True"/>
And this is the function I want it to be binded to:
public void vibrationSlider_move(object Sender, RoutedPropertyChangedEventArgs<double> e)
{
VibrationValue = (byte)e.NewValue;
SendPacket(cockpitType, (byte)Index.VibrationSlider, VibrationValue);
}
As you can see, I need to use the 'e' coming with the event, I have no idea how to reach it without using the "ValueChanged" slider event.
Notes:
Please don't tell me to add the "ValueChanged" attribute like this:
<Slider ValueChanged="VibrationSlider_move"/>
:)
It's a generated dynamic slider using DataTemplate with an observableCollection, the function isn't in the window.cs file, therefore just using an event is not possible.
Thank you.
You can use the MVVMLight Toolkit, which allows to send the EventArgs as CommandParameter to the ViewModel:
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand Command="{Binding ValueChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your command.Execute method, you now get an object as parameter which you just have to parse to the correct type...
You could create an extension
public partial class Extensions
{
public static readonly DependencyProperty ValueChangedCommandProperty = DependencyProperty.RegisterAttached("ValueChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Slider;
if (element != null)
{
element.ValueChanged -= OnSingleValueChanged;
if (e.NewValue != null)
{
element.ValueChanged += OnSingleValueChanged;
}
}
}));
public static ICommand GetValueChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(ValueChangedCommandProperty);
}
public static void SetValueChangedCommand(UIElement element, ICommand value)
{
element.SetValue(ValueChangedCommandProperty, value);
}
private static void OnSingleValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var element = sender as Slider;
var command = element.GetValue(ValueChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element))
{
command.Execute(element);
e.Handled = true;
}
}
}
which then can be used in xaml as below.
<Slider Minimum="0" Maximum="100" local:Extensions.ValueChangedCommand="{Binding ValueChangedCommand}"/>
As #Philip W stated, you could use e.g. MVVMLight to help dealing with MVVM pattern and with your problem at hand.
You could, for example, have a XAML with DataTemplate and Slider like so:
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
Title="MainWindow"
Height="250"
Width="250">
<Window.Resources>
<DataTemplate x:Key="SomeTemplate">
<StackPanel Margin="15">
<!-- Wrong DataContext can drive you mad!1 -->
<StackPanel.DataContext>
<local:SomeTemplateViewModel />
</StackPanel.DataContext>
<TextBlock Text="This is some template"/>
<Slider
Height="30"
IsSnapToTickEnabled="True"
Maximum="100"
SmallChange="1"
IsMoveToPointEnabled="True">
<!-- Bind/pass event as command -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<command:EventToCommand
Command="{Binding Mode=OneWay, Path=ValueChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
<!-- Show current value, just for sake of it... -->
<TextBlock
Text="{Binding Value}"
FontWeight="Bold"
FontSize="24">
</TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{StaticResource SomeTemplate}" />
</Window>
So basically you bind desired event to named Command and pass EventArgs to it as parameter. Then in your ViewModel, being the DataContext of you Slider, you handle the event-passed-as-command.
public class SomeTemplateViewModel : ViewModelBase
{
private double _value;
public SomeTemplateViewModel()
{
// Create command setting Value as Slider's NewValue
ValueChangedCommand = new RelayCommand<RoutedPropertyChangedEventArgs<double>>(
args => Value = args.NewValue);
}
public ICommand ValueChangedCommand { get; set; }
public double Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged(); } // Notify UI
}
}
This would give you something similar to this.
Since your slider is dynamically generated, nothing prevents you from adding your ValueChanged event at a later time:
XAML:
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="10,143,0,0" VerticalAlignment="Top" Width="474" Grid.ColumnSpan="2" />
Code-behind:
public MainWindow()
{
InitializeComponent();
// it is a good idea to not allow designer to execute custom code
if (DesignerProperties.GetIsInDesignMode(this))
return;
slider.ValueChanged += Slider_ValueChanged;
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// do your stuff here
}
Checking design mode is not simple in any context, as pointed out here.
I have the following on a datagrid in my C# code:
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding CmdTransUnitFillerRowDblClick}" />
</DataGrid.InputBindings>
It works for the most part except if user first selects the row (single click) and then tries double-clicking the row. In this situation the CmdTransUnitFillerRowDblClick code is never fired for processing.
So, how can I get the CmdTransUnitFillerRowDblClick to fire correctly on a double-click when the row is already selected?
Since someone may ask:
private void ExecutecmdTransUnitFillerRowDblClick(object parameter)
{
if (DgTransUnitFillerSelectedItem != null)
TransUnitFillerDoubleClick(DgTransUnitFillerSelectedItem.CollectionRowId);
}
See my answer to another related question. The problem is that the datagrid no longer has the focus after the user selects a row (or cell, actually); the cell that the user clicked in the datagrid does. So you have to change the focus back to the datagrid to allow this.
Change:
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding CmdTransUnitFillerRowDblClick}" />
</DataGrid.InputBindings>
To:
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding CmdTransUnitFillerRowDblClick}" />
<MouseBinding Gesture="LeftClick" Command="{Binding CmdTransUnitFillerRowClick}" />
</DataGrid.InputBindings>
...and add:
private void ExecutecmdTransUnitFillerRowClick(object parameter)
{
if (DgTransUnitFillerSelectedItem != null)
The_Name_Of_Your_DataGrid.Focus();
}
On top of your existing InputBinding, you can use a Style to attach the InputBinding to each cell:
<Style TargetType="DataGridCell">
<Setter Property="local:MouseCommands.LeftDoubleClick" Value="ApplicationCommands.New" />
</Style>
This requires use of the MouseCommands class from here.
public static class MouseCommands
{
private static void LeftDoubleClickChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var control = (Control)sender;
if (args.NewValue != null && args.NewValue is ICommand)
{
var newBinding = new MouseBinding(args.NewValue as ICommand, new MouseGesture(MouseAction.LeftDoubleClick));
control.InputBindings.Add(newBinding);
}
else
{
var oldBinding = control.InputBindings.Cast<InputBinding>().First(b => b.Command.Equals(args.OldValue));
control.InputBindings.Remove(oldBinding);
}
}
public static readonly DependencyProperty LeftDoubleClickProperty =
DependencyProperty.RegisterAttached("LeftDoubleClick",
typeof(ICommand),
typeof(MouseCommands),
new UIPropertyMetadata(LeftDoubleClickChanged));
public static void SetLeftDoubleClick(DependencyObject obj, ICommand value)
{
obj.SetValue(LeftDoubleClickProperty, value);
}
public static ICommand GetLeftDoubleClick(DependencyObject obj)
{
return (ICommand)obj.GetValue(LeftDoubleClickProperty);
}
}
Though I think a cleaner way is to just handle the MouseDoubleClick event in the code-behind and manually raise the Command execution by calling your ViewModel directly, or calling .Execute() on the command.
I have a ListView/GridView setup and I want to handle a right click on the dislayed items. Is there are Databinding-way of doing this? I have seen complicated workarounds like handling the super-elements event and poking around to find its origin, but that feels awfully bloated for such basic request.
What I'd love to see is something like binding the event to an action of the item's ViewModel - is there a way to do that? Similar to this, but I can't quite wrap my head around how to adapt that to work on a single ListView item (I am not even sure that's possible, tbh).
Rough outline:
<ListView>
<ListView.View>
<GridView />
</ListView.View>
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
</Style>
</ListView.Resources>
</ListView/>
There is a way using the Interactivity assembly form the Blend SDK. It will provide an EventTrigger which executes a command when an event is raised.
<!--
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
-->
<Button Content="Click me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Edit:
A possible solution for your problem could look like this:
View:
<ListView x:Name="listView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding RightClickOnItemCommand}"
CommandParameter={Binding SelectedItem, ElementName=listView} />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
ViewModel:
public ICommand RightClickOnItemCommand { get; set; }
public void RightClickOnItem(object item)
{
}
You could try to create a style template for the list view item, and add an attached behaviour to it to handle mouse clicks.
public static readonly DependencyProperty PreviewMouseLeftButtonDownCommandProperty =
DependencyProperty.RegisterAttached("PreviewMouseLeftButtonDownCommand", typeof (ICommand),
typeof (MouseBehaviour), new FrameworkPropertyMetadata(PreviewMouseLeftButtonDownCommandChanged));
private static void PreviewMouseLeftButtonDownCommandChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var element = (FrameworkElement) dependencyObject;
element.PreviewMouseLeftButtonDown += Element_PreviewMouseLeftButtonDown;
}
private static void Element_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
var element = (FrameworkElement) sender;
ICommand command = GetPreviewMouseLeftButtonDownCommand(element);
if (command != null)
{
command.Execute(args);
}
}
public static void SetPreviewMouseLeftButtonDownCommand(UIElement element, ICommand value)
{
element.SetValue(PreviewMouseLeftButtonDownCommandProperty, value);
}
public static ICommand GetPreviewMouseLeftButtonDownCommand(UIElement element)
{
return (ICommand) element.GetValue(PreviewMouseLeftButtonDownCommandProperty);
}
All
I have a issue. Now I'm using MVVM framework to develop Windows Phone 8 app. I just want to when I press the button, then begin to record something, when release the button, stop recording, I used InvokeCommandAction to bind the command in ViewModel, this is the code as follow
Xaml:
<Button x:Name="BtnRecord" Height="50" Width="180" Background="#D43637" Content="Record" Margin="20,0,0,0" Style="{StaticResource BasicButtonStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding StartRecordCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding EndRecordCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
ModelView:
public ICommand StartRecordCommand
{
get
{
return new DelegateCommand(StartRecord);
}
}
public ICommand EndRecordCommand
{
get
{
return new DelegateCommand(EndRecord);
}
}
private void StartRecord(object parameter){}
private void EndRecord(object parameter){}
When I debug the app, I found it didn't fire the neither the MouseLeftButtonDown nor MouseLeftButtonUp events, so I register the two event handler as follow:
BtnRecord.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
BtnRecord.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(Button_MouseLeftButtonUp), true);
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
}
private void Button_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}
OK, keep on going, but the next problem is coming, it didn't fire the ICommand in ViewModel, it called the Button_MouseLeftButtonDown, oh, god, I crazy
Anyone know how to call the ICommand in ViewModel?
Or another way to implement it?
You can use ICommand.Execute. So, your handler should be
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
StartRecordCommand.Execute(null);
}
Try binding the IsPressed property of the Button with a TwoWay binding to a bool property called IsRecording in your ViewModel and start/stop your recording logic from inside the setter based on the new bool value (true would mean start).
Let me know if it works.
I've a Text box KeyUp Event Trigger Wired up to a command in WPF.
I need to pass the actual key that was pressed as a command parameter.
The command executes fine, but the code that handles it needs to know the actual key that was pressed (remember this could be an enter key or anything not just a letter, so I can't get it from the TextBox.text).
Can't figure out how to do this.
XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAML:
<TextBox Height="23" Name="TextBoxSelectionSearch" Width="148" Tag="Enter Selection Name" Text="{Binding Path=SelectionEditorFilter.SelectionNameFilter,UpdateSourceTrigger=PropertyChanged}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding SelectionEditorSelectionNameFilterKeyUpCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I don't think that's possible with InvokeCommandAction but you can quickly create your own Behavior which could roughly look like this one:
public class KeyUpWithArgsBehavior : Behavior<UIElement>
{
public ICommand KeyUpCommand
{
get { return (ICommand)GetValue(KeyUpCommandProperty); }
set { SetValue(KeyUpCommandProperty, value); }
}
public static readonly DependencyProperty KeyUpCommandProperty =
DependencyProperty.Register("KeyUpCommand", typeof(ICommand), typeof(KeyUpWithArgsBehavior), new UIPropertyMetadata(null));
protected override void OnAttached()
{
AssociatedObject.KeyUp += new KeyEventHandler(AssociatedObjectKeyUp);
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObjectKeyUp);
base.OnDetaching();
}
private void AssociatedObjectKeyUp(object sender, KeyEventArgs e)
{
if (KeyUpCommand != null)
{
KeyUpCommand.Execute(e.Key);
}
}
}
and then just attach it to the TextBox:
<TextBox Height="23" Name="TextBoxSelectionSearch" Width="148" Tag="Enter Selection Name" Text="{Binding Path=SelectionEditorFilter.SelectionNameFilter,UpdateSourceTrigger=PropertyChanged}" >
<i:Interaction.Behaviors>
<someNamespace:KeyUpWithArgsBehavior
KeyUpCommand="{Binding SelectionEditorSelectionNameFilterKeyUpCommand}" />
</i:Interaction.Behaviors>
</TextBox>
With just that you should receive the Key as a parameter to the command.