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:


  1. Create WPF app.

  2. Install Fluent MVVM nuget package.

  3. Create a ViewModel class.


    using MoralesLarios.FluentMVVM;
    
    namespace FluentMVVMExamplesDesc.Commands.ViewModels
    {
        public class MainViewModel : ViewModelBase
        {
    
    
        }
    }
    

  4. 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>
    
    For extreme simplicity of the example it isn't necessary to configure IoC, since we don't need to inject any class.


App run empty.


App 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.



  1. Add a SimpleRelayCommand to our ViewModel class. (Add using to MoralesLarios.FluentMVVM.Infrastructure).

    public SimpleRelayCommand MySimpleRC => new SimpleRelayCommand
    (
        () => FillDisplay("Click (Command)")
    );
    

  2. Add new button with binding to last SimpleRelayCommand to our View.

    <Button Command="{Binding MySimpleRC}" Content="Only Executed" ... >
    

Result.


App Empty


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.


App Empty

We would follow this rule.



App Empty


  1. Add a SimpleRelayCommand with Execute and CanExecute parameters.

    public SimpleRelayCommand MySimpleRCWithCanExecute => new SimpleRelayCommand
    (
        () => FillDisplay("Click (Command)"),
        () => Math.Truncate(Convert.ToDecimal(DateTime.Now.Second / 10)) % 2 == 0
    );
    

  2. Add new button with binding to last SimpleRelayCommand to our View.

    <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.



  1. 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 a CommandParameter, and is the value cames from View.


  2. 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:


Button_GotFocus(object sender, RoutedEventArgs e)
Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
Button_MouseUp(object sender, MouseButtonEventArgs e)
Button_ToolTipOpening(object sender, ToolTipEventArgs e)

RelayCommands Examples:


RelayEventToCommand<RoutedEventArgs> ButtonGotFocusCommand
RelayEventToCommand<MouseButtonEventArgs> ButtonMouseDobleClickCommand
RelayEventToCommand<MouseButtonEventArgs> ButtonMouseUpCommand
RelayEventToCommand<ToolTipEventArgs> ButtonToolTipOpening



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"/>

Literal clock

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");
}
MessengerManager is part of the Messages section , click in menu for more info. We use a message to change the TextBlock color.

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.



EventToCommandArgs properties

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");
}
We show the EventToCommandParameter for didactic reasons. It isn't practic for this example.




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"/>
It's only a simple property.


EventToCommandArgs example


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.


Elipse

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.


MultiEvents


All process in video





No hay comentarios :

Publicar un comentario