MVVM передает EventArgs как параметр команды

Я использую Microsoft Expression Blend 4
у меня есть браузер ..,

[XAML] ConnectionView «Пустой код позади»

        <WebBrowser local:AttachedProperties.BrowserSource="{Binding Source}">
            <i:Interaction.Triggers>
                <i:EventTrigger>
                    <i:InvokeCommandAction Command="{Binding LoadedEvent}"/>
                </i:EventTrigger>
                <i:EventTrigger EventName="Navigated">
                    <i:InvokeCommandAction Command="{Binding NavigatedEvent}" CommandParameter="??????"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </WebBrowser>  

[C #] класс AttachedProperties

public static class AttachedProperties
    {
        public static readonly DependencyProperty BrowserSourceProperty = DependencyProperty . RegisterAttached ( "BrowserSource" , typeof ( string ) , typeof ( AttachedProperties ) , new UIPropertyMetadata ( null , BrowserSourcePropertyChanged ) );

        public static string GetBrowserSource ( DependencyObject _DependencyObject )
        {
            return ( string ) _DependencyObject . GetValue ( BrowserSourceProperty );
        }

        public static void SetBrowserSource ( DependencyObject _DependencyObject , string Value )
        {
            _DependencyObject . SetValue ( BrowserSourceProperty , Value );
        }

        public static void BrowserSourcePropertyChanged ( DependencyObject _DependencyObject , DependencyPropertyChangedEventArgs _DependencyPropertyChangedEventArgs )
        {
            WebBrowser _WebBrowser = _DependencyObject as WebBrowser;
            if ( _WebBrowser != null )
            {
                string URL = _DependencyPropertyChangedEventArgs . NewValue as string;
                _WebBrowser . Source = URL != null ? new Uri ( URL ) : null;
            }
        }
    }

[C #] Класс ConnectionViewModel

public class ConnectionViewModel : ViewModelBase
    {
            public string Source
            {
                get { return Get<string> ( "Source" ); }
                set { Set ( "Source" , value ); }
            }

            public void Execute_ExitCommand ( )
            {
                Application . Current . Shutdown ( );
            }

            public void Execute_LoadedEvent ( )
            {
                MessageBox . Show ( "___Execute_LoadedEvent___" );
                Source = ...... ;
            }

            public void Execute_NavigatedEvent ( )
            {
                MessageBox . Show ( "___Execute_NavigatedEvent___" );
            }
    }

[C #] класс ViewModelBase Здесь

Наконец:
Привязка с командами работает хорошо, и отображаются сообщения MessageBoxes.


Мой вопрос:
Как передать NavigationEventArgs в качестве параметров команды при возникновении события перехода?


person Ahmed Ghoneim    schedule 01.06.2011    source источник


Ответы (13)


Это нелегко поддержать. Вот статью с инструкциями о том, как передавать EventArgs в качестве параметров команды.

Возможно, вы захотите изучить использование MVVMLight - он напрямую поддерживает EventArgs в команде; ваша ситуация будет выглядеть примерно так:

 <i:Interaction.Triggers>
    <i:EventTrigger EventName="Navigated">
        <cmd:EventToCommand Command="{Binding NavigatedEvent}"
            PassEventArgsToCommand="True" />
    </i:EventTrigger>
 </i:Interaction.Triggers>
person E.Z. Hart    schedule 01.06.2011
comment
тогда нет прямого метода? так как я ненавижу использовать шаблоны, в которых всегда есть ошибки и т. д., так что мне нравится писать код с нуля - person Ahmed Ghoneim; 01.06.2011
comment
@ Ахмед Адель: Это довольно забавное заявление. - person H.B.; 02.06.2011
comment
На самом деле, просто используйте MVVM Light. Это намного проще, и вам действительно нужно использовать только классы RelayCommand и EventToCommand. - person Mike Post; 03.06.2011
comment
Silverlight / WPF - это вообще непростая задача, не так ли? - person Trident D'Gao; 26.11.2012
comment
Что такое пространство имен cmd? - person Stepan Ivanenko; 10.10.2019
comment
Пространство имен cmd следующее: xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform" - person S_Mindcore; 06.04.2021

Я стараюсь свести свои зависимости к минимуму, поэтому я реализовал это сам, вместо того, чтобы использовать EventToCommand из MVVMLight. Пока работает у меня, но отзывы приветствуются.

Xaml:

<i:Interaction.Behaviors>
    <beh:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" />
</i:Interaction.Behaviors>

ViewModel:

public ActionCommand<DragEventArgs> DropCommand { get; private set; }

this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);

private void OnDrop(DragEventArgs e)
{
    // ...
}

EventToCommandBehavior:

/// <summary>
/// Behavior that will connect an UI event to a viewmodel Command,
/// allowing the event arguments to be passed as the CommandParameter.
/// </summary>
public class EventToCommandBehavior : Behavior<FrameworkElement>
{
    private Delegate _handler;
    private EventInfo _oldEvent;

    // Event
    public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } }
    public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));

    // Command
    public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));

    // PassArguments (default: false)
    public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } }
    public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));


    private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var beh = (EventToCommandBehavior)d;

        if (beh.AssociatedObject != null) // is not yet attached at initial load
            beh.AttachHandler((string)e.NewValue);
    }

    protected override void OnAttached()
    {
        AttachHandler(this.Event); // initial set
    }

    /// <summary>
    /// Attaches the handler to the event
    /// </summary>
    private void AttachHandler(string eventName)
    {
        // detach old event
        if (_oldEvent != null)
            _oldEvent.RemoveEventHandler(this.AssociatedObject, _handler);

        // attach new event
        if (!string.IsNullOrEmpty(eventName))
        {
            EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName);
            if (ei != null)
            {
                MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
                _handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
                ei.AddEventHandler(this.AssociatedObject, _handler);
                _oldEvent = ei; // store to detach in case the Event property changes
            }
            else
                throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name));
        }
    }

    /// <summary>
    /// Executes the Command
    /// </summary>
    private void ExecuteCommand(object sender, EventArgs e)
    {
        object parameter = this.PassArguments ? e : null;
        if (this.Command != null)
        {
            if (this.Command.CanExecute(parameter))
                this.Command.Execute(parameter);
        }
    }
}

