How to trigger ViewModel command for a specific button events - wpf

How can a command on a ViewModel be invoked by a specific event of a button, such as MouseDoubleClick?

You can use the EventTrigger in the System.Windows.Interactivity namespace, which is part of the so-called Prism framework. If you're just getting started with MVVM, don't care too much for Prism by now, but keep it in mind for later. Anyway, you can steel the EventTrigger
It works like this:
Reference the assembly System.Windows.Interactivity.dll
In XAML, reference the namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then in your Button or any other control, add a EventTrigger like this:
<Button Content="Button">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding CommandToBindTo}"
CommandParameter="{Binding CommandParameterToBindTo}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
This way, you bind your event to a Command on your DataContext.
Remark
To clarify the usage, here's a kind of real life example including the ViewModel. The fictional requirement is to allow the user to select an item in a list and then perform a command which takes the selected item as a parameter:
<ListBox x:Name="ItemsList" ItemsSource="{Binding Items}" />
<Button Content="Do something with selected item">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoSomethingCommand}"
CommandParameter="{Binding SelectedItem,
ElementName=ItemsList}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
And that would be the ViewModel. Note how the parameter to the command is used, in the example with a generic version of a DelegateCommand object as you get it in every MVVM framework (sometimes RelayCommand). This class takes the type of the required parameter as a generic parameter (here ItemViewModel) and requires a method which takes an according parameter (here ExecuteDoSomethingWithItem(ItemViewModel ...)). The rest is WPF magic: The oject to which the CommandParameter property is bound in your XAML will be passed through as the parameter in your Execute(...) function.
public class ViewModel
{
ObservableCollection<ItemViewModel> Items { get; set; }
public ICommand DoSomethingCommand
{
get
{
return _doSomethingCommand ??
(_doSomethingCommand = new DelegateCommand<ItemViewModel>(ExecuteDoSomethingWithItem));
}
}
private DelegateCommand<ItemViewModel> _doSomethingCommand;
private void ExecuteDoSomethingWithItem(ItemViewModel itemToDoSomethingWith)
{
// Do something
}
public ViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
// Fill the collection
}
}
Have fun with learning MVVM, it's worth it.

you can use attached command behaviors
=> http://geekswithblogs.net/HouseOfBilz/archive/2009/08/21/adventures-in-mvvm-ndash-generalized-command-behavior-attachments.aspx

You need to do a lot of pluming yourself if you going to use Command and Event Binding from out of the box WPF. You can gain a lot of just using existing framework such as MVVM Light Toolkit, or Cliburn Micro that already provide command and even binding.

Related

How to bind DataGrid Sorting event to Prism ViewModel DelegateCommand

How do I bind the sorting command of my datagrid to view model?
Below is my XAML Code
<DataGrid ItemsSource="{Binding ViewModels}"
CanUserSortColumns="True"
Sorting="{Binding ViewModel_SortingCommand}">
</DataGrid>
Below is the ViewModel implemented that cause the error in binding
ViewModel_SortingCommand = new DelegateCommand<DataGridSortingEventArgs>(ViewModel_Sorting;
public void ViewModel_Sorting(DataGridSortingEventArgs args)
{
// Error on binding
}
Since Sorting is an event, you cannot bind it directly, but you can use an EventTrigger.
<DataGrid ItemsSource="{Binding ViewModels}"
CanUserSortColumns="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Sorting">
<i:InvokeCommandAction Command="{Binding ViewModel_SortingCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
If you use the legacy blend behaviors shipped with Blend, use this namespace:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
If you use the new Microsoft.Xaml.Behaviors.Wpf Nuget package, use this namespace:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
If you need to process the event args in your command, set PassEventArgsToCommand to True:
<b:InvokeCommandAction Command="{Binding FavContextMenuEditCmd}" PassEventArgsToCommand="True"/>
Also note, that there is an EventArgsParameterPath property to specify a property in the event args that should be passed to the command and an EventArgsConverter property to use a converter. These are useful to avoid passing UI related types like event args to your view model.
Passing a DataGridSortingEventArgs to a view model breaks the MVVM pattern.
You should either perform the sorting in the view/control or, if you are really interested in the sort order in the context of the view model, sort the actual source collection that the view binds to. In general, the view model isn't interested in or aware of how the user sorts, groups or filter the data in the view.
Either way, the view model should not depend on a DataGridSortingEventArgs or anything else that is related to the DataGrid control in the view.

How to use Interaction.EventTrigger and bubbling custom events in WPF

I am trying to handle a RoutedEvent from a UserControl I built by hooking it up to a command using Interaction.Triggers. The following works--it calls the AddingNewThingCommand:
<WrapPanel>
<local:MyCustomUserControl Header="MyHeader1"
ItemsSource="{Binding List1}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AddingNewThing">
<prism:InvokeCommandAction Command="{Binding DataContext.AddingNewThingCommand, ElementName=rootViewElement}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</local:MyCustomUserControl >
<local:MyCustomUserControl Header="MyHeader2"
ItemsSource="{Binding List2}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AddingNewThing">
<prism:InvokeCommandAction Command="{Binding DataContext.AddingNewThingCommand, ElementName=rootViewElement}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</local:MyCustomUserControl >
</WrapPanel>
Now I actually have a lot of these user control instances in this wrap panel, so I would prefer to move the interaction triggers to the parent element -- the WrapPanel. The following does not work:
<WrapPanel>
<i:Interaction.Triggers>
<!-- Also tried local:MyCustomUserControl.AddingNewThing -->
<i:EventTrigger EventName="MyCustomUserControl.AddingNewThing">
<prism:InvokeCommandAction Command="{Binding DataContext.AddingNewThingCommand, ElementName=rootViewElement}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<local:MyCustomUserControl Header="MyHeader1"
ItemsSource="{Binding List1}"/>
<local:MyCustomUserControl Header="MyHeader2"
ItemsSource="{Binding List2}"/>
</WrapPanel>
Does EventTrigger work with bubbling events?
My RoutedEvent:
public static readonly RoutedEvent AddingNewThingEvent = EventManager.RegisterRoutedEvent(
"AddingNewThing", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyCustomUserControl));
public event RoutedEventHandler AddingNewThing
{
add { AddHandler(AddingNewThingEvent, value); }
remove { RemoveHandler(AddingNewThingEvent, value); }
}
Normally, the routed event system permits handlers for any routed event to be attached to any UIElement or ContentElement. That's how the routed events work.
However, the System.Windows.Interactivity.EventTrigger is not a part of the visual tree and therefore cannot participate in this routed event dispatching. The EventTrigger subscribes directly to the CLR event with given EventName (using Reflection: Type.GetEvent).
The event source object will be determined using the following order:
If SourceObject property of the EventTrigger is set, try to get that object. If it's not null, use it.
If SourceName of the EventTrigger is set, get the object using a named objects resolver.
Otherwise, use the associated object the EventTrigger is directly attached to.
The EventName has to be a simple event name. Fully qualified event names (i.e. Type.EventName) are not supported. This is in contrast with System.Windows.EventTrigger, where you can specify the fully qualified routed event name in the RoutedEvent property.
So in short, you cannot simplify your WrapPanel in that way.
Maybe you could use an ItemsControl with a WrapPanel as ItemsPanel and define a DataTemplate containing your MyCustomUserControl with the EventTriggers.

