While using the ModelViewViewModel Pattern, people running in different kind of problems.
One common problem is the handling of the UI Events in the ViewModel without to write any code in the code behind file.
In my example I want to get the position of the mouse while it moves.
My approach for a solution is:
1. Write a Behavior to grab the EventArgs of its associated object
public class ExtendedInvokeCommandAction : TriggerAction<FrameworkElement>
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ExtendedInvokeCommandAction), new PropertyMetadata(null, CommandChangedCallback));
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(ExtendedInvokeCommandAction), new PropertyMetadata(null, CommandParameterChangedCallback));
private static void CommandParameterChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var invokeCommand = d as ExtendedInvokeCommandAction;
if (invokeCommand != null)
invokeCommand.SetValue(CommandParameterProperty, e.NewValue);
}
private static void CommandChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var invokeCommand = d as ExtendedInvokeCommandAction;
if (invokeCommand != null)
invokeCommand.SetValue(CommandProperty, e.NewValue);
}
protected override void Invoke(object parameter)
{
if (this.Command == null)
return;
if (this.Command.CanExecute(parameter))
{
var commandParameter = new ExtendedCommandParameter(parameter as EventArgs, this.AssociatedObject,
GetValue(CommandParameterProperty));
this.Command.Execute(commandParameter);
}
}
#region public properties
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public ICommand Command
{
get { return GetValue(CommandProperty) as ICommand; }
set { SetValue(CommandParameterProperty, value); }
}
#endregion
}
2. Write a class to store the Information about the event and the object
public class ExtendedCommandParameter
{
public ExtendedCommandParameter(EventArgs eventArgs, FrameworkElement sender, object parameter)
{
EventArgs = eventArgs;
Sender = sender;
Parameter = parameter;
}
public EventArgs EventArgs { get; private set; }
public FrameworkElement Sender { get; private set; }
public object Parameter { get; private set; }
}
3. Attach the Behavior to any object of the View
In my sample I attached my ExtendedInvokeCommandAction to a Rectangle
4. Bind this Behavior to a Command in the ViewModel and choose an Event on which the Command will be invoked.
In my sample below I used the MouseMove Event and bound this to the MouseMoved Command, which I defined in the ViewModel. Additionally you can bind any object to the Command as the CommandParameter, if you need it for some reasons.
I bound the View (name=”userControl”) in my sample(not needed).
<Rectangle Fill="#FF9B9BC5" Height="200" Stroke="Black" Width="200">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<local:ExtendedInvokeCommandAction
Command="{Binding MouseMoved}"
CommandParameter="{Binding ElementName=userControl}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
5. Handle the event in the ViewModel.
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
MouseMoved = new MainPageCommand(this); //initialize the Command
}
//gets the position of the mouse
private void OnMouseMove(ExtendedCommandParameter commandParameter)
{
MouseEventArgs eventArgs;
//cast the EventArgs to the type you expect, according to the event you handle
//f.e. MouseMove Event gets you MouseEventArgs
// Click Event gets you RoutedEventArgs
if (commandParameter.EventArgs.GetType() == typeof(MouseEventArgs))
{
eventArgs = commandParameter.EventArgs as MouseEventArgs;
if (commandParameter.Parameter != null)
{
var view = commandParameter.Parameter as UIElement;
MousePosition = eventArgs.GetPosition(view).ToString();
}
}
}
public MainPageCommand MouseMoved { get; set; }
private string _mousePosition;
public string MousePosition
{
get { return _mousePosition; }
set { _mousePosition = value; OnPropertyChanged("MousePosition"); }
}
#region INotifyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region command class
public class MainPageCommand : ICommand
{
public MainPageCommand(MainPageViewModel view)
{
_view = view;
}
private MainPageViewModel _view;
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
//call the method to handle the event
_view.OnMouseMove(parameter as ExtendedCommandParameter);
}
#endregion
}
#endregion
}
You can use this Behavior for every EventArgs you need. Just bind it to a Command and in the code of the Command handling cast the EventArgs field of the ExtendedCommandParameter object to the EventArgs you expect.
Download Visual Studio 2010 Solution