Easy Events To Commands
Fluent MVVM has 2 mechanisms for events treatment. These mechanisms are strongly linked and are RelayCommands
and EventToCommnads
.
RelayCommands
have 2 basic types. The others complex types have bounded to special EventsToCommands
.
As other characteristics of Fluent MVVM, EventToCommands
tries to improve and optimize old MVVM focus making it easier and less cumbersome. The old techniques coming from Expression aren’t simples and impractical. Fluent MVVM EventToCommands
allows send RoutedEventArgs
even properties + CommandParameter in the same dispatch.
In the first step we will study simples RelayCommands
to then move on to complex cases.
These classes are in MoralesLarios.FluentMVVM.Infrastructure
namespace.
Example app.
For App example we make simple IU of one View
and ViewModel
only. You can see a Fluent MVVM initialization example in QuickStart Section.
In Summary:
-
Create
WPF
app. -
Install Fluent MVVM
nuget package
. -
Create a
ViewModel
class.
using MoralesLarios.FluentMVVM; namespace FluentMVVMExamplesDesc.Commands.ViewModels { public class MainViewModel : ViewModelBase { } }
-
We will connecte ViewModel to View.
<Window x:Class="FluentMVVMExamplesDesc.Commands.MainWindow" ... xmlns:fluent="clr-namespace:MoralesLarios.FluentMVVM.Infrastructure;assembly=MoralesLarios.FluentMVVM" mc:Ignorable="d" fluent:AutoViewModelClass.ViewModelClassName="MainViewModel" ... > <Grid > </Grid> </Window>
App run empty.
On right, we will put a simple RelayCommands
(Buttons
, MenuItems
, etc), controls inherit of ButtonBase
than have a Command property.
On screen botton we will add controls without Command property to listen its events.
In center screen we will see results.
We will complete all bindings
for displays values between View
to ViewModel
. For this work we will use ViewBag ViewModel porperty. More info in its section ViewBag Property and QuickStart.
Initialize ViewBag properties in ViewModel
class.
public class MainViewModel : ViewModelBase { public MainViewModel() { InitializeViewBag(); } private void InitializeViewBag() { ViewBag.Event = string.Empty; ViewBag.CommandParameter = string.Empty; ViewBag.Target = string.Empty; ViewBag.ActualDateTime = string.Empty; // Isn't necessary datetime } }
We will create a private ViewModel helper method to fill display ViewBag properties
private void FillDisplay (string myevent, string commandParameter = "x:null", string target = "x:null", string actualDateTime = null) { actualDateTime = actualDateTime ?? DateTime.Now.ToString(); ViewBag.Event = myevent; ViewBag.CommandParameter = commandParameter; ViewBag.Target = target; ViewBag.ActualDateTime = actualDateTime; }
We will create a TextBlocks views bingdings.
<TextBlock Text="{Binding ViewBag.Event , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... /> <TextBlock Text="{Binding ViewBag.CommandParameter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... /> <TextBlock Text="{Binding ViewBag.Target , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... /> <TextBlock Text="{Binding ViewBag.ActualDateTime , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
We are already prepared to get into the examples.
SimpleRelayCommand
Is the simplest command in Fluent MVVM. It doesn’t allow send parameter from the View
. This RelayCommand
is advised for direct simple actions such (simple executions from Buttons
, MenuItems
and actions coming from controls than inherit from CommandBase class).
SimpleRelayCommand has 2 constructors:
public SimpleRelayCommand(Action execute) : base(a => execute()) { } public SimpleRelayCommand(Action execute, Func<bool> canExecute) : base(a => execute(), b => canExecute()) { }
Only Action execute parameter.
-
Add a SimpleRelayCommand to our ViewModel class. (Add using to MoralesLarios.FluentMVVM.Infrastructure).
public SimpleRelayCommand MySimpleRC => new SimpleRelayCommand ( () => FillDisplay("Click (Command)") );
-
Add new
button
withbinding
to last SimpleRelayCommand to ourView
.
<Button Command="{Binding MySimpleRC}" Content="Only Executed" ... >
Result.
All in video.
With Action execute and CanExecute parameter.
For this new example, we added a window timer. The idea is that new button
(action) is only available when time even seconds.
This is the new window timer.
We would follow this rule.
-
Add a SimpleRelayCommand with
Execute
andCanExecute
parameters.
public SimpleRelayCommand MySimpleRCWithCanExecute => new SimpleRelayCommand ( () => FillDisplay("Click (Command)"), () => Math.Truncate(Convert.ToDecimal(DateTime.Now.Second / 10)) % 2 == 0 );
-
Add new
button
withbinding
to last SimpleRelayCommand to ourView
.
<Button Command="{Binding MySimpleRCWithCanExecute}" Content="With Executed and CanExecuted" ... >
Since the execution is very similar to previous example, we will put the result directly in vido to see better the change.
RelayCommand (Generic)
Very similar than SimpleRalayCommand, RelayCommand generic version allows receive a parameter from View through CommandParameter
control property.
RelayCommands generics are advice from the same options than SimpleRelayCommand, but with parameters.
It has 2 constructors too, but with generic delegate versions.
public RelayCommand(Action<T> execute) : this(execute, null) { } public RelayCommand(Action<T> execute, Predicate<T> canExecute) { }
Since this type is very similar to any other MVVM toolkit, we will add to our example only the most complet type with CanExecute
with the same restrictions than the last SimpleRelayCommand CanExcecute (Timer Clock).
In this case we will pass parameter, this parameter will be a DateTime format.
-
Add a RelayCommand to our ViewModel.
public RelayCommand<string> MyRelayCommand => new RelayCommand<string> ( parameter => FillDisplay("Click (generic)", parameter, actualDateTime: DateTime.Now.ToString(parameter)), parameter => Math.Truncate(Convert.ToDecimal(DateTime.Now.Second / 10)) % 2 == 0 );
Is this case, our lambda expressions has a
parameter
, this parameter is aCommandParameter
, and is the value cames fromView
. -
Add bindings and commandParameters to our new
View
MenuItems
.<Menu > <MenuItem Header="Commands Time Formats"> <MenuItem Command="{Binding MyRelayCommand}" CommandParameter="ddMMyyyy" Header="Format - ddMMyyyy"/> <MenuItem Command="{Binding MyRelayCommand}" CommandParameter="yyyyMMdd" Header="Format - yyyyMMdd"/> </MenuItem> </Menu>
Video is the better choice to view solution.
EventToCommands and RelayEventToCommands
The main EventToCommand objective is allow use an ICommand ViewModel property as control event response different to CommandBase control property.
When we use any event different to ‘click’ we will have to use EventToCommands in the View and your referent RelayEventToCommands in ViewModel for events with (MouseEnter, KeyPress, etc).
RelayEventToCommand hasn't CanExecute parameter posibility.
RelayEventToCommand are tied to EventToCommand and we will use together.
EventToCommands
EventToCommand is a XAML exclusive use mainly. It is based in AtachDependencyProperties and it has only 3 AtachDependencyProperties:
EventToCommand.EventName
String AtachDependencyProperty. Stores event name to fires.
EventToCommand.Command
ICommand AtachDependencyProperty. Stores RelayEventToCommand to execute
EventToCommand.EventToCommandParameter
Object AtachDependencyProperty. Stores an EventToCommandParameter to send to ViewModel
Call Examples
<TextBox fluent:EventToCommand.EventName="KeyDown" fluent:EventToCommand.Command="{Binding Example1Command}" fluent:EventToCommand.EventToCommandParameter="One value" /> <DataGrid fluent:EventToCommand.EventName="Sorting" fluent:EventToCommand.Command="{Binding Example2Command}" fluent:EventToCommand.EventToCommandParameter="Two value" /> <ListBox fluent:EventToCommand.EventName="LostFocus" fluent:EventToCommand.Command="{Binding Example3Command}" fluent:EventToCommand.EventToCommandParameter="Three value" />
RelayEventToCommand
RelayEventToCommand hasn’t a CanExecute constructor parameter/property, because isn’t useless for this ICommand.
RelayEventToCommand is a generic type and your parameter type depends on 2 use cases:
1.- Without EventToCommandParameter
In this case, how to use the EventToCommandParameter, the generic parameter will be event class EvenArgs type. Normal events Examples:
RelayCommands Examples:
Example
let's go with our example.
We have added an action TextBlock near to clock to activate/desactivate with Mouse Up.
Add the XAML code.
<TextBlock x:Name="txtActionClock" Text="{Binding ViewBag.LiteralActionClock}" fluent:EventToCommand.EventName="MouseUp" fluent:EventToCommand.Command="{Binding LiteralClockMouseUpCommand}" Cursor="Hand" Foreground="Red"/>
ViewModel code:
public RelayEventToCommand<MouseButtonEventArgs> LiteralClockMouseUpCommand => new RelayEventToCommand<MouseButtonEventArgs>(LiteralClockMouseUpExecute); private void LiteralClockMouseUpExecute(MouseButtonEventArgs obj) { if(ViewBag.LiteralActionClock == "stop") { StopClock(); ViewBag.LiteralActionClock = "start"; } else { StartClock(); ViewBag.LiteralActionClock = "stop"; } MessengerManager._.Send($"{nameof(MainViewModel)}-ChangeColor"); }
All process in video.
2.- With EventToCommandParameter
Fluent MVVM EventToCommand offers send parameter action info apart of include in the same event (Args), and it can be useful to reuse commands for several similar actions.
If we use CommandParameter, we can’t build our RelayEventToCommands generics with an EventArgs type as generic type. The generic parameter type for this case is EventToCommandArgs. This class has these properties:
EventArgs
This property will have EventArgs event information, and for its use will be necessary cast to your event type.
EventToCommandParameter
Is an object property and has Command Parameter info.
EventName
Is a string property with an event name.
Our Example
For Add EventToCommandParameter to our example, we change the LiteralClockMouseUpCommand RelayEventToCommand type parameter to EventToCommandArgs type:
public RelayEventToCommand<EventToCommandArgs> LiteralClockMouseUpCommand => new RelayEventToCommand<EventToCommandArgs>(LiteralClockMouseUpExecute); private void LiteralClockMouseUpExecute(EventToCommandArgs obj) { var parameter = obj.EventToCommandParameter; // not use if (ViewBag.LiteralActionClock == "stop") { StopClock(); ViewBag.LiteralActionClock = "start"; } else { StartClock(); ViewBag.LiteralActionClock = "stop"; } MessengerManager._.Send($"{nameof(MainViewModel)}-ChangeColor"); }
Add EventToCommandParameter property to our TextBlock in our View.
<TextBlock x:Name="txtActionClock" Text="{Binding ViewBag.LiteralActionClock}" fluent:EventToCommand.EventName="MouseUp" fluent:EventToCommand.Command="{Binding LiteralClockMouseUpCommand}" fluent:EventToCommand.EventToCommandParameter="parameter 11111" Margin="248,6,0,6" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="15" FontWeight="Black" Cursor="Hand" Foreground="Red"/>
All in video
RelayMultiEventToCommand
RelayMultiEventToCommand allows the same RelayEventToCommand functionality but registering different control events.
RelayMultiEventToCommand isn’t a generic type and supplies an EventToCommandArgs execution parameter as RelayEventToCommand with EventToCommandParameter, that we saw few last lines.
To Indicate to Fluent MVVM that we use RelayMultiEventToCommand we must indicate events names separated by commas in EventName EventToCommand property.
Example code.
fluent:EventToCommand.EventName="MouseEnter,MouseLeave,SizeChanged,LostFocus"
Our Example
We add an elipse in our window to listener an events group. When these events fire, we show the info in our textblocks with an initial example. To better notice event changes, we will change the elipse color fill for each of them. This work we make with Fluent MVVM Messages engine.
We add an elipse to our View.
XAML code.
<Ellipse x:Name="MyElipse" Width="70" Height="70" Fill="Gray" Margin="314,45,315,13" fluent:EventToCommand.Command="{Binding ListenEventsMultiCommand}" fluent:EventToCommand.EventName="MouseEnter,MouseLeave,MouseMove,MouseLeftButtonDown,MouseLeftButtonUp,MouseRightButtonDown,MouseRightButtonUp,MouseWheel" />
We add RelayMultiEventToComand to our ViewModel.
ViewModel code.
public RelayMultiEventToCommand ListenEventsMultiCommand => new RelayMultiEventToCommand(ListenEventsMultiExecute); private void ListenEventsMultiExecute(EventToCommandArgs multiEventInfo) { var window = App.Current.MainWindow; // it isn't good practice var eventArgsInfo = multiEventInfo.EventArgs as MouseEventArgs; var eventButtonArgsInfo = multiEventInfo.EventArgs as MouseButtonEventArgs; var eventWhellArgsInfo = multiEventInfo.EventArgs as MouseWheelEventArgs; switch (multiEventInfo.EventName) { case "MouseEnter": case "MouseLeave": case "MouseMove": FillDisplay(multiEventInfo.EventName, target: $" X -> { eventArgsInfo.GetPosition(window).X } - Y -> { eventArgsInfo.GetPosition(window).Y }"); break; case "MouseLeftButtonDown": case "MouseLeftButtonUp": case "MouseRightButtonDown": case "MouseRightButtonUp": FillDisplay(multiEventInfo.EventName, target: $" X -> { eventArgsInfo.GetPosition(window).X } - Y -> { eventArgsInfo.GetPosition(window).Y } - Clicks -> {eventButtonArgsInfo.ClickCount}"); break; case "MouseWheel": FillDisplay(multiEventInfo.EventName, target: $" X -> { eventArgsInfo.GetPosition(window).X } - Y -> { eventArgsInfo.GetPosition(window).Y } - Delta -> {eventWhellArgsInfo.Delta}"); break; } MessengerManager._.Send($"{nameof(MainViewModel)}-Listener", multiEventInfo.EventName); }
At hte beginning of the method, we performed EventArgs casting for the 3 events types requiered. We have mad a switch case with event name to call a FillDisplay method. At the end, we sent a message to view codebehind to change elipse color.
View Codebehind code.
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); MessengerManager._.Subscribe($"{nameof(MainViewModel)}-ChangeColor", ChangeColorAction); MessengerManager._.Subscribe<string>($"{nameof(MainViewModel)}-Listener", ListenerAction); } private void ListenerAction(string obj) { switch (obj) { case "MouseEnter": MyElipse.Fill = Brushes.Aqua; break; case "MouseLeave": MyElipse.Fill = Brushes.Gray; break; case "MouseMove": MyElipse.Fill = Brushes.Green; break; case "MouseLeftButtonDown": MyElipse.Fill = Brushes.Blue; break; case "MouseLeftButtonUp": MyElipse.Fill = Brushes.Yellow; break; case "MouseRightButtonDown": MyElipse.Fill = Brushes.BlueViolet; break; case "MouseRightButtonUp": MyElipse.Fill = Brushes.Orange; break; case "MouseWheel": MyElipse.Fill = Brushes.Red; break; } } private void ChangeColorAction() { txtActionClock.Foreground = txtActionClock.Foreground == Brushes.Red ? Brushes.Green : Brushes.Red; } }
We have suscribed to ViewModel message 'Listener' to change elipse color.
More info about Messegener in your section.
All process in video
No hay comentarios :
Publicar un comentario