ActionCommand:

public class ActionCommand<T> : ICommand
{
    public event EventHandler CanExecuteChanged;
    private Action<T> _action;

    public ActionCommand(Action<T> action)
    {
        _action = action;
    }

    public bool CanExecute(object parameter) { return true; }

    public void Execute(object parameter)
    {
        if (_action != null)
        {
            var castParameter = (T)Convert.ChangeType(parameter, typeof(T));
            _action(castParameter);
        }
    }
}
person Mike Fuchs    schedule 01.05.2013
comment
Приемлемый уровень шаблонного кода, позволяющий отказаться от использования другой структуры. У меня тоже работает, ура! - person jeebs; 08.07.2013
comment
Интересное решение. Единственная проблема, с которой я столкнулся, заключается в том, что он помещает связанный с пользовательским интерфейсом код в ViewModel. DragEventArgs взят из System.Windows.Forms, а ActionCommand, возможно, также связан с пользовательским интерфейсом. Я стараюсь держать свои ViewModels отдельно друг от друга в их собственной сборке без каких-либо ссылок, связанных с пользовательским интерфейсом. Это предохраняет меня от случайного пересечения «черты». Это личное предпочтение, и каждый разработчик решает, насколько строго они хотят придерживаться шаблона MVVM. - person Matthew; 11.07.2013
comment
Мэтью, команды совершенно допустимы в шаблоне MVVM и принадлежат ViewModel. Можно утверждать, что EventArgs здесь не место, но если вам это не нравится, вы можете прокомментировать его по вопросу, а не по его решению. Кстати, DragEventArgs находится в пространстве имен System.Windows для WPF. - person Mike Fuchs; 12.07.2013
comment
@Matthew Я думаю, мы могли бы просто создать отдельный проект и добавить туда классы EventToCommandBehavior и ActionCOmmand. Таким образом, вы можете использовать SYstem.Windows там, где это необходимо, и избегать ссылок на пространство имен System.Windows.Interactivity, в котором размещены Behaviors. - person Adarsha; 23.01.2014
comment
@adabyron Вы когда-нибудь делали это с несколькими событиями? Могу я просто поместить несколько экземпляров этого поведения в xaml? - person Walter Williams; 26.11.2015
comment
@WalterWilliams Да, я это сделал, это работает без проблем. - person Mike Fuchs; 26.11.2015

