Where to put events when using MVVM? - wpf

Should I put all events in views code behind or there is a more proper way, like place commands in ViewModel?
For example, I want to open Tab on double click on the datagrid row, where should I handle this event?

No you should not put events in code behind. In MVVM (Model-View-ViewModel) design pattern, the view model is the component that is responsible for handling the application's presentation logic and state. This means that your view's code-behind file should contain no code to handle events that are raised from any user interface (UI) element.
for eg if you have button in your xaml
<Button Content="OK" Click="btn_Click"/>
protected void btn_Click(object sender, RoutedEventArgs e)
{
/* This is not MVVM! */
}
Instead you can use WPF Command.All you have to do is bind to its Execute and CanExecute delegates and invoke your command.
So your code will now be
public class ViewModel
{
private readonly DelegateCommand<string> _clickCommand;
public ViewModel()
{
_clickCommand = new DelegateCommand(
(s) => { /* perform some action */ }, //Execute
null
} //CanExecute );
public DelegateCommand ButtonClickCommand
{
get { return _clickCommand; }
}
}
<Button Content="COOL" Command="ButtonClickCommand"/>

Kyle is correct in that your handlers should appear in the view model. If a command property doesn't exist then you can use an interaction trigger instead:
<DataGrid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Mode=OneWay, Path=OpenClientCommand}" CommandParameter="{Binding ElementName=searchResults, Path=SelectedItems}" />
</i:EventTrigger>
</i:Interaction.Triggers>
... other stuff goes here ...
</DataGrid>
Or you can use MVVM Lite's EventToCommand, which also allows you to pass in the message parameters:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
Which is used in in this case to cancel the window close event in response to the "Are you sure you want to quit?" dialog:
public ICommand ClosingCommand { get { return new RelayCommand<CancelEventArgs>(OnClosing); } }
private void OnClosing(CancelEventArgs args)
{
if (UserCancelsClose())
args.Cancel = true;
}
Relevant namespaces are as follows:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"

Related

How to bind Click in combobox in the view model?

I would like to do something when a click in a combobox, in a MVVM pattern.
I am trying to use input bindings in this way:
<ComboBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding BuscarKeyDownCommand}"/>
<MouseBinding Gesture="LeftClick" Command="{Binding TiposFacturasMouseLeftClickCommand}"/>
</ComboBox.InputBindings>
In my view model I have this code:
private RelayCommand _tiposFacturasMouseLeftClickCommand;
public RelayCommand TiposFacturasMouseLeftClickCommand
{
get { return _tiposFacturasMouseLeftClickCommand ?? (_tiposFacturasMouseLeftClickCommand = new RelayCommand(param => TiposFacturasMouseLeftClick(), param => true)); }
}
private async void TiposFacturasMouseLeftClick()
{
try
{
//search for items.
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
But the command is not fired.
However, the key return input binding works as expected.
Which is the best way to bind the click event of the combobox? Or perhaps there are another better solution to search the items on the combobox the first time that I click on it.
Thanks.
You could install the Microsoft.Xaml.Behaviors.Wpf NuGet package and use an EventTrigger to invoke a command when an event is raised, e.g.:
<ComboBox xmlns:i="http://schemas.microsoft.com/xaml/behaviors">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown" >
<i:InvokeCommandAction Command="{Binding TiposFacturasMouseLeftClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ComboBoxItem>1</ComboBoxItem>
<ComboBoxItem>2</ComboBoxItem>
<ComboBoxItem>3</ComboBoxItem>
</ComboBox>
Please refer to this blog for more informatio about how to handle events in MVVM.

WPF MVVM passing event parameters to view model

I have grid control and I need to pass MouseWheel event to view model.
Now I'm doing this like that
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseWheel">
<i:InvokeCommandAction Command="{Binding MouseWheelCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
but I need to do different actions on mouse scroll up and mouse scroll down.
How to do that?
Can I do that without code in view and without extern libraries? Im using c#, wpf, visual studio 2010 express.
You can use input bindings with a custom mouse gesture, which is very easy to implement:
public class MouseWheelUp : MouseGesture
{
public MouseWheelUp(): base(MouseAction.WheelClick)
{
}
public MouseWheelUp(ModifierKeys modifiers) : base(MouseAction.WheelClick, modifiers)
{
}
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
if (!base.Matches(targetElement, inputEventArgs)) return false;
if (!(inputEventArgs is MouseWheelEventArgs)) return false;
var args = (MouseWheelEventArgs)inputEventArgs;
return args.Delta > 0;
}
}
and then use it like this:
<TextBlock>
<TextBlock.InputBindings>
<MouseBinding Command="{Binding Command}">
<MouseBinding.Gesture>
<me:MouseWheelUp />
</MouseBinding.Gesture>
</MouseBinding>
</TextBlock.InputBindings>
ABCEFG
</TextBlock>
For this you need MouseWheelEventArgs in your MVVM. So Pass this EventArgs as commandParamter.
You can refer this link ---
Passing EventArgs as CommandParameter
Then in your View-Model Class you can use this event args as follow
void Scroll_MouseWheel(MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
// Mouse Wheel Up Action
}
else
{
// Mouse Wheel Down Action
}
e.Handled = true;
}

