Generic ViewModel for mouse events WPF C#

Question:

I am somewhat new to MVVM . I am trying to create a generic ViewModel to manage all the mouse events of my application and have all that code centralized. I have seen several examples of MVVM but most are very complex classes that in the end all they do is call a void method.

I currently have a group of methods in each View that I would like to centralize in a ViewModel for reasons of improving system maintainability and not having to go to each View to modify the code.


view

#region Eventos del Ratón

private void MouseEnter(object sender, MouseEventArgs e)
{
    dynamic control = sender as Image;
    if (control != null)
        control.OpacityMask = new SolidColorBrush { Opacity = 1, Color = Colors.Black };

    //...
}

private void MouseLeave(object sender, MouseEventArgs e)
{
    //...
}

private void MouseLeftButtonDown(object sender, MouseEventArgs e)
{
    //...
}

#endregion

The problem is that I don't know how to create the commands with parameters in the ViewModel and call them in the XAML passing the sender as a parameter. Excuse me if it is a very basic question, I am new to this MVVM thing .

Hope someone can help me

Answer:

I will answer you how to handle ICommands with parameters, for this example a button that removes an item from a listbox (button integrated inside an item from a listbox and sends itself to the command). You adapt it:

First create this class (it's generic, it works for every project):

public class ParamCommand : ICommand
{
    private Action<object> _action;
    private readonly Func<bool> _canExecute;

    public ParamCommand(Action<object> action)
    {
        _action = action;
        _canExecute = () => true;
    }

    public ParamCommand(Action<object> action, Func<bool> canExecute)
    {
        _action = action;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (this._canExecute == null)
        {
            return true;
        }
        else
        {
            bool result = this._canExecute.Invoke();
            return result;
        }
    }

    public void Execute(object parameter)
    {
        if (CanExecute(parameter))
        {
            if (parameter != null)
            {
                _action(parameter);
            }
        }
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

}

Then if you want to call a command from a common button:

<!-- Botón comun -->
 <Button Content="Quitar" 
 Command="{Binding EliminarArchivoCommand}" 
 CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>

If the button is inside a container as an item of a ListBox, note that it will call the DataContext first, and it will specify the AncestorType of type ListBox and all this means that it will use the ICommand of the datacontext of the listbox that is hierarchically above.. the When the button is in a listbox item, it will not see the viewmodel by itself, but it will see the control that contains it and through it, it will see the viewmodel.

<!-- Botón dentro de un item de un listbox -->
<Button 
Content="Quitar este item del listbox" 
Command="{Binding DataContext.EliminarItemCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" 
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}" />

Then in your ViewModel you declare the ICommand like so:

private ICommand _eliminarArchivoCommand;
public ICommand EliminarArchivoCommand
{
    get
    {
        if (_eliminarArchivoCommand == null)
        _eliminarArchivoCommand = new ParamCommand(new Action<object>(EliminarArchivo));
        return _eliminarArchivoCommand;
    }
}

And the method would be like this:

private void EliminarArchivo(object obj)
{
    if (obj != null) //Este object es el botón!!
        //aquí está tu boton
        MessageBox.Show(obj.GetType().Name); //Button
    }
}

You can then use the button and get its DataContext and properties

((Button)obj).Content

But since your viewmodel is global you could call the method with any control, so you should identify which control is the one that called the command, but that's another problem, with a switch you could… but the answer ends here.

switch(obj.GetType().Name)
{
    case "Button":

    break;
    case "Grid":

    break;
    default:
    break;
    }

ADD FOR COMMENT:

To manage different events of the same control you can do it like this, for example the same ListBox:

First: Add the namespace above in XAML

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

You can put it directly below:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

And then in the control you can manage all its events like this:

<ListBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding CargarItemSourceCommand}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseLeftButtonDown">
            <i:InvokeCommandAction Command="{Binding MostrarMenuContextualCommand}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="KeyDown">
            <i:InvokeCommandAction Command="{Binding ActualizarAlgoCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

If you pay attention, you only put the name of the event, you can put as many as you want.

An example with PasswordBox that also has a CommandParameter which is what you ask above.

<i:EventTrigger EventName="PasswordChanged">
    <i:InvokeCommandAction Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=tx_password}"/>
</i:EventTrigger>

An example with a TextBlock that is inside a ListBox item where the DataContext appears again, which is an indicator that the control is inside another container type control.

<TextBlock Text="{Binding MyProperty}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseLeftButtonDown">
            <i:InvokeCommandAction Command="{Binding DataContext.CopiarTextoCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TextBlock}, Path=Text}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBlock>
Scroll to Top