Я всегда возвращался сюда за ответом, поэтому я хотел сделать короткий простой ответ.

Есть несколько способов сделать это:

1. Использование инструментов WPF. Самый простой.

Добавить пространства имен:

  • System.Windows.Interactivitiy
  • Microsoft.Expression.Interactions

XAML:

Используйте EventName для вызова нужного события, затем укажите свое Method имя в MethodName.

<Window>
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">

    <wi:Interaction.Triggers>
        <wi:EventTrigger EventName="SelectionChanged">
            <ei:CallMethodAction
                TargetObject="{Binding}"
                MethodName="ShowCustomer"/>
        </wi:EventTrigger>
    </wi:Interaction.Triggers>
</Window>

Код:

public void ShowCustomer()
{
    // Do something.
}

2. Использование MVVMLight. Самый сложный.

Установите пакет GalaSoft NuGet.

введите описание изображения здесь

Получите пространства имен:

  • System.Windows.Interactivity
  • GalaSoft.MvvmLight.Platform

XAML:

Используйте EventName для вызова нужного события, а затем укажите свое Command имя в привязке. Если вы хотите передать аргументы метода, отметьте PassEventArgsToCommand как истина.

<Window>
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:cmd="http://www.galasoft.ch/mvvmlight">

    <wi:Interaction.Triggers>
       <wi:EventTrigger EventName="Navigated">
           <cmd:EventToCommand Command="{Binding CommandNameHere}"
               PassEventArgsToCommand="True" />
       </wi:EventTrigger>
    </wi:Interaction.Triggers>
</Window>

Делегаты, реализующие код: Источник

Для этого вы должны получить пакет NuGet Prism MVVM.

введите описание изображения здесь

using Microsoft.Practices.Prism.Commands;

// With params.
public DelegateCommand<string> CommandOne { get; set; }
// Without params.
public DelegateCommand CommandTwo { get; set; }

public MainWindow()
{
    InitializeComponent();

    // Must initialize the DelegateCommands here.
    CommandOne = new DelegateCommand<string>(executeCommandOne);
    CommandTwo = new DelegateCommand(executeCommandTwo);
}

private void executeCommandOne(string param)
{
    // Do something here.
}

private void executeCommandTwo()
{
    // Do something here.
}

Код без DelegateCommand: Источник

using GalaSoft.MvvmLight.CommandWpf

public MainWindow()
{
    InitializeComponent();

    CommandOne = new RelayCommand<string>(executeCommandOne);
    CommandTwo = new RelayCommand(executeCommandTwo);
}

public RelayCommand<string> CommandOne { get; set; }

public RelayCommand CommandTwo { get; set; }

private void executeCommandOne(string param)
{
    // Do something here.
}

private void executeCommandTwo()
{
    // Do something here.
}

3. Использование Telerik EventToCommandBehavior. Это вариант.

Вам нужно будет загрузить его Пакет NuGet.

XAML:

<i:Interaction.Behaviors>
    <telerek:EventToCommandBehavior
         Command="{Binding DropCommand}"
         Event="Drop"
         PassArguments="True" />
</i:Interaction.Behaviors>

Код:

public ActionCommand<DragEventArgs> DropCommand { get; private set; }

this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);

