- ¿Qué es un comando? ======================
Para explicar este concepto, utilizaremos un escenario hipotético. Supongamos que en un momento dado, el jefe de una empresa le dice a un empleado: "Juan, ve a la recepción a recoger mi paquete". Lo que el jefe le dice a Juan puede entenderse como un comando, es decir, simplemente es una pieza de información. ¿Por qué involucrar al "jefe" y a "Juan"? Porque son partes indispensables relacionadas con el comando: son las personas que generan el comando (origen del comando) y lo ejecutan (objetivo del comando). De manera similar, el sistema de comandos en WPF también contiene estos elementos. El modelo de comandos de WPF se puede dividir en cuatro conceptos principales: ICommand, ICommandSource, el objetivo del comando y CommandBinding.
ICommand
El comando representa la operación que se va a ejecutar.
Los comandos en WPF se crean implementando la interfaz ICommand. ICommand expone dos métodos: Execute y CanExecute, además de un evento llamado CanExecuteChanged. Execute ejecuta la operación asociada con el comando. CanExecute determina si el comando puede ejecutarse en el objetivo actual. Si el administrador de comandos que gestiona centralmente las operaciones detecta un cambio en el origen del comando que podría invalidar un comando ya generado pero que aún no ha sido ejecutado por el enlace del comando, se activa el evento CanExecuteChanged.
Las implementaciones predeterminadas de ICommand en WPF son RoutedCommand y RoutedUICommand.
ICommandSource
El origen del comando es el objeto que genera o invoca el comando. Los orígenes de comando en WPF generalmente implementan la interfaz ICommandSource; los más comunes son Button y MenuItem. Su definición es la siguiente:
public interface ICommandSource
{
ICommand Comando { get; }
object ParametroComando { get; }
IInputElement ObjetivoComando { get; }
}
Donde:
- Comando: es el comando que se ejecutará.
- ParametroComando: es un objeto que se utiliza para pasar información al ejecutor del comando.
- ObjetivoComando: es el objeto sobre el cual se ejecutará el comando y debe implementar la interfaz IInputElement.
Objetivo del Comando
Es el objeto sobre el cual se ejecuta el comando. El objetivo del comando no tiene una restricción tan evidente como el origen del comando (ICommandSource), pero debe implementar la interfaz IInputElement.
CommandBinding
Es el objeto que mapea la lógica del comando.
CommandBinding expone cuatro eventos: VistaPreviaEjecutado, Ejecutado, VistaPreviaPuedeEjecutar y PuedeEjecutar, además de dos métodos: EnPodeEjecutar y EnEjecutado. El método EnPodeEjecutar activa los eventos VistaPreviaPuedeEjecutar y PuedeEjecutar, mientras que el método EnEjecutado activa los eventos VistaPreviaEjecutado y Ejecutado.
CommandBinding fue diseñado específicamente para RoutedCommand en WPF.
- Propósito de los Comandos ============================
En términos simples, los comandos tienen dos propósitos:
1. Separar el emisor del ejecutor del comando: esto permite que el emisor envíe el mismo comando a diferentes ejecutores.
2. Indicar si la operación enviada puede ejecutarse. Volviendo al escenario anterior, cuando el jefe emite el comando, Juan podría decidir que no tiene tiempo o no quiere ir, y decirle al jefe: "¡Cállate!" (quizás Juan tiene medios económicos). En ese caso, el comando no se ejecutará. En WPF, si ButtonA está asociado al comando A, cuando el comando A no puede ejecutarse (determinado por el sistema de comandos), ButtonA se mostrará deshabilitado, es decir, el origen del comando no puede emitir el comando.
- Cómo Utilizar los Comandos =============================
Tomemos RoutedCommand como ejemplo.
Implementación en XAML
<Window.Resources>
<RoutedCommand x:Key="comandoEjemplo"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource comandoEjemplo}"
CanExecute="AlComandoPuedeEjecutar"
Executed="AlComandoEjecutado"/>
</Window.CommandBindings>
<Grid>
<Button x:Name="botonEjemplo"
Content="botón de ejemplo"
Command="{StaticResource comandoEjemplo}"/>
</Grid>
Implementación en C#
RoutedCommand comandoEjemplo = new RoutedCommand();
CommandBinding enlaceComando = new CommandBinding(comandoEjemplo, AlComandoEjecutado, AlComandoPuedeEjecutar);
CommandBindings.Add(enlaceComando);
botonEjemplo.Command = comandoEjemplo;
private void AlComandoEjecutado(object remitente, ExecutedRoutedEventArgs argumentos)
{
MessageBox.Show("Botón de ejemplo seleccionado.");
}
private void AlComandoPuedeEjecutar(object remitente, CanExecuteRoutedEventArgs argumentos)
{
argumentos.CanExecute = true;
}
- Cómo se Ejecutan los Comandos ================================
A continuación, utilizaremos el control Button para explicar cómo se ejecuta un comando. Sabemos que cuando se presiona un Button, se invoca el método Click, cuya implementación es la siguiente:
protected virtual void OnClick()
{
RoutedEventArgs argumentos = new RoutedEventArgs(ClickEvent, this);
RaiseEvent(argumentos);
CommandHelpers.ExecuteCommandSource(this);
}
El método Click primero activa el evento enrutado ClickedEvent y luego accede a la lógica de invocación del comando a través de la clase de utilidad CommandHelpers:
internal static void ExecuteCommandSource(ICommandSource origenComando)
{
object parametroComando = origenComando.CommandParameter;
IInputElement elementoEntrada = origenComando.CommandTarget;
RoutedCommand comandoEnrutado = comando as RoutedCommand;
if (comandoEnrutado != null)
{
if (elementoEntrada == null)
{
elementoEntrada = (origenComando as IInputElement);
}
if (comandoEnrutado.CanExecute(parametroComando, elementoEntrada))
{
comandoEnrutado.ExecuteCore(parametroComando, elementoEntrada, userInitiated);
}
}
else if (command.CanExecute(parametroComando))
{
command.Execute(parametroComando);
}
}
En el método ExecuteCommandSource, primero se verifica si el comando emitido por el origen es un RoutedCommand. Si no lo es, se invoca directamente el método Execute de la interfaz ICommand, pasando el CommandParameter del origen como parámetro. Si es un RoutedCommand, el siguiente paso es especificar el objetivo del comando:
- Paso 1: Si el objetivo del comando en el origen es válido, ese objetivo se utiliza como destino final.
- Paso 2: Si el objetivo del comando en el origen no es válido, se utiliza el origen como objetivo.
- Paso 3: Si el origen no es un objetivo válido, se utiliza el objeto que tiene el foco actual como objetivo.
Una vez determinado el objetivo, los eventos CommandManager.VistaPreviaEjecutadoEvent y CommandManager.EjecutadoEvent se activan secuencialmente en el objetivo. El código específico es el siguiente:
internal bool ExecuteCore(object parametro, IInputElement objetivo, bool iniciadoPorUsuario)
{
if (objetivo == null)
{
objetivo = FilterInputElement(Keyboard.FocusedElement);
}
return ExecuteImpl(parametro, objetivo, iniciadoPorUsuario);
}
private bool ExecuteImpl(object parametro, IInputElement objetivo, bool iniciadoPorUsuario)
{
if (objetivo != null && !IsBlockedByRM)
{
UIElement elementoUI = objetivo as UIElement;
ContentElement elementoContenido = null;
UIElement3D elementoUI3D = null;
ExecutedRoutedEventArgs argumentosEjecutados = new ExecutedRoutedEventArgs(this, parametro);
argumentosEjecutados.RoutedEvent = CommandManager.VistaPreviaEjecutadoEvent;
if (elementoUI != null)
{
elementoUI.RaiseEvent(argumentosEjecutados, iniciadoPorUsuario);
}
else
{
// Manejo para otros tipos de elementos
}
if (!argumentosEjecutados.Handled)
{
argumentosEjecutados.RoutedEvent = CommandManager.EjecutadoEvent;
if (elementoUI != null)
{
elementoUI.RaiseEvent(argumentosEjecutados, iniciadoPorUsuario);
}
// Manejo para otros tipos de elementos
}
return argumentosEjecutados.Handled;
}
return false;
}
Desde que comienza Button.OnClick nos encontramos con RaiseEvent. ¿Qué hace exactamente este método? En pocas palabras, la responsabilidad de RaiseEvent es construir la ruta de enrutamiento del evento especificado y ejecutar ese evento a lo largo de la ruta. El método de construcción consiste en buscar en el árbol de elementos si un elemento (IInputElement) necesita manejar el evento enrutado específico (aquí son CommandManager.VistaPreviaEjecutadoEvent o CommandManager.EjecutadoEvent). Si un elemento necesita manejar el evento enrutado, ese elemento se agregará a la ruta de enrutamiento.
¿Cómo se logra asociar un objeto con un evento enrutado? Se puede hacer de dos maneras:
Método 1: Invocar el método AddHandler(RoutedEvent eventoEnrutado, Delegate manejador) en la interfaz IInputElement.
Método 2: Utilizando los métodos de registro de eventos enrutados proporcionados por la clase de utilidad EventManager.
EventManager proporciona métodos para mapear controladores de eventos enrutados a tipos, y los controladores de eventos enrutados registrados a través de EventManager se invocan antes que los registrados a través del método IInputElement.AddHandler. Esto está determinado por las clases que implementan IInputElement. Por ejemplo, UIElement, al construir la ruta de enrutamiento de un evento, primero busca coincidencias con los controladores de eventos enrutados mapeados a través de la clase de utilidad EventManager, y luego busca los mapeados a través de la instancia IInputElement.
Una vez construida la ruta de enrutamiento del evento, el método RaiseEvent ejecuta los controladores de eventos a lo largo de la ruta. Hasta aquí, la activación y ejecución de un evento están completas.
¡El comando se ejecutó correctamente! ¿Hay dudas?
Duda 1: Según el método de uso del comando descrito anteriormente, el comando se asocia a su manejador a través de CommandBinding. Sin embargo, si revisamos la implementación de CommandBinding, no registra los controladores de eventos enrutados de ninguna de las dos formas mencionadas. Además, el código anterior no registra explícitamente los eventos CommandManager.VistaPreviaEjecutadoEvent o CommandManager.EjecutadoEvent. ¿Cómo se ejecuta entonces el comando?
Duda 2: Aunque algún objeto haya registrado implícitamente estos dos eventos, cuando se active el evento, debería invocarse el método público de CommandBinding para ejecutar los controladores de eventos. ¡Pero no se encontró dónde UIElement llama a CommandBinding!
No se preocupen, ¡ahora resolveremos estas dudas!
Para la Duda 1, supongamos que WPF todavía ejecuta el comando a través de eventos enrutados. Anteriormente mencionamos dos formas de registrar eventos enrutados. Busquemos en UIElement si estos dos eventos han sido registrados. ¡Efectivamente! En su método RegisterEvents(Type tipo) se registran estos dos eventos:
internal static void RegisterEvents(Type tipo)
{
// Código adicional...
EventManager.RegisterClassHandler(tipo, CommandManager.VistaPreviaEjecutadoEvent, new ExecutedRoutedEventHandler(ManejadorVistaPrevia), handledEventsToo: false);
EventManager.RegisterClassHandler(tipo, CommandManager.EjecutadoEvent, new ExecutedRoutedEventHandler(ManejadorEjecutado), handledEventsToo: false);
}
private static void ManejadorEjecutado(object remitente, ExecutedRoutedEventArgs argumentos)
{
// Código adicional...
CommandManager.OnExecuted(remitente, argumentos);
}
¡La primera duda ha sido revelada! Es decir, el constructor estático de UIElement registra estos dos eventos para el tipo UIElement a través del método RegisterEvents, y Button es una clase derivada de UIElement, por lo que naturalmente aplica.
Para la Duda 2, la forma más directa es ver quién llama al método público OnExecuted de CommandBinding. A través de la herramienta de descompilación ILSpy, podemos ver que el método UIElement.ManejadorEjecutado eventualmente ejecuta el método CommandBinding.OnExecuted a través de una serie de llamadas.
El método ManejadorEjecutado parece familiar. ¡Correcto! En la explicación de la Duda 1 mencionamos que este método fue registrado como controlador de eventos para el evento CommandManager.EjecutadoEvent. ¡Ah, las dudas 1 y 2 llegan al mismo punto!
Hasta aquí, deberíamos tener una comprensión clara del proceso de ejecución de los comandos (enrutados). A continuación, explicaremos cómo el sistema de comandos de WPF logra lo mencionado en el "Propósito de los comandos" sobre "indicar si la operación enviada puede ejecutarse". ¡Es hora de presentar a CommandManager!
CommandManager
CommandManager proporciona un conjunto de métodos estáticos para agregar y eliminar controladores de eventos para VistaPreviaEjecutado, Ejecutado, VistaPreviaPuedeEjecutar y PodeEjecutar en elementos específicos. También proporciona métodos para registrar objetos CommandBinding e InputBinding en clases específicas. Además, CommandManager ofrece un método a través del evento RequerySuggested para notificar a los comandos que activen sus eventos CanExecuteChanged.
El método InvalidateRequerySuggested proporcionado por CommandManager puede forzar a CommandManager a activar el evento RequerySuggested. En otras palabras, invocar el método InvalidateRequerySuggested puede notificar al comando especificado para activar su evento CanExecuteChanged.
¿Qué hace el evento CanExecuteChanged? Veamos la implementación en RoutedCommand:
public class RoutedCommand : ICommand
{
// Otros miembros...
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
// Otros miembros...
}
Podemos ver que el evento CanExecuteChanged en RoutedCommand es simplemente una envoltura del evento RequerySuggested de CommandManager. Es decir, los controladores de eventos registrados en CanExecuteChanged en realidad se registran en el evento RequerySuggested de CommandManager. Por lo tanto, cuando se activa el evento RequerySuggested a través del método InvalidateRequerySuggested de CommandManager, se invocarán los controladores de eventos CanExecuteChanged del comando.
Sin embargo, al usar RoutedCommand, no es necesario especificar un controlador de eventos para CanExecuteChanged. Solo necesita especificar un controlador de eventos para CanExecute en el CommandBinding asociado, y el estado del origen del comando se actualizará según la lógica del controlador de eventos de CanExecute. ¿Parece extraño? Dado que el estado del origen del comando se actualiza según la lógica de CanExecute, esto significa que el evento CanExecute fue activado. ¿Por quién? ¡No着急, la respuesta se revelará pronto!
Dado que el comando puede afectar el estado del origen del comando, busqeumos el origen del comando y su propiedad Command. Tomando Button como ejemplo, podemos ver que la propiedad de dependencia CommandProperty de Button tiene una callback llamada AlComandoCambiado especificada durante la inicialización.
CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonBase), new FrameworkPropertyMetadata(null, AlComandoCambiado));
Su implementación es la siguiente:
private static void AlComandoCambiado(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ButtonBase botonBase = (ButtonBase)d;
botonBase.AlComandoCambiado((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void AlComandoCambiado(ICommand comandoAnterior, ICommand comandoNuevo)
{
// Código adicional...
if (comandoNuevo != null)
{
ConectarComando(comandoNuevo);
}
}
private void ConectarComando(ICommand comando)
{
CanExecuteChangedEventManager.AddHandler(comando, AlCanExecuteChanged);
ActualizarPuedeEjecutar();
}
private void AlCanExecuteChanged(object remitente, EventArgs e)
{
ActualizarPodeEjecutar();
}
private void ActualizarPodeEjecutar()
{
if (Command != null)
{
CanExecute = CommandHelpers.CanExecuteCommandSource(this);
}
else
{
CanExecute = true;
}
}
La última propiedad CanExecute en el método ActualizarPodeEjecutar está asociada con la propiedad IsEnabled de Button. La implementación de CommandHelpers.CanExecuteCommandSource es la siguiente:
internal static bool CanExecuteCommandSource(ICommandSource origenComando)
{
ICommand comando = origenComando.Command;
if (comando != null)
{
object parametroComando = origenComando.CommandParameter;
IInputElement elementoEntrada = origenComando.CommandTarget;
RoutedCommand comandoEnrutado = comando as RoutedCommand;
if (comandoEnrutado != null)
{
if (elementoEntrada == null)
{
elementoEntrada = (origenComando as IInputElement);
}
return comandoEnrutado.CanExecute(parametroComando, elementoEntrada);
}
return command.CanExecute(parametroComando);
}
return false;
}
¿Empieza a tener sentido?
Cuando la propiedad Command de Button cambia, el método ConectarComando invoca el método ActualizarPodeEjecutar, que a su vez llama al método CanExecuteCommandSource de CommandHelpers, y este último invoca el método CanExecute de RoutedCommand (si el comando no es de tipo RoutedCommand, invoca directamente el método CanExecute de la interfaz ICommand). Este método CanExecute activa el evento enrutado CommandManager.CanExecuteEvent, que finalmente es capturado y procesado por el controlador de eventos CanExecute del CommandBinding asociado con este comando. El controlador de eventos de CanExecute tiene lógica para actualizar el estado del origen del comando.
Sin embargo, esto solo actualiza el estado de Button cuando la propiedad Command de Button cambia. ¿Cómo se actualiza el estado cuando la propiedad Command no cambia? El misterio está en ConectarComando, que asocia el método ActualizarPodeEjecutar con la clase CanExecuteChangedEventManager.
Podemos ver que en el método ConectarComando se invoca:
CanExecuteChangedEventManager.AddHandler(comando, AlCanExecuteChanged);
El segundo parámetro AlCanExecuteChanged realmente puede equivaler a invocar el método ICommand.CanExecute(object). La clave está en el método AddHandler, cuya implementación es:
public static void AddHandler(ICommand origen, EventHandler<EventArgs> controlador)
{
// Código adicional...
PrivateAddHandler(origen, controlador);
}
private void PrivateAddHandler(ICommand origen, EventHandler<EventArgs> controlador)
{
List<HandlerSink> lista = (List<HandlerSink>)base[origen];
if (lista == null)
{
lista = (List<HandlerSink>)(base[origen] = new List<HandlerSink>());
}
HandlerSink elemento = new HandlerSink(this, origen, controlador);
lista.Add(elemento);
AddHandlerToCWT(controlador, _cwt);
}
public HandlerSink(CanExecuteChangedEventManager manager, ICommand origen, EventHandler<EventArgs> controladorOriginal)
{
_manager = manager;
_source = new WeakReference(origen);
_originalHandler = new WeakReference(controladorOriginal);
_manejadorCanExecuteChanged = AlCanExecuteChanged;
origen.CanExecuteChanged += _manejadorCanExecuteChanged;
}
private void AlCanExecuteChanged(object remitente, EventArgs e)
{
// Código adicional...
EventHandler<EventArgs> controladorEventos = (EventHandler<EventArgs>)_originalHandler.Target;
if (controladorEventos != null)
{
controladorEventos(remitente, e);
}
else
{
_manager.ScheduleCleanup();
}
}
Por conveniencia, he incluido todo el código relacionado con el método AddHandler. A través del código anterior, podemos ver que AddHandler finalmente pasa el segundo parámetro controlador (equivalente a invocar ICommand.CanExecute(object)) al constructor de una instancia de HandlerSink. Al observar más de cerca este constructor, encontramos que dentro del constructor, HandlerSink se suscribe al evento ICommand.CanExecuteChanged. Lo que hace el controlador de eventos AlCanExecuteChanged es simplemente invocar el parámetro originalHandler pasado al constructor. Un poco más arriba en el código podemos ver que AddHandler asigna su segundo parámetro al parámetro formal originalHandler. En resumen, el constructor de HandlerSink vincula el método ICommand.CanExecute(object) al evento ICommand.CanExecuteChanged. A través de HandlerSink, RoutedCommand forma un ciclo cerrado. Por lo tanto, cuando el evento CommandManager.RequerySuggested se activa, se invocará el método ICommand.CanExecute(object).
Hagamos un resumen del método de actualización del estado del origen del comando:
Primero, a través de la encapsulación del evento RequerySuggested de CommandManager, el CanExecuteChanged de RoutedCommand se asocia con el método InvalidateRequerySuggested de CommandManager. Esto significa que cuando se invoca el método InvalidateRequerySuggested, se activa el evento CanExecuteChanged de RoutedCommand.
Segundo, a través de la propiedad Command de ICommandSource, su evento CanExecuteChanged se asocia con el evento CanExecute del CommandBinding relacionado (o con el método CanExecute del comando personalizado).
De esta manera, cuando se invoca el método InvalidateRequerySuggested de CommandManager, finalmente se invocará el evento CanExecute del CommandBinding del comando relacionado (o el método CanExecute del comando personalizado).
Hasta aquí, la ruta básica para actualizar el origen del comando está clara. ¡Pero falta algo! ¿Quién y cuándo invoca el método InvalidateRequerySuggested de CommandManager?
Veamos a través de ILSpy:
¡Hay tantas invocaciones! Veamos la primera:
// System.Windows.Input.CommandDevice
private void PostProcessInput(object remitente, ProcessInputEventArgs e)
{
if (e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent)
{
// Código adicional...
}
else if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyUpEvent || e.StagingItem.Input.RoutedEvent == Mouse.MouseUpEvent || e.StagingItem.Input.RoutedEvent == Keyboard.GotKeyboardFocusEvent || e.StagingItem.Input.RoutedEvent == Keyboard.LostKeyboardFocusEvent)
{
CommandManager.InvalidateRequerySuggested();
}
}
Podemos ver que cuando ocurren eventos de teclado o mouse, se activa la actualización del estado del origen del comando.
Hasta aquí termina la explicación sobre CommandManager y cómo actualizar el estado del origen del comando. A continuación, veamos cómo personalizar comandos.
- Comandos Personalizados ==========================
Si ha llegado hasta aquí, debería tener una comprensión profunda de RoutedCommand de WPF. RoutedCommand, en esencia, es un evento enrutado. Su ejecución necesariamente pasa por un proceso complejo: iniciar el evento enrutado, construir la ruta de enrutamiento y ejecutar los manejadores de comandos a lo largo de la ruta. Por lo tanto, al usar comandos, podría enfrentar dos opciones:
- Mi comando necesita enrutamiento.
- Mi comando no necesita enrutamiento; quiero que sea ligero.
Para estos dos casos, hay dos formas de personalizar comandos:
Método 1: Si el comando necesita enrutamiento, puede instanciar directamente RoutedCommand o RoutedUICommand, o extender estas dos implementaciones.
A continuación, nos centraremos en el Método 2.
La interfaz ICommand contiene un evento CanExecuteChanged y dos métodos: CanExecute y Execute.
public interface ICommand
{
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);
void Execute(object parameter);
}
Mentionamos anteriormente que al ejecutar un comando, cuando el comando no es de tipo RoutedCommand, WPF primero invocará el método CanExecute de la interfaz ICommand para determinar si el comando puede ejecutarse en ese momento, y luego invocará su método Execute. La ejecución del comando se completa aquí. Por lo tanto, al implementar la interfaz ICommand, solo necesita que el método Execute de la clase derivada maneje la operación del comando.
Aquí está el código directamente:
class MiComando : ICommand
{
private readonly Predicate<object> _metodoPodeEjecutar;
private readonly Action<object> _metodoEjecutar;
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public MiComando(Action<object> metodoEjecutar, Predicate<object> metodoPodeEjecutar = null)
{
_metodoEjecutar = metodoEjecutar ?? throw new ArgumentNullException(nameof(metodoEjecutar));
_metodoPodeEjecutar = metodoPodeEjecutar;
}
public bool CanExecute(object parameter)
{
if (_metodoPodeEjecutar != null)
{
return _metodoPodeEjecutar(parameter);
}
return true;
}
public void Execute(object parameter)
{
_metodoEjecutar(parameter);
}
}
Resumen
- El sistema de comandos incluye cuatro elementos básicos: ICommand, ICommandSource, el objetivo del comando y CommandBinding. Sin embargo, la propiedad CommandTarget en ICommandSource solo es útil cuando el comando es RoutedCommand; de lo contrario, se ignora directamente durante la ejecución del comando.
- Como su nombre indica, RoutedCommand en esencia sigue siendo un evento enrutado, pero solo es responsable de iniciar el evento enrutado y no ejecuta la lógica del comando. La lógica del comando la ejecuta el CommandBinding asociado con el comando específico.
- Dado que RoutedCommand está basado en eventos enrutados, el proceso complejo de iniciar eventos enrutados, construir rutas de enrutamiento y ejecutar manejadores de comandos a lo largo de la ruta inevitalbemente afectará el rendimiento. Por lo tanto, si no necesita que el comando se enrute, puede construir un comando personalizado simple.
- Al personalizar un comando, si desea que el sistema de comandos cambie el estado ejecutable del origen del comando, necesita encapsular el evento RequerySuggested de CommandManager a través del evento CanExecuteChanged durante la implementación.
A continuación se presenta un diagrama UML del sistema de comandos:
Referencias
- Command Overview - WPF .NET Framework | Microsoft Learn