MVVM way to wire up event handlers in Silverlight

Using an MVVM pattern in Silverlight/WPF, how do you wire up event handers? I'm trying to bind the XAML Click property to a delegate in the view model, but can't get it to work.
In other words, I want to replace this:
<Button Content="Test Click" Click="Button_Click" />
where Button_Click is:
private void Button_Click(object sender, RoutedEventArgs e)
{
// ...
}
with this:
<Button Content="Test Click" Click="{Binding ViewModel.HandleClick}" />
where HandleClick is the handler. Attempting this throws a runtime exception:
Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Windows.RoutedEventHandler'.
The MVVM way to do so is by using commands and the ICommand interface.
The Button control has a property named Command which receives an object of type ICommand
A commonly used implementation of ICommand is Prism's DelegateCommand. To use it, you can do this in your view model:
public class ViewModel
{
public ICommand DoSomethingCommand { get; private set; }
public ViewModel()
{
DoSomethingCommand = new DelegateCommand(HandleDoSomethingCommand);
}
private void HandleDoSomethingCommand()
{
// Do stuff
}
}
Then in XAML:
<Button Content="Test Click" Command={Binding DoSomethingCommand} />
Also, make sure that the viewmodel is set as your view's DataContext. One way to do so is in your view's code-behind:
this.DataContext = new ViewModel();
This article is a good place to start if you want to know more about MVVM.
The answer is to use the extensions provided by Microsoft in the Prism framework. With the DLLs System.Windows.Interactivity.dll and Microsoft.Expression.Interactions.dll, it's possible to bind an event to a handler method in a view model:
<Button Content="Test Click"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding ViewModel}" MethodName="HandleClick" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

Another implementation of WPF Event to Command (with problems)

