MVVM que pasa EventArgs como parámetro de comando

Estoy usando Microsoft Expression Blend 4
Tengo un navegador …,

[XAML] ConnectionView “Código vacío detrás”

          

[C #] Clase 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 #] Clase 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 #] Clase ViewModelBase Aquí

Finalmente :
La vinculación con comandos funciona bien y se muestran los cuadros de mensaje


Mi pregunta :
¿Cómo pasar a NavigationEventArgs como parámetros de comando cuando ocurre un evento navegado?

No es fácilmente compatible. Aquí hay un artículo con instrucciones sobre cómo pasar EventArgs como parámetros de comando.

Es posible que desee considerar el uso de MVVMLight : admite EventArgs en el comando directamente; su situación se vería así:

       

Intento mantener mis dependencias al mínimo, así que lo implementé yo mismo en lugar de ir con EventToCommand de MVVMLight. Funciona para mí hasta ahora, pero los comentarios son bienvenidos.

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); } } } 

Siempre he regresado aquí por la respuesta, así que quería hacer una breve y simple para ir.

Hay varias formas de hacer esto:

1. Usando las herramientas de WPF. Más fácil

Agregar espacios de nombres:

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

XAML:

Use el EventName del evento para llamar al evento que desea y luego especifique el nombre del Method en 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 MVVMLight. Más difícil.

Instale el paquete GalaSoft NuGet.

enter image description here

Obtenga los espacios de nombres:

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

XAML:

Use el EventName del evento para llamar al evento que desea y luego especifique su nombre de Command en su enlace. Si desea pasar los argumentos del método, marque PassEventArgsToCommand en verdadero.

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

Delegados implementadores del código: fuente

Debe obtener el paquete Prism MVVM NuGet para esto.

enter image description here

 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 sin DelegateCommand : Fuente

 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 Telerik EventToCommandBehavior . Es una opción.

Tendrás que descargar su paquete NuGet .

XAML :

    

Código:

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

Sé que esta es una pregunta bastante antigua, pero me encontré con el mismo problema hoy y no estaba muy interesado en hacer referencia a todo MVVMLight solo para poder usar desencadenantes de eventos con eventos args. He utilizado MVVMLight en el pasado y es un gran marco, pero ya no quiero usarlo para mis proyectos.

Lo que hice para resolver este problema fue crear una acción de desencadenador personalizada MUY mínima, extremadamente adaptable, que me permitiera vincularme al comando y proporcionar un convertidor de argumentos de eventos para transmitir los argumentos a las funciones CanExecute y Execute del comando. No desea pasar los argumentos del evento palabra por palabra, ya que esto daría como resultado la vista de los tipos de capas que se envían a la capa del modelo de visualización (lo que nunca debería ocurrir en MVVM).

Aquí está la clase EventCommandExecuter que surgió:

 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); } } } } 

Esta clase tiene dos propiedades de dependencia, una para permitir el enlace al comando de su modelo de visualización, la otra le permite vincular el origen del evento si lo necesita durante la conversión de argumentos de evento. También puede proporcionar configuraciones de cultivo si lo necesita (su valor predeterminado es la cultura de UI actual).

Esta clase le permite adaptar los eventos args para que puedan ser consumidos por la lógica de comandos de su modelo de vista. Sin embargo, si desea pasar los argumentos del evento al pie de la letra, simplemente no especifique un event args convertidor.

El uso más simple de esta acción desencadenante en XAML es el siguiente:

      

Si necesitabas acceder al origen del evento, te vincularías con el propietario del evento

      

(Esto supone que el nodo XAML al que está conectando los desencadenadores tiene asignado x:Name="SomeEventSource"

Este XAML se basa en la importación de algunos espacios de nombres requeridos

 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" 

y crear un IValueConverter (llamado NameChangedArgsToStringConverter en este caso) para manejar la lógica de conversión real. Para los convertidores básicos, normalmente creo una instancia predeterminada de convertidor de static readonly , que luego puedo hacer referencia directamente en XAML como lo hice anteriormente.

El beneficio de esta solución es que realmente solo necesita agregar una sola clase a cualquier proyecto para usar el marco de interacción de la misma manera que lo usaría con InvokeCommandAction . Agregar una sola clase (de aproximadamente 75 líneas) debería ser mucho más preferible a una biblioteca completa para lograr resultados idénticos.

NOTA

esto es algo similar a la respuesta de @adabyron, pero usa desencadenantes de eventos en lugar de comportamientos. Esta solución también proporciona una capacidad de conversión de argumentos de evento, no es que la solución de @ adabyron tampoco pueda hacer esto. Realmente no tengo ninguna buena razón por la que prefiero factores desencadenantes de comportamientos, solo una elección personal. OMI cualquiera de las estrategias es una elección razonable.

Para las personas que acaban de encontrar esta publicación, deben saber que en versiones más recientes (no estoy seguro de la versión exacta ya que los documentos oficiales son delgados sobre este tema), el comportamiento predeterminado de InvokeCommandAction, si no se especifica ningún parámetro de comando, es pasar los argumentos del evento al que está conectado como el parámetro de comando. Entonces, el XAML del póster original podría simplemente escribirse como:

      

Luego, en su comando, puede aceptar un parámetro de tipo NavigationEventArgs (o el tipo de evento args apropiado) y se proporcionará automáticamente.

Para agregar a lo que joshb ya ha dicho, esto funciona bien para mí. Asegúrese de agregar referencias a Microsoft.Expression.Interactions.dll y System.Windows.Interactivity.dll y en su xaml do:

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

Terminé usando algo como esto para mis necesidades. Esto muestra que también puede pasar un parámetro personalizado:

      

No creo que pueda hacer eso fácilmente con InvokeCommandAction . InvokeCommandAction un vistazo a EventToCommand desde MVVMLight o similar.

Con Behaviors and Actions in Blend para Visual Studio 2013 puede usar InvokeCommandAction. Intenté esto con el evento Drop y, aunque no se especificó CommandParameter en el XAML, para mi sorpresa, el parámetro Execute Action contenía DragEventArgs. Supongo que esto sucederá para otros eventos pero no los he probado.

Lo que hago es usar InvokeCommandAction para vincular el evento cargado de control a un comando en el modelo de vista, dar el control ax: Name en Xaml y pasar como CommandParameter, luego en dichos gestores de modelos de vista hook cargados hasta los eventos donde necesito para obtener el evento args.

Aquí hay una versión de la respuesta de @ adabyron que evita la abstracción de EventArgs filtraciones.

Primero, la clase EventToCommandBehavior modificada (ahora una clase abstracta genérica y formateada con la limpieza del código de ReSharper). Tenga en cuenta el nuevo método virtual GetCommandParameter y su implementación predeterminada:

 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); } } } 

A continuación, una clase derivada de ejemplo que oculta DragCompletedEventArgs . Algunas personas expresson su preocupación por la filtración de la abstracción EventArgs en su ensamblaje de modelo de vista. Para evitar esto, creé una interfaz que representa los valores que nos importan. La interfaz puede vivir en el ensamblaje del modelo de visualización con la implementación privada en el ensamblado de UI:

 // 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 el parámetro de comando a IDragCompletedArgs , similar a la respuesta de @ adabyron.

Como una adaptación de la respuesta de @Mike Fuchs, aquí hay una solución aún más pequeña. Estoy usando Fody.AutoDependencyPropertyMarker para reducir parte de la placa de la caldera.

La clase

 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); } } } } 

The EventArgs

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

El XAML

        

The ViewModel

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