Не могу передать Action в UserControl [ C# ] [ WPF ]

В UserControl есть Border, на него накинуто событие Mouse Down. Также есть свойство типа Action.

При вызове Mouse Down надо выполнить полученный Action.

Что бы не делал, он (Action) попросту не передаётся в User Control. Подскажите причину, если знаете. Или, пожалуйста, скажите как правильно такое реализовать (передачу и реализацию метода / комманды) в User Control.

PS: Не сделал Button вместо Border, потому что никак не мог убрать подсветку кнопки при наведении.

Свойство в UC:

public Action ButtonMouseDown
        {
            get { return (Action)GetValue(ButtonMouseDownProperty); }
            set { SetValue(ButtonMouseDownProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ButtonMouseDown.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ButtonMouseDownProperty =
            DependencyProperty.Register("ButtonMouseDown", typeof(Action), typeof(ButtonIndicatorsInfo));

Mouse Down в UC:

private void buttonMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (ButtonMouseDown != null)
            {
                Application.Current.Dispatcher.InvokeAsync(ButtonMouseDown);
            }
        }

Как я назначаю Action:

public MainViewModel()
        {
            ActionForUC = new Action(ClickMethod);
        }
        public Action ActionForUC { get; set; }
        private void ClickMethod()
        {
            MessageBox.Show("Тест");
        }

В свойствах у UC указываю:

ButtonMouseDown="{Binding ActionForUC}"

Ответы (1 шт):

Автор решения: aepot

Используйте команды

UserControl

Разметка

<UserControl x:Class="WpfApp1.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Style TargetType="{x:Type local:MyUserControl}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:MyUserControl}">
                        <Button Content="Click me!" Command="{TemplateBinding Command}" CommandParameter="{TemplateBinding CommandParameter}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>
</UserControl>

DependencyProperty

public static readonly DependencyProperty CommandProperty =
     DependencyProperty.Register("Command", typeof(ICommand), typeof(MyUserControl), new PropertyMetadata(null));
public static readonly DependencyProperty CommandParameterProperty =
     DependencyProperty.Register("CommandParameter", typeof(object), typeof(MyUserControl), new PropertyMetadata(null));

public ICommand Command
{
    get => (ICommand)GetValue(CommandProperty);
    set => SetValue(CommandProperty, value);
}

public object CommandParameter
{
    get => GetValue(CommandParameterProperty);
    set => SetValue(CommandParameterProperty, value);
}

Вызов

if (Command?.CanExecute?.Invoke(CommandParameter) ?? true)
{
    Command?.Execute?.Invoke(CommandParameter);
}

Использование

Разметка

<local:MyUserControl Command="{Binding MyCommand}" CommandParameter="Hello World"/>

Дополнительный класс

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

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

    public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
        => (_execute, _canExecute) = (execute, canExecute);

    public bool CanExecute(object parameter)
        => _canExecute == null || _canExecute(parameter);

    public void Execute(object parameter)
        => _execute(parameter);
}

Код вьюмодели

private ICommand _myCommand;

public ICommand MyCommand => _myCommand ??= new RelayCommand(parameter =>
{
    if (parameter is string text)
    {
        MessageBox.Show(text);
    }
}, parameter => true);

Это совершенно стандартная команда, обычная Button имеет точно такую же с точно таким же параметром.

Дополнительно, если у вашего контрола есть свойство Enabled, которое визуально "отключает" контрол, то в случае если CanExecute возвращает false, то надо применить стиль отключенного контрола к юзерконтролу. Чтобы отслеживать изменение CanExecute, нужно подписаться на событие CanExecuteChanged команды при изменении самой команды в DP. Button делает точно так же.

Если у вас в разметке юзерконтрола есть Button или другой кликабельный контрол с поддержкой команды, то можете просто пробросить команду и параметр туда через TemplateBinding, тогда реализовать логику вызова команды не придется.

→ Ссылка