private void OnDrop(DragEventArgs e)
{
    // Do Something
}
person AzzamAziz    schedule 09.02.2015
comment
Нет, совсем нет. Я хотел использовать MVVM Light, но мне это не нужно. Это прекрасно работает само по себе. - person AzzamAziz; 26.02.2015
comment
stackoverflow.com/questions/28448319/ - person AzzamAziz; 15.04.2015
comment
@DavidNichols, второй зависит от MVVM Light. - person AzzamAziz; 16.04.2015
comment
Использование здесь варианта 1 невероятно упрощает жизнь. Я не согласен с использованием MVVMLight [это самая сложная], но лучшая практика. Если это добавляет дополнительную сложность, а MS уже включила функциональность MVVM, которая поддерживает разделение проблем, зачем добавлять еще 2 пакета, если вам это не нужно? - person Conrad; 29.08.2016
comment
Согласованный. Лучшие практики касаются ПРИЗМЫ, которая здесь не применяется. Обновлю ответ, спасибо! - person AzzamAziz; 31.08.2016
comment
Черт побери! Огромное спасибо. Теперь я могу передать аргументы mouseeventargs моей команде Relay. - person Mr Tangjai; 20.03.2017
comment
Ты получил это! Рад, что могу помочь! :) @MrTangjai - person AzzamAziz; 21.03.2017
comment
Просто хочу отметить, что реализация Telerik немного изменилась с момента публикации этого поста. Теперь это выглядит так: ‹telerik: EventToCommandBehavior.EventBindings› ‹telerik: EventBinding Command = {Binding CellEditEnded} EventName = CellEditEnded PassEventArgsToCommand = True /› ‹/telerik:EventToCommandBehavior› - person Maxim Gershkovich; 15.09.2018
comment
Вариант 1 великолепен (я вижу его как Конрад), и ответ должен быть наиболее популярным. - person Julian; 13.12.2018
comment
Я не придерживаюсь варианта 1: есть ли ваш пример ShowCustomer в файле window.xaml.cs? Я не понимаю, как здесь используются какие-либо параметры, особенно при привязке к ICommand в модели просмотра. Вы можете это прояснить? - person Paul Gibson; 14.08.2019
comment
Вариант 1 не передает аргументы методу, это неполный вариант или он невозможен? - person luis_laurent; 02.11.2019

Для людей, которые только что нашли этот пост, вы должны знать, что в более новых версиях (не уверены в точной версии, поскольку официальные документы по этой теме невелики) поведение InvokeCommandAction по умолчанию, если не указан CommandParameter, заключается в передаче аргументов событие, к которому он прикреплен как CommandParameter. Итак, XAML оригинального плаката можно было бы просто записать как:

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Navigated">
    <i:InvokeCommandAction Command="{Binding NavigatedEvent}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>

Затем в своей команде вы можете принять параметр типа NavigationEventArgs (или любой другой подходящий тип аргументов события), и он будет предоставлен автоматически.

person joshb    schedule 24.08.2015
comment
Эй, похоже, так не работает. Хм, это было бы слишком просто. :) - person Pompair; 01.01.2016
comment
Я использовал эту технику для приложения Windows 10 UWP, не уверен, где все это работает таким образом. - person joshb; 02.01.2016
comment
Он работает для Prism InvokeCommandAction msdn.microsoft.com/en-us/library/ - person Anton Shakalo; 30.08.2016
comment
Для такого поведения вам определенно понадобится Prism. - person IgorMF; 09.07.2017
comment
И снова Prism потрясает. Плюс, опять же, я должен был обратиться к руководству, прежде чем просматривать веб-страницы. Спасибо! - person Informagic; 08.02.2019

Я знаю, что это довольно старый вопрос, но сегодня я столкнулся с той же проблемой и не слишком интересовался ссылками на все MVVMLight, чтобы я мог использовать триггеры событий с аргументами событий. Я использовал MVVMLight в прошлом, и это отличный фреймворк, но я просто не хочу больше использовать его в своих проектах.

Для решения этой проблемы я создал ULTRA минимальное, EXTREMELY настраиваемое действие настраиваемого триггера, которое позволило бы мне привязаться к команде и предоставить преобразователь аргументов событий для передачи аргументы функций CanExecute и Execute команды. Вы не хотите передавать аргументы события дословно, так как это приведет к отправке типов слоев представления на уровень модели представления (чего никогда не должно происходить в MVVM).

Вот созданный мной класс EventCommandExecuter:

public class EventCommandExecuter : TriggerAction<DependencyObject>
{
    #region Constructors

    public EventCommandExecuter()
        : this(CultureInfo.CurrentCulture)
    {
    }

    public EventCommandExecuter(CultureInfo culture)
    {
        Culture = culture;
    }

    #endregion

    #region Properties

    #region Command

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

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandExecuter), new PropertyMetadata(null));

    #endregion

    #region EventArgsConverterParameter

    public object EventArgsConverterParameter
    {
        get { return (object)GetValue(EventArgsConverterParameterProperty); }
        set { SetValue(EventArgsConverterParameterProperty, value); }
    }

    public static readonly DependencyProperty EventArgsConverterParameterProperty =
        DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(EventCommandExecuter), new PropertyMetadata(null));

    #endregion

    public IValueConverter EventArgsConverter { get; set; }

    public CultureInfo Culture { get; set; }

    #endregion

    protected override void Invoke(object parameter)
    {
        var cmd = Command;

        if (cmd != null)
        {
            var param = parameter;

            if (EventArgsConverter != null)
            {
                param = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.InvariantCulture);
            }

            if (cmd.CanExecute(param))
            {
                cmd.Execute(param);
            }
        }
    }
}

