MVVM Passando EventArgs Como Parâmetro de Comando

Estou usando o Microsoft Expression Blend 4
Eu tenho um navegador ..

[XAML] ConnectionView “Empty Code Behind”

          

[C #] Classe 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 #] Classe ConnectionViewModel

 public class ConnectionViewModel : ViewModelBase { public string Source { get { return Get ( "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 #] Classe ViewModelBase Aqui

Finalmente :
Ligação com comandos funciona bem e MessageBoxes mostrado


Minha pergunta :
Como passar NavigationEventArgs como parâmetros de comando quando ocorre evento navegado?

Não é facilmente suportado. Aqui está um artigo com instruções sobre como passar EventArgs como parâmetros de comando.

Você pode querer olhar para o MVVMLight – ele suporta EventArgs diretamente no comando; sua situação seria algo como isto:

       

Eu tento manter minhas dependencies no mínimo, então eu mesmo implementei isso em vez de ir com o EventToCommand do MVVMLight. Funciona para mim até agora, mas o feedback é bem-vindo.

Xaml:

    

ViewModel:

 public ActionCommand DropCommand { get; private set; } this.DropCommand = new ActionCommand(OnDrop); private void OnDrop(DragEventArgs e) { // ... } 

EventToCommandBehavior:

 ///  /// Behavior that will connect an UI event to a viewmodel Command, /// allowing the event arguments to be passed as the CommandParameter. ///  public class EventToCommandBehavior : Behavior { 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 } ///  /// Attaches the handler to the event ///  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)); } } ///  /// Executes the Command ///  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 : ICommand { public event EventHandler CanExecuteChanged; private Action _action; public ActionCommand(Action 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); } } } 

Eu sempre volto aqui para a resposta, então eu queria fazer uma simples e curta para ir.

Existem várias maneiras de fazer isso:

1. Usando as ferramentas do WPF. Mais fácil.

Adicionar Namespaces:

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

XAML:

Use o EventName para chamar o evento desejado e, em seguida, especifique o nome do Method no MethodName .

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

Código:

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

2. Usando o MVVMLight. Mais difícil.

Instale o pacote GalaSoft NuGet.

insira a descrição da imagem aqui

Obtenha os namespaces:

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

XAML:

Use o EventName para chamar o evento desejado e, em seguida, especifique o nome do Command na sua binding. Se você quiser passar os argumentos do método, marque PassEventArgsToCommand como true.

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

Delegados de Implementação do Código: Origem

Você deve obter o pacote Prism MVVM NuGet para isso.

insira a descrição da imagem aqui

 using Microsoft.Practices.Prism.Commands; // With params. public DelegateCommand CommandOne { get; set; } // Without params. public DelegateCommand CommandTwo { get; set; } public MainWindow() { InitializeComponent(); // Must initialize the DelegateCommands here. CommandOne = new DelegateCommand(executeCommandOne); CommandTwo = new DelegateCommand(executeCommandTwo); } private void executeCommandOne(string param) { // Do something here. } private void executeCommandTwo() { // Do something here. } 

Código sem DelegateCommand : Source

 using GalaSoft.MvvmLight.CommandWpf public MainWindow() { InitializeComponent(); CommandOne = new RelayCommand(executeCommandOne); CommandTwo = new RelayCommand(executeCommandTwo); } public RelayCommand CommandOne { get; set; } public RelayCommand CommandTwo { get; set; } private void executeCommandOne(string param) { // Do something here. } private void executeCommandTwo() { // Do something here. } 

3. Usando o Telerik EventToCommandBehavior . É uma opção.

Você terá que baixar o pacote NuGet .

XAML :

    

Código:

 public ActionCommand DropCommand { get; private set; } this.DropCommand = new ActionCommand(OnDrop); private void OnDrop(DragEventArgs e) { // Do Something } 

Eu sei que esta é uma questão bastante antiga, mas eu corri para o mesmo problema hoje e não estava muito interessado em referenciar todo o MVVMLight apenas para que eu possa usar gatilhos de events com args de evento. Eu usei MVVMLight no passado e é um ótimo framework, mas eu não quero mais usá-lo para meus projetos.

O que eu fiz para resolver esse problema foi criar uma ação de gatilho personalizado ULTRA mínima, EXTREMAMENTE adaptável que me permitiria vincular ao comando e fornecer um conversor args de evento para passar os argumentos para as funções CanExecute e Execute do comando. Você não deseja passar os argumentos de evento textualmente, pois isso resultaria em tipos de camada de visualização sendo enviados para a camada de modelo de visualização (o que nunca deveria acontecer no MVVM).

Aqui está a class EventCommandExecuter que eu criei :

 public class EventCommandExecuter : TriggerAction { #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); } } } } 

Essa class tem duas propriedades de dependência, uma para permitir a binding ao comando do modelo de exibição, a outra permite vincular a origem do evento, se necessário, durante a conversão de argumentos do evento. Você também pode fornecer configurações de cultura se precisar (elas são padronizadas para a cultura atual da interface do usuário).

Essa class permite adaptar os argumentos do evento para que eles possam ser consumidos pela lógica de comando do modelo de exibição. No entanto, se você quiser apenas passar os argumentos do evento on-line, simplesmente não especifique um conversor args de evento.

O uso mais simples dessa ação de acionador no XAML é o seguinte:

      

Se você precisasse de access à fonte do evento, você se ligaria ao dono do evento

      

(isso pressupõe que o nó XAML ao qual você está anexando os gatilhos foi designado x:Name="SomeEventSource"

Esse XAML depende da importação de alguns namespaces necessários

 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" 

e criar um IValueConverter (chamado NameChangedArgsToStringConverter neste caso) para manipular a lógica de conversão real. Para conversores básicos, eu geralmente crio uma instância do conversor static readonly padrão, que eu posso referenciar diretamente no XAML, como fiz acima.

O benefício desta solução é que você realmente só precisa adicionar uma única class a qualquer projeto para usar a estrutura de interação da mesma maneira que você usaria com InvokeCommandAction . Adicionar uma única class (de aproximadamente 75 linhas) deve ser muito mais preferível a uma biblioteca inteira para obter resultados idênticos.

NOTA

isso é um pouco semelhante à resposta do @adabyron, mas usa gatilhos de events em vez de comportamentos. Essa solução também fornece uma capacidade de conversão de argumentos de evento, não que a solução da @adabyron também não pudesse fazer isso. Eu realmente não tenho nenhum bom motivo para eu preferir gatilhos para comportamentos, apenas uma escolha pessoal. OMI ou estratégia é uma escolha razoável.

Para as pessoas que estão encontrando este post, você deve saber que nas versões mais novas (não na versão exata desde que os documentos oficiais são pequenos neste tópico) o comportamento padrão do InvokeCommandAction, se nenhum parâmetro de comando for especificado, é passar os argumentos do evento é anexado como o CommandParameter. Então, o XAML do pôster original pode ser simplesmente escrito como:

      

Em seguida, em seu comando, você pode aceitar um parâmetro do tipo NavigationEventArgs (ou qualquer tipo de argumento de evento apropriado) e ele será fornecido automaticamente.

Para adicionar ao que joshb já declarou – isso funciona muito bem para mim. Certifique-se de adicionar referências a Microsoft.Expression.Interactions.dll e System.Windows.Interactivity.dll e em seu xaml faça:

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

Acabei usando algo assim para minhas necessidades. Isso mostra que você também pode passar um parâmetro personalizado:

      

Eu não acho que você pode fazer isso facilmente com o InvokeCommandAction – eu daria uma olhada no EventToCommand do MVVMLight ou similar.

Com Comportamentos e Ações no Blend for Visual Studio 2013, você pode usar o InvokeCommandAction. Eu tentei isso com o evento Drop e, embora nenhum CommandParameter foi especificado no XAML, para minha surpresa, o parâmetro Execute Action continha o DragEventArgs. Eu presumo que isso aconteceria para outros events, mas não os testei.

O que eu faço é usar InvokeCommandAction para vincular o evento de controle carregado a um comando no modelo de exibição, fornecer o controle ax: Name em Xaml e passar como CommandParameter, depois nos manipuladores de modelo de visualização de gancho de comando carregados até os events em que preciso para obter os argumentos do evento.

Aqui está uma versão da resposta do @ adabyron que impede a abstração de EventArgs vazamento.

Primeiro, a class EventToCommandBehavior modificada (agora uma class abstrata genérica e formatada com limpeza de código ReSharper). Observe o novo método virtual GetCommandParameter e sua implementação padrão:

 public abstract class EventToCommandBehavior : Behavior where TEventArgs : EventArgs { public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged)); public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null)); public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), 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).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)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); } } } 

Em seguida, um exemplo de class derivada que oculta DragCompletedEventArgs . Algumas pessoas expressaram preocupação sobre o vazamento da abstração EventArgs em seu assembly de modelo de exibição. Para evitar isso, criei uma interface que representa os valores que nos interessam. A interface pode viver no assembly do modelo de exibição com a implementação privada no assembly da interface do usuário:

 // UI assembly public class DragCompletedBehavior : EventToCommandBehavior { 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; } } 

Transmita o parâmetro de comando para IDragCompletedArgs , semelhante à resposta de @ adabyron.

Como uma adaptação da resposta do @Mike Fuchs, aqui está uma solução ainda menor. Estou usando o Fody.AutoDependencyPropertyMarker para reduzir parte da placa da caldeira.

A class

 public class EventCommand : TriggerAction { [AutoDependencyProperty] public ICommand Command { get; set; } protected override void Invoke(object parameter) { if (Command != null) { if (Command.CanExecute(parameter)) { Command.Execute(parameter); } } } } 

O EventArgs

 public class VisibleBoundsArgs : EventArgs { public Rect VisibleVounds { get; } public VisibleBoundsArgs(Rect visibleBounds) { VisibleVounds = visibleBounds; } } 

O XAML

        

The ViewModel

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