wpf xaml resource dictionary add custom property

I want to use the following xaml code for navigation in some pages:
<Button Content="Go to page2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Source" TargetObject="{Binding NavigationService, RelativeSource={RelativeSource AncestorType={x:Type Page}, Mode=FindAncestor}}">
<ei:ChangePropertyAction.Value>
<System:Uri>Page2.xaml</System:Uri>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
So I´m wondering if there is a possibility to outsource the interaction part into a style (in a resource dictionary) and add a custom property like "NavigationUri" where you can directly declare the page to navigate to.
Another idea (which would probably be the better approach) is to create a custom control and inherit from button class.
Anywhere I would prefer a more compact and lean way without code behind.
Please let me know, which is the more suitable solution and how to implement it.
Although there are various simple techniques to make our Behavior Xaml as static resource. But, we need a custom behavior, as we are using a parameter in the form of Page name to navigate to. This variable demands programming.
So, I came up with
a. Custom behavior(NavigationBehavior), and
b. Button subclassing(NavigationButton)
NavigationBehavior
using System;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApplication1.Navigation
{
public class NavigationBehavior:Behavior<NavigationButton>
{
protected override void OnAttached()
{
AssociatedObject.Click += AssociatedObject_Click;
base.OnAttached();
}
void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
{
((Page)AssociatedObject.DataContext).NavigationService.Source = new Uri(AssociatedObject.DestinationUri, UriKind.Relative);
}
}
}
NavigationButton
namespace WpfApplication1.Navigation
{
public class NavigationButton : Button
{
NavigationBehavior behavior = new NavigationBehavior();
public NavigationButton()
{
behavior.Attach(this);
}
public string DestinationUri { get; set; }
}
}
Usage :
<nav:NavigationButton Content="Navigate to Page2" DestinationUri="/Navigation/Page2.xaml" />
Important Note
We are using DataContext property in our behavior to get access to the containing page. So, set this.DataContext = this; in the constructor of your all pages.
One can try using a common base class / interface to avoid this.
you cannot have interaction triggers extracted but there is a workaround to get this done
you can create a Button in resources with interaction logic in it, and then where needed you can have a content control with content set to your resource.
something like this --
<Window.Resources>
<Button x:Key="MyButton"
Content="Go to page2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Source" TargetObject="{Binding NavigationService, RelativeSource={RelativeSource AncestorType={x:Type Page}, Mode=FindAncestor}}">
<ei:ChangePropertyAction.Value>
<System:Uri>Page2.xaml</System:Uri>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Window.Resources>
<Grid>
<ContentControl Name="MyLocalButton" Content="{StaticResource MyButton}" />
</Grid>
You should put the navigation logic in a command in each page's view model and bind the buttons' Command properties to those commands, or else give the view models each a NextPage property and bind to that.
And create separate buttons in the views. Define a Style in the resource dictionary to make them all look the same.
Defining a Button as a resource is a bad idea. Among other things, there's only one instance of it and it can have only one visual parent, so when you add it to one view it'll vanish from the last. And you run into ugly problems like this one. You're working against XAML, and XAML is already hard enough when you're working with it.