I am looking at various implementations of hooking a ICommand up to a control's event. So for instance the GotFocus of a TextBox should call a GotFocusCommand in my View Model. I then got an idea to implement my own version (for my own learning) and it is working well, but I can only link one event to one command in the XAML.
( Basically I just use reflection to find the specified Event and then do a AddEventHandler that executes the command )
This works fine :
<Button
local:EventToCommand.Event="Click"
local:EventToCommand.Command="{Binding TestCommand}"
/>
But this does not :
<Button
local:EventToCommand.Event="Click"
local:EventToCommand.Command="{Binding ClickCommand}"
local:EventToCommand.Event="GotFocus"
local:EventToCommand.Command="{Binding GotFocusCommand}"
/>
as you it leads to a duplicate attribute name error.
Would it be possible to do something like :
<Button>
<Some Xaml Element>
<local:EventToCommand Event="Click" Command="{Binding ClickCommand}" />
<local:EventToCommand Event="GotFocus" Command="{Binding GotFocusCommand}" />
</Some Xaml Element>
</Button>
to "map" multiple events to commands ?
There are a couple of ways you could approach this, either using an Attached Property or inheriting from Button and adding your own DependencyProperty that contains a list of EventToCommand objects, and when you add to that collection you wire up the event to command. If this seems confusing, I can try to whip up some examples.
C#
public class EventedButton : Button
{
public static DependencyProperty EventCommandsProperty
= DependencyProperty.Register("EventCommands", typeof(EventToCommandCollection), typeof(EventedButton), new PropertyMetadata(null));
public EventToCommandCollection EventCommands
{
get
{
return this.GetValue(EventCommandsProperty) as EventToCommandCollection;
}
set
{
this.SetValue(EventCommandsProperty, value);
}
}
public EventedButton()
{
this.EventCommands = new EventToCommandCollection(this);
}
}
Xaml:
<local:EventedButton>
<local:EventedButton.EventCommands>
<local:EventToCommand />
</local:EventedButton.EventCommands>
</local:EventedButton>
Inside of EventToCommandCollection, you would attach/detach to the Event you wanted when items are added to the collection.
UPDATE: Attached Property
Here is some code to do the collection as an attached property:
C#
public static DependencyProperty CommandsProperty =
DependencyProperty.RegisterAttached(
"Commands",
typeof(ICollection<EventToCommand>),
typeof(DependencyObject),
new PropertyMetadata(null, OnCommandsChanged));
private static void OnCommandsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Attach/Detach event handlers
}
public static void SetCommands(DependencyObject element, ICollection<EventToCommand> value)
{
element.SetValue(CommandsProperty, value);
}
public static ICollection<EventToCommand> GetCommands(DependencyObject element)
{
return (ICollection<EventToCommand>)element.GetValue(CommandsProperty);
}
Xaml:
<local:EventedButton>
<local:EventToCommand.Commands>
<local:EventToCommandCollection>
<local:EventToCommand/>
</local:EventToCommandCollection>
</local:EventToCommand.Commands>
</local:EventedButton>
Using the Blend Event Triggers and an action negates the need to handle your own collections.
And it can be added to any control.
See MVVM Lights EventToCommand
Or my extension of it here.(source)
GalaSoft MVVM Light ToolKit - EventToCommand you can do this
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click" >
<cmd:EventToCommand
PassEventArgsToCommand="True"
Command="{Binding ButtonClick}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus" >
<cmd:EventToCommand
PassEventArgsToCommand="True"
Command="{Binding ButtonGotFocus}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Where import this namespaces
i- xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
cmd-xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;
assembly=GalaSoft.MvvmLight.Extras.WPF4"

MVVM - WPF DataGrid - AutoGeneratingColumn Event

