MVVM Передача EventArgs в качестве параметра команды - PullRequest
57 голосов
/ 01 июня 2011

Я использую 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 Class

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 class Здесь

Наконец:
Связывание с командами работает хорошо, и MessageBoxes показаны


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

Ответы [ 12 ]

60 голосов
/ 01 июня 2011

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

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

 <i:Interaction.Triggers>
    <i:EventTrigger EventName="Navigated">
        <cmd:EventToCommand Command="{Binding NavigatedEvent}"
            PassEventArgsToCommand="True" />
    </i:EventTrigger>
 </i:Interaction.Triggers>
39 голосов
/ 01 мая 2013

Я стараюсь свести свои зависимости к минимуму, поэтому я сам реализовал это вместо того, чтобы использовать 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);
        }
    }
}
19 голосов
/ 09 февраля 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.

enter image description here

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

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

XAML:

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

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

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

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

enter image description here

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
}
12 голосов
/ 08 октября 2014

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

Что я сделал, чтобы решить эту проблему, так это создать ULTRA минимальное, ЧРЕЗВЫЧАЙНОЕ настраиваемое настраиваемое действие триггера, которое позволило бы мне привязаться к команде и предоставить конвертер аргументов событий для передачи аргументов в функции 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) для обработки фактической логики преобразования. Для базовых преобразователей Iобычно создают экземпляр конвертера static readonly по умолчанию, который я затем могу ссылаться непосредственно в XAML, как я это делал выше.

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

NOTE

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

11 голосов
/ 24 августа 2015

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

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

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

5 голосов
/ 08 января 2016

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

    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>
3 голосов
/ 01 июня 2011

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

1 голос
/ 25 марта 2014

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

0 голосов
/ 09 апреля 2019

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

https://docs.microsoft.com/en-us/previous-versions/msp-n-p/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>
0 голосов
/ 22 марта 2018

Как вариант ответа @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)));
...