Bind events to Item ViewModel - c#

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);
}

Related

How to handle RoutedEvent in a grid with command in MVVM light wpf

I am using MVVM light to refacter my code. My old code looks like this-
<Grid x:Name="root" ButtonBase.Click="LayoutRoot_Click">
The LayoutRoot_Click will handle all the RoutedEvent from child controls.
Because the grid have no command interface ,so the new code like this
<Grid x:Name="root" ButtonBase.Click="LayoutRoot_Click">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ButtonBase.Click">
<mvvm:EventToCommand Command="{Binding InputCommand}" CommandParameter="{Binding ElementName=button1}" />
</i:EventTrigger>
</i:Interaction.Triggers>
The viewmodel like this-
public class SomeViewModel : ViewModelBase
{
public SomeViewModel()
{
InputCommand = new RelayCommand(InputCode);
}
public ICommand InputCommand { get; set; }
private void InputCode()
{
string input = string.Empty;
}
}
But this doesn't work. The command did not get the RoutedEvent.
Try adding PassEventArgsToCommand="True" to your command binding
<Grid x:Name="root" ButtonBase.Click="LayoutRoot_Click">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ButtonBase.Click">
<mvvm:EventToCommand Command="{Binding InputCommand}" CommandParameter="{Binding ElementName=button1}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Your command would then have to have a parameter of the correct type which I think is RoutedEventArgs
public RelayCommand<RoutedEventArgs> SomeCommand { get; private set; }
private void SomeCode(RoutedEvent routedEvent)
{
//Do something with the routed event
}

How to detect keypress in TextBox using MVVM Light

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>

WPF bind a dynamic generated slider to function

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.

mvvm command is called by long-pressing a button

I have a button
<Button Command="{Binding MyCommand}" />
But MyCommand is being hit when I long-press the button as well as when I just click it. Is there any way around this?
thanks
What you could do is the following:
Reference the System.Windows.Interactivity dll.
Define the namespace in your xaml code:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then in your xaml code wire up this event trigger:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap" SourceName="btnTest">
<i:InvokeCommandAction Command="{Binding DoSomething}" />
</i:EventTrigger>
</i:Interaction.Triggers>
The EventName in this case is "Tap", the SourceName is the x:Name of the button you want to watch. Like this:
<Button Content="Click me" x:Name="btnTest"/>
Then in your ViewModel, you can wire it up to an ICommand, I typically use a RelayCommand:
private ICommand _DoSomething;
public ICommand DoSomething
{
get
{
if (_DoSomething == null)
{
_DoSomething = new RelayCommand(DoSomethingExecute);
}
return _DoSomething;
}
}
private void DoSomethingExecute()
{
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("btnTest on the tap event");
});
}
I tested, only the tap event is captured, not the long press event.

How do you send in the LayoutRoot into a RelayCommand via a EventToCommand?

Grid example with the trigger:
<Grid x:Name="LayoutRoot" DataContext="{Binding ProjectGrid, Source={StaticResource Locator}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding LoadedCommand, Mode=OneWay}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In my ViewModel I set the LoadedCommand like this:
public RelayCommand<RoutedEventArgs> LoadedCommand {get;private set;}
And in the ViewModel initializer I have this:
public ProjectGridViewModel()
{
LoadedCommand = new RelayCommand<RoutedEventArgs>(e =>
{
this.DoLoaded(e);
}
);
}
Then, in my DoLoaded I'm trying to do this:
Grid _projectGrid = null;
public void DoLoaded(RoutedEventArgs e)
{
_projectGrid = e.OriginalSource as Grid;
}
You can see I'm trying to get rid of my Loaded="" in my Grid in my view, and do a RelayCommand instead. The issue is the OriginalSource brings back nothing. My loaded event is running nice this way, but I need to get the Grid in via the RoutedEventArgs it seems.
I tried passing in the Grid in the EventCommand with CommandParameter="{Binding ElementName=LayoutRoot}", but this just crashes VS2010 when hitting F5 and running the project.
Any ideas? Or a better way to do this? I had the Loaded event run in the views C# then call the ViewModel in the Views code-behind, but I'd like to do a nicer binding. Talking to the ViewMode in the Views code-behind feels like a hack.
You could try to bind the CommandParameter of the EventToCommand:
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding LoadedCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=LayoutRoot}" PassEventArgsToCommand="True"/>
Then, your code will be:
public RelayCommand<UIElement> LoadedCommand {get;private set;}
public ProjectGridViewModel()
{
LoadedCommand = new RelayCommand<UIElement>(e =>
{
this.DoLoaded(e);
}
);
}
Grid _projectGrid = null;
public void DoLoaded(UIElement e)
{
_projectGrid = e;
}
It should work fine :)
Bye.

Categories