I'm currently taking a good look at the excellent toolkit from Laurent and I have the following question.
From Blend 4, I have added an EventTrigger for the Loaded event, in my ViewModel I have the following:
public RelayCommand rcAutoGeneratingColumn { get; private set; }
In the constructor I have:
rcAutoGeneratingColumn =
new RelayCommand(o => DataGridAutoGeneratingColumn(o));
Also in the ViewModel, I have the method which I wish to be invoked by the RelayCommand:
private void DataGridAutoGeneratingColumn(Object o)
{
DataGrid grid = (DataGrid)o;
foreach (DataGridTextColumn col in grid.Columns)
{
if (col.Header.ToString().ToLower() == "id")
{
col.Visibility = System.Windows.Visibility.Hidden;
}
}
}
My XAML contains the following (for the DataGrid):
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<GalaSoft_MvvmLight_Command:EventToCommand
Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
There is NO PROBLEM here the code works just fine, but obviously the event used to hide certain columns should be the AutoGeneratingColumn event and not Loaded.
I have used to Loaded event as a getaround.
I was hoping that I could relay any event offered by the control so that, in this case, the following would work instead:
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn">
<GalaSoft_MvvmLight_Command:EventToCommand
Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I am unable to get the AutoGeneratingColumn event to trigger, and I'm hoping that I've overlooked something and appreciate any advice given!
This behaviour is the same with the GridControl from DevExpress, in that the Loaded event is triggered whereas the ColumnsPopulated event (this being the equivalent of the AutoGeneratingColumn event) is not.
DevExpress offered the following information with regard to my question:
"We have reviewed this question, and come to an interesting conclusion. It looks like the visual tree is not being built at the moment when the Interaction.Triggers are being processed"
If this is true, and there is no other way in which to invoke the events within the ViewModel, then one would have to go ahead and - by using trial and error - note which of the DataGrid events (of which there are over 100) can be invoked in this way and which cannot!
One would like to think that every event which is available in the code-behind, can also be reached when applying the MVVM pattern.
I have searched for an answer but I cannot rule out that I have overlooked something, so if this is to be the case, then please accept my apologies!
You don't have to use evil code behind ;-) You can do this using an attached behaviour...
public class AutoGeneratingColumnEventToCommandBehaviour
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(AutoGeneratingColumnEventToCommandBehaviour),
new PropertyMetadata(
null,
CommandPropertyChanged));
public static void SetCommand(DependencyObject o, ICommand value)
{
o.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject o)
{
return o.GetValue(CommandProperty) as ICommand;
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as DataGrid;
if (dataGrid != null)
{
if (e.OldValue != null)
{
dataGrid.AutoGeneratingColumn -= OnAutoGeneratingColumn;
}
if (e.NewValue != null)
{
dataGrid.AutoGeneratingColumn += OnAutoGeneratingColumn;
}
}
}
private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dependencyObject = sender as DependencyObject;
if (dependencyObject != null)
{
var command = dependencyObject.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
Then use it in XAML like this...
<DataGrid ItemsSource="{Binding MyGridSource}"
AttachedCommand:AutoGeneratingColumnEventToCommandBehaviour.Command="{Binding CreateColumnsCommand}">
</DataGrid>
Just set EventTrigger.SourceObject property.
<DataGrid
x:Name="DataGrid"
AutoGenerateColumns="True"
IsReadOnly="True"
ItemsSource="{Binding Data}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
<local:EventToCommand
Command="{Binding ColumnGeneratingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
As MVVMLight from Galasoft is deprecated now, we can use CommunityToolkit.Mvvm package and use it like this:
<DataGrid AutoGenerateColumns="True"
Name="DataGrid"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
<i:InvokeCommandAction Command="{Binding AutoGeneratingColumnCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Note that Items property is a simple List, It could be an ObservableCollection or whatever.
The trick to get the fired event is to load your data after the window is loaded, or raise OnpropertyChanged on Items property after loaded.
<Window ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
In your View Model:
private RelayCommand<DataGridAutoGeneratingColumnEventArgs> myAutoGeneratingColumnCommand;
public RelayCommand<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumnCommand
{
get
{
if (myAutoGeneratingColumnCommand == null)
myAutoGeneratingColumnCommand = new RelayCommand<DataGridAutoGeneratingColumnEventArgs>(AutoGeneratingColumnCommandAction);
return myAutoGeneratingColumnCommand;
}
}
private void AutoGeneratingColumnCommandAction(DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Id")
{
e.Column.Width = 60;
}
else if (e.PropertyName == "Name")
{
e.Column.Header = "myName";
e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
else
e.Cancel = true; // ignore all other properties and remove their column
}
RelayCommand myLoadedCommand;
public RelayCommand LoadedCommand
{
get
{
if (myLoadedCommand == null)
myLoadedCommand = new RelayCommand(LoadedCommandAction);
return myLoadedCommand;
}
}
private void LoadedCommandAction()
{
Load(); // Populate the Items List
}
During the course of developing a project with MVVM you're going to have circumstances where you must handle events in your view's code-behind and EventToCommand just plain doesn't work. You especially find this with Silverlight, but I assume from your question that you're using WPF. It's okay to do some event handling in your view's code-behind, just don't put any business logic there. You can even leave the command in your view model, just call it directly from your event handler.
((YourViewModel)this.DataContext).rcAutoGeneratingColumn.Execute(sender);

Resources