WPF: How to bind MouseEnter event from an ItemsControl.ItemTemplate to elements outside this ItemsControl

I'm new to WPF but I manage to advance slowly towards writing my first serious project. I'm not sure I use the correct terms so please bear with me.
I implemented a set of ItemsControl (User Controls). The item source is a collection of items. Each item holds much data including it's own ID. I would like the User Control to change a property when the mouse hovers over another control outside this set of ItemsControl.
To complicate things the other control is also an element of another set of ItemsControl.
I already managed to store the ID of the hovered-over control in a top-level property (In the top DataContext) but I can't find a way to bind to it from within the element buried inside the DataTemplate.
Here's a screenshot:
In this example the user hovers over channel 14 - as a result, bottom axes X and Z should highlight (I chose them arbitrarily - according to data stored in the database).
I'd be grateful for any idea. Examples will be most welcome!
Assuming that:
1) You are using the MVVM design pattern.
2) That there is an underlying ViewModel for each of the items in your ItemsControl.
Then what you need to do is handle the MouseEnter and MouseLeave events in your view and have them trigger Commands in your view model. Then have those ViewModels update properties in your other ViewModels to have them highlight the appropriate items in the other ItemsControl.
<UserControl x:Class="ClassName"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4">
<i:Interaction.Triggers>
<!-- this will call the associated commands on your viewmodel when the mouse enters/leaves the corresponding view in your itemscontrol, from there you can create the viewmodel functionality you'd like-->
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseEnterCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid Height="10" Width="10">
<!--the content of your usercontrol-->
</Grid>
</UserControl>
Once you get the correct commands notifying your viewmodel that the mouse is hovering over it (or left it). You can manage the state of your viewmodels to create the affects you are looking for.
I really like the Blend SDK for stuff like this.
Since you mentioned you have the hovered-over ID setting in a property, you could start with a PropertyChangedTrigger at the root level. Next, you will probably want to invoke a command (rather than just a method), since your action includes a parameter (the ID). Use InvokeCommandAction for this. You can trigger a command either on the view or the view-model. If you want to trigger it on the view, then you'll probably want to use ElementName as the binding.
Here's an example.
<UserControl>
<i:Interaction.Triggers>
<!-- When "SelectedID" changes, invoke the "SelectedIDChangedCommand" on the
element "AxesAndButtons". Pass the value of "SelectedID" as the
command parameter -->
<ei:PropertyChangedTrigger Binding="{Binding SelectedID}">
<i:InvokeCommandAction CommandParameter="{Binding SelectedID}"
Command="{Binding ElementName=AxesAndButtons,Path=SelectedIDChangedCommand}" />
</ei:PropertyChangedTrigger>
<i:Interaction.Triggers>
<my:AxesAndButtonsControl x:Name="AxesAndButtons">
</my:AxesAndButtonsControl>
</UserControl>
I have assumed that ID property that gets changed is called "SelectedID" (and is a property of the root data context). Also, that your target user control has a defined ICommand "SelectedIDChangedCommand" dependency property that performs the update. That is, something like this:
public class AxesAndButtonsControl : UserControl
{
public static readonly DependencyProperty SelectedIDChangedCommand = DependencyProperty.Register("SelectedIDChangedCommand",
typeof(ICommand),
typeof(AxesAndButtonsControl),
new PropertyMetadata(null));
public AxesAndButtonsControl()
{
SelectedIDChangedCommand = new DelegateCommand(id => {
// highlight "X" and "Z" or whatever
});
}
}
Edit I just noticed that maybe you haven't bound the MouseOver event to update the SelectedID property yet. If that's the case, then you should be able to use an EventTrigger along with a ChangePropertyAction (in a FindAncestor or ElementName binding) to update it. Something like this:
<DataTemplate>
<Grid>
<i:Interaction.Triggers>
<!-- When "MouseEnter" gets triggered, set the property "SelectedID" on the
data context of the closest "UserControl" parent to the value of "ItemID"
in the current data context -->
<i:EventTrigger EventName="MouseEnter">
<ei:ChangePropertyAction Value="{Binding ItemID}"
TargetObject="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext}"
PropertyName="SelectedID" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</DataTemplate>

Flexible Close Command

I am searching for a way how to realize a window close command, which is usable in the window's CommandBindings and by other sources, for example a button. I am working with MVVM. So far, I only found solutions that were either meant for use by CommandBindings or with a simple Command featured inside the MVVM. I need to have one central handler for this.
Thanks in advance!
Simply use EventToCommand.
ViewModel:
public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}
and in XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" />
</i:EventTrigger>
</i:Interaction.Triggers>

Resources