Этот класс имеет два свойства зависимостей: одно позволяет привязать к команде вашей модели представления, другое позволяет вам привязать источник события, если он вам нужен во время преобразования аргументов события. При необходимости вы также можете указать параметры культуры (по умолчанию они соответствуют текущей культуре пользовательского интерфейса).

Этот класс позволяет вам адаптировать аргументы событий, чтобы они могли использоваться логикой команд вашей модели представления. Однако, если вы хотите просто передать аргументы событий дословно, просто не указывайте преобразователь аргументов событий.

Самое простое использование этого триггерного действия в XAML выглядит следующим образом:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="NameChanged">
        <cmd:EventCommandExecuter Command="{Binding Path=Update, Mode=OneTime}" EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

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

<i:Interaction.Triggers>
    <i:EventTrigger EventName="NameChanged">
        <cmd:EventCommandExecuter 
            Command="{Binding Path=Update, Mode=OneTime}" 
            EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"
            EventArgsConverterParameter="{Binding ElementName=SomeEventSource, Mode=OneTime}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

(предполагается, что узел XAML, к которому вы подключаете триггеры, был назначен x:Name="SomeEventSource"

Этот XAML основан на импорте некоторых требуемых пространств имен.

xmlns:cmd="clr-namespace:MyProject.WPF.Commands"
xmlns:c="clr-namespace:MyProject.WPF.Converters"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

и создание IValueConverter (называемого в данном случае NameChangedArgsToStringConverter) для обработки фактической логики преобразования. Для базовых преобразователей я обычно создаю экземпляр преобразователя по умолчанию static readonly, на который затем могу ссылаться непосредственно в XAML, как я сделал выше.

Преимущество этого решения состоит в том, что вам действительно нужно добавить только один класс в любой проект, чтобы использовать структуру взаимодействия почти так же, как вы использовали бы ее с InvokeCommandAction. Добавление одного класса (около 75 строк) должно быть намного предпочтительнее целой библиотеки для достижения идентичных результатов.

ПРИМЕЧАНИЕ

это несколько похоже на ответ от @adabyron, но вместо поведения он использует триггеры событий. Это решение также обеспечивает возможность преобразования аргументов событий, но решение @adabyron не может этого сделать. У меня действительно нет веской причины, по которой я предпочитаю триггеры поведению, это просто личный выбор. ИМО, любая стратегия - разумный выбор.

person pjs    schedule 07.10.2014
comment
Идеальное решение для меня. Потрясающий. - person damccull; 02.12.2014

Чтобы добавить к тому, что уже сказал joshb - это отлично работает для меня. Обязательно добавьте ссылки на Microsoft.Expression.Interactions.dll и System.Windows.Interactivity.dll, а в вашем xaml выполните:

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

В итоге я использовал что-то подобное для своих нужд. Это показывает, что вы также можете передать специальный параметр:

<i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">

                <i:InvokeCommandAction Command="{Binding Path=DataContext.RowSelectedItem, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
                                       CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
            </i:EventTrigger>
</i:Interaction.Triggers>
person EbbnFlow    schedule 08.01.2016
comment
Это прекрасно работает, когда к нужному параметру можно получить доступ через привязку (OP требовал EventArgs) и не требует ничего, кроме пространства имен Interactivity. Явное указание привязки между CommandParameter и SelectedItem элемента было для меня ключевым, потому что я пытался просто ввести строку SelectedItem, что, конечно, не сработало. Ваше здоровье! - person Paul; 10.10.2017

Не думаю, что с InvokeCommandAction это можно легко сделать - я бы взглянул на _ 2_ из MVVMLight или аналогичного.

person Tim    schedule 01.06.2011

InvokeCommandAction Prism будет передавать аргументы событий по умолчанию, если CommandParameter не установлен.

https://docs.microsoft.com/en-us/previous-versions/msp-np/gg405494(v=pandp.40)#passing-eventargs-parameters-to-the-command

Вот пример. Обратите внимание на использование prism:InvokeCommandAction вместо i:InvokeCommandAction.

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Sorting">
        <prism:InvokeCommandAction Command="{Binding SortingCommand}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

ViewModel

    private DelegateCommand<EventArgs> _sortingCommand;

    public DelegateCommand<EventArgs> SortingCommand => _sortingCommand ?? (_sortingCommand = new DelegateCommand<EventArgs>(OnSortingCommand));

    private void OnSortingCommand(EventArgs obj)
    {
        //do stuff
    }

Вышла новая версия документации Prismlibrary.

person datchung    schedule 09.04.2019
comment
Это кажется самым простым решением. Возможно, так было не всегда. - person Tomas Kosar; 06.08.2019

С поведением и действиями в Blend для Visual Studio 2013 вы можете использовать InvokeCommandAction. Я попробовал это с помощью события Drop, и хотя CommandParameter не был указан в XAML, к моему удивлению, параметр Execute Action содержал DragEventArgs. Я предполагаю, что это произойдет с другими событиями, но я не проверял их.

person OzFrog    schedule 25.03.2014
comment
Можете ли вы предоставить пример кода (XAML и VM)? Как описано, он не работает для меня (WPF, .NET 4.5) - person Pete Stensønes; 07.10.2014

Что я делаю, так это использую InvokeCommandAction для привязки события загруженного элемента управления к команде в модели представления, присваиваю элементу управления ax: Name в Xaml и передаю его как CommandParameter, а затем в указанных обработчиках модели представления обработчиков обработчиков обработчиков обработчиков до тех событий, где мне нужно чтобы получить аргументы события.

person DRL    schedule 17.04.2013

Вот версия ответа @adabyron, которая предотвращает утечку EventArgs абстракции.

Во-первых, модифицированный класс EventToCommandBehavior (теперь это общий абстрактный класс, отформатированный с помощью очистки кода ReSharper). Обратите внимание на новый виртуальный метод GetCommandParameter и его реализацию по умолчанию:

public abstract class EventToCommandBehavior<TEventArgs> : Behavior<FrameworkElement>
    where TEventArgs : EventArgs
{
    public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(null, OnEventChanged));
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(null));
    public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(false));
    private Delegate _handler;
    private EventInfo _oldEvent;

    public string Event
    {
        get { return (string)GetValue(EventProperty); }
        set { SetValue(EventProperty, value); }
    }

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

    public bool PassArguments
    {
        get { return (bool)GetValue(PassArgumentsProperty); }
        set { SetValue(PassArgumentsProperty, value); }
    }

    protected override void OnAttached()
    {
        AttachHandler(Event);
    }

    protected virtual object GetCommandParameter(TEventArgs e)
    {
        return e;
    }

    private void AttachHandler(string eventName)
    {
        _oldEvent?.RemoveEventHandler(AssociatedObject, _handler);

        if (string.IsNullOrEmpty(eventName))
        {
            return;
        }

        EventInfo eventInfo = AssociatedObject.GetType().GetEvent(eventName);

        if (eventInfo != null)
        {
            MethodInfo methodInfo = typeof(EventToCommandBehavior<TEventArgs>).GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);

            _handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, methodInfo);
            eventInfo.AddEventHandler(AssociatedObject, _handler);
            _oldEvent = eventInfo;
        }
        else
        {
            throw new ArgumentException($"The event '{eventName}' was not found on type '{AssociatedObject.GetType().FullName}'.");
        }
    }

    private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = (EventToCommandBehavior<TEventArgs>)d;

        if (behavior.AssociatedObject != null)
        {
            behavior.AttachHandler((string)e.NewValue);
        }
    }

    // ReSharper disable once UnusedMember.Local
    // ReSharper disable once UnusedParameter.Local
    private void ExecuteCommand(object sender, TEventArgs e)
    {
        object parameter = PassArguments ? GetCommandParameter(e) : null;

        if (Command?.CanExecute(parameter) == true)
        {
            Command.Execute(parameter);
        }
    }
}

