Wpf Binding UI Events to Commands in Viewmodel

WPF event binding from View to ViewModel?

One way to handle events in MVVM and XAML is to use the Blend Interactivity features. This namespace contains the InvokeCommandAction and the CallMethodAction classes.

InvokeCommandAction lets you bind any event to a view-model command while CallMethodAction lets you bind any event to a view-model method.

For example if you want to bind the DoubleClick event of a Button to a view-model command you would do like this:

<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Path=DoSomethingCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

And declaring this namespace:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

All you need to reference it in your projects is to install Expression Blend or the Expression Blend SDK.

Binding Events to Commands without Blend SDK or third party. (WPF / MVVM)

You cannot do the following because MouseDoubleClick is en event and not a dependency property:

MouseDoubleClick="{Binding DoubleClickCommand}">

You can bind to the Command property of a MouseBinding though:

<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DoubleClickCommand}"/>
</DataGrid.InputBindings>

If you want to invoke the command when you double-click on a cell, you should add the MouseBinding to the DataGridCell:

<DataGrid x:Name="dg" AutoGenerateColumns="False">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<ContentPresenter.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DataContext.DoubleClickCommand,RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</ContentPresenter.InputBindings>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DoubleClickCommand}"/>
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
</DataGrid.Columns>
</DataGrid>

UI updating command in View Model (WPF)

this.Background refers to the Background property of your view model provided that the ChangeColor method belongs to the view model class. For the window's background to change, you need to bind it to the Background property of the view model and raise an event to tell the UI to update. This requires your view model to implement the INotifyPropertyChanged event:

public class ViewModel : INotifyPropertyChanged
{
public RelayCommand ColorChangerCommand { get; set; }

public TreeViewModel() //Constructor of the View Model
{
ColorChangerCommand = new RelayCommand(ChangeColor);
}

public void ChangeColor(object sender)
{
this.Background = (sender as TreeViewItem).Foreground;
}

private Brush background= Brushes.White;
public Brush Background
{
get { return background; }
set { Background = value; NotifyPropertyChanged(Background); }
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

XAML:

<Window .... Background="{Binding Background}" />

You also need to set the DataContext of the window to an instance of your view model class and bind the Command property of the Hyperlink like this:

<Hyperlink Command="{Binding DataContext.ColorChangerCommand, RelativeSource={RelativeSource AncestorType=Window}}" 
Foreground="{Binding Foreground}" TextDecorations="None">

Bind event to ViewModel

InvokeCommandAction requires the ICommand to be bound not an event handler as you've bound (EmployeeGrid_MouseLeftButtonUp).

So you can introduce a command in ViewModel and bind to it:

View Model:

public ICommand SomeActionCommand { get; set; }

XAML:

<i:InvokeCommandAction Command="{Binding SomeActionCommand}" />

Binding SizeChanged event to ViewModel

You could use the CommandParameter property and value converter.

XAML:

<Grid x:Name="grid">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="SizeChanged">
<Interactions:InvokeCommandAction Command="{Binding OnSizeChanged}">
<Interactions:InvokeCommandAction.CommandParameter>
<Binding ElementName="grid">
<Binding.Converter>
<local:SizeConverter />
</Binding.Converter>
</Binding>
</Interactions:InvokeCommandAction.CommandParameter>
</Interactions:InvokeCommandAction>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Grid>

Converter:

public class SizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) =>
value is FrameworkElement fe ? (fe.ActualWidth, fe.ActualHeight) : (double.NaN, double.NaN);

public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotSupportedException();
}

View Model:

public IRelayCommand OnSizeChanged => new RelayCommand<(double, double)>(SizeChangedEvent);

private void SizeChangedEvent((double width, double height) size)
{
//...
}

Bind events to Item ViewModel

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

Binding Event to a Method of the ViewModel without ICommand

1) One of the benefits of ICommand is that you can more easily route commands around your application by simply modifying the bindings accordingly. By binding directly to a handler you lose this functionality and would have to implement it yourself. That may not be an issue in your specific case but it's an unnecessary layer regardless.

2) This is probably a bit of a subjective topic but I personally think that although it's not a technical violation of MVVM it's not in keeping with the overall philosophy. WPF, and especially MVVM, are designed to be data-driven; binding to a method smacks of going back to the old event-driven way of doing things (at least to me). In any case while binding to a method might still qualify as MVVM, at least technically, passing a UI object in as the sender most definitely would not!

3) You'll need to modify the GetHandler function to construct, compile and return either a LINQ expression or an IL delegate that accepts the expected parameters, removes the first and passes the rest to the binding target's method. This should be enough to get you started:

static Delegate GetHandler(object dataContext, EventInfo eventInfo, string eventHandlerName)
{
// get the vm handler we're binding to
var eventParams = GetParameterTypes(eventInfo.EventHandlerType);
var method = dataContext.GetType().GetMethod(eventHandlerName, eventParams.Skip(1).ToArray());
if (method == null)
return null;

// construct an expression that calls it
var instance = Expression.Constant(dataContext);
var paramExpressions = eventParams.Select(p => Expression.Parameter(p)).ToArray();
var call = Expression.Call(instance, method, paramExpressions.Skip(1));

// wrap it in a lambda and compile it
return Expression.Lambda(eventInfo.EventHandlerType, call, paramExpressions).Compile();
}

4) Bit of a generic question, the only one I use frequently is Translate for localization.



Related Topics



Leave a reply



Submit