Затем пример производного класса, который скрывает DragCompletedEventArgs. Некоторые люди выразили обеспокоенность по поводу утечки EventArgs абстракции в их сборку модели представления. Чтобы предотвратить это, я создал интерфейс, который представляет ценности, которые нам небезразличны. Интерфейс может существовать в сборке модели представления с частной реализацией в сборке пользовательского интерфейса:

// UI assembly
public class DragCompletedBehavior : EventToCommandBehavior<DragCompletedEventArgs>
{
    protected override object GetCommandParameter(DragCompletedEventArgs e)
    {
        return new DragCompletedArgs(e);
    }

    private class DragCompletedArgs : IDragCompletedArgs
    {
        public DragCompletedArgs(DragCompletedEventArgs e)
        {
            Canceled = e.Canceled;
            HorizontalChange = e.HorizontalChange;
            VerticalChange = e.VerticalChange;
        }

        public bool Canceled { get; }
        public double HorizontalChange { get; }
        public double VerticalChange { get; }
    }
}

// View model assembly
public interface IDragCompletedArgs
{
    bool Canceled { get; }
    double HorizontalChange { get; }
    double VerticalChange { get; }
}

Приведите параметр команды к IDragCompletedArgs, аналогично ответу @adabyron.

person NathanAldenSr    schedule 21.06.2016

В качестве адаптации ответа @Mike Fuchs вот еще меньшее решение. Я использую Fody.AutoDependencyPropertyMarker, чтобы уменьшить часть котла.

Класс

public class EventCommand : TriggerAction<DependencyObject>
{
    [AutoDependencyProperty]
    public ICommand Command { get; set; }

    protected override void Invoke(object parameter)
    {
        if (Command != null)
        {
            if (Command.CanExecute(parameter))
            {
                Command.Execute(parameter);
            }
        }
    }
}

EventArgs

public class VisibleBoundsArgs : EventArgs
{
    public Rect VisibleVounds { get; }

    public VisibleBoundsArgs(Rect visibleBounds)
    {
        VisibleVounds = visibleBounds;
    }
}

XAML

<local:ZoomableImage>
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="VisibleBoundsChanged" >
         <local:EventCommand Command="{Binding VisibleBoundsChanged}" />
      </i:EventTrigger>
   </i:Interaction.Triggers>
</local:ZoomableImage>

ViewModel

public ICommand VisibleBoundsChanged => _visibleBoundsChanged ??
                                        (_visibleBoundsChanged = new RelayCommand(obj => SetVisibleBounds(((VisibleBoundsArgs)obj).VisibleVounds)));
person Ralt    schedule 22.03.2018

Я знаю, что это немного поздно, но Microsoft сделала свой Xaml.Behaviors открытым исходным кодом, и теперь намного проще использовать интерактивность с одним пространством имен.

  1. Сначала добавьте в проект пакет Nuget Microsoft.Xaml.Behaviors.Wpf.
    https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Wpf/
  2. добавьте пространство имен xmlns: behavior = http: //schemas.microsoft.com/xaml/behaviors в свой xaml.

Тогда используйте это так,

<Button Width="150" Style="{DynamicResource MaterialDesignRaisedDarkButton}">
   <behaviours:Interaction.Triggers>
       <behaviours:EventTrigger EventName="Click">
           <behaviours:InvokeCommandAction Command="{Binding OpenCommand}" PassEventArgsToCommand="True"/>
       </behaviours:EventTrigger>
    </behaviours:Interaction.Triggers>
    Open
</Button>

PassEventArgsToCommand = True должен быть установлен как True, а RelayCommand, которую вы реализуете, может принимать RoutedEventArgs или объекты в качестве шаблона. Если вы используете объект в качестве типа параметра, просто приведите его к соответствующему типу события.

Команда будет выглядеть примерно так:

OpenCommand = new RelayCommand<object>(OnOpenClicked, (o) => { return true; });

Метод команды будет выглядеть примерно так:

private void OnOpenClicked(object parameter)
{
    Logger.Info(parameter?.GetType().Name);
}

«Параметром» будет объект события Routed.

И журнал, если вам любопытно,

2020-12-15 11: 40: 36.3600 | ИНФОРМАЦИЯ | MyApplication.ViewModels.MainWindowViewModel | RoutedEventArgs

Как вы можете видеть, зарегистрированное TypeName - это RoutedEventArgs.

Выполнение RelayCommand можно найти здесь.

Почему RelayCommand

PS: Вы можете привязать к любому событию любого элемента управления. Как событие закрытия окна, вы получите соответствующие события.

person Snippy Valson    schedule 15.12.2020