Me he encontrado con un problema con WPF y los comandos que están vinculados a un botón dentro del DataTemplate de un ItemsControl. El escenario es bastante sencillo. ItemsControl está vinculado a una lista de objetos, y quiero poder eliminar cada objeto de la lista haciendo clic en un botón. El botón ejecuta un comando y el comando se encarga de la eliminación. El parámetro CommandParameter está vinculado al objeto que quiero eliminar. De esa manera sé en qué hizo clic el usuario. Un usuario solo debería poder eliminar sus "propios" objetos, por lo que necesito hacer algunas comprobaciones en la llamada "CanExecute" del comando para verificar que el usuario tiene los permisos correctos.
El problema es que el parámetro pasado a CanExecute es NULL la primera vez que se llama, por lo que no puedo ejecutar la lógica para habilitar / deshabilitar el comando. Sin embargo, si lo habilito siempre y luego hago clic en el botón para ejecutar el comando, el parámetro CommandParameter se pasa correctamente. Eso significa que el enlace contra CommandParameter está funcionando.
El XAML para ItemsControl y DataTemplate se ve así:
<ItemsControl
x:Name="commentsList"
ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
Width="Auto" Height="Auto">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button
Content="Delete"
FontSize="10"
Command="{Binding Path=DataContext.DeleteCommentCommand, ElementName=commentsList}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Como puede ver, tengo una lista de objetos Comentarios. Quiero que el parámetro CommandParameter del DeleteCommentCommand esté vinculado al objeto Command.
Así que supongo que mi pregunta es: ¿alguien ha experimentado este problema antes? CanExecute recibe una llamada en mi comando, pero el parámetro siempre es NULL la primera vez, ¿por qué?
Actualización: pude reducir un poco el problema. Agregué un Debug ValueConverter vacío para poder generar un mensaje cuando CommandParameter está vinculado a datos. Resulta que el problema es que el método CanExecute se ejecuta antes de que CommandParameter esté vinculado al botón. Intenté configurar el parámetro CommandParameter antes del comando (como se sugiere), pero todavía no funciona. Cualquier consejo sobre cómo controlarlo.
Update2: ¿Hay alguna forma de detectar cuando el enlace está "hecho", de modo que pueda forzar la reevaluación del comando? Además, ¿es un problema que tenga varios botones (uno para cada elemento en ItemsControl) que se unen a la misma instancia de un objeto Command?
Update3: He subido una reproducción del error a mi SkyDrive: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip
Respuestas:
Me encontré con un problema similar y lo resolví usando mi confiable TriggerConverter.
public class TriggerConverter : IMultiValueConverter { #region IMultiValueConverter Members public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // First value is target value. // All others are update triggers only. if (values.Length < 1) return Binding.DoNothing; return values[0]; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion }
Este convertidor de valores toma cualquier número de parámetros y devuelve el primero de ellos como valor convertido. Cuando se usa en un MultiBinding en su caso, tiene el siguiente aspecto.
<ItemsControl x:Name="commentsList" ItemsSource="{Binding Path=SharedDataItemPM.Comments}" Width="Auto" Height="Auto"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Button Content="Delete" FontSize="10" CommandParameter="{Binding}"> <Button.Command> <MultiBinding Converter="{StaticResource TriggerConverter}"> <Binding Path="DataContext.DeleteCommentCommand" ElementName="commentsList" /> <Binding /> </MultiBinding> </Button.Command> </Button> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
Tendrá que agregar TriggerConverter como recurso en algún lugar para que esto funcione. Ahora, la propiedad Command se establece no antes de que el valor del CommandParameter esté disponible. Incluso podría enlazar a RelativeSource.Self y CommandParameter en lugar de. para lograr el mismo efecto.
fuente
Estaba teniendo este mismo problema al intentar vincularme a un comando en mi modelo de vista.
Lo cambié para usar un enlace de origen relativo en lugar de referirme al elemento por su nombre y eso funcionó. La vinculación de parámetros no cambió.
Código antiguo:
Nuevo código:
Actualización : me encontré con este problema sin usar ElementName, estoy vinculando un comando en mi modelo de vista y mi contexto de datos del botón es mi modelo de vista. En este caso, simplemente tuve que mover el atributo CommandParameter antes del atributo Command en la declaración del botón (en XAML).
fuente
CommandParameter
yCommand
me asusta.Descubrí que el orden en el que configuro Command y CommandParameter marca la diferencia. Establecer la propiedad Command hace que CanExecute se llame inmediatamente, por lo que desea que CommandParameter ya esté configurado en ese punto.
Descubrí que cambiar el orden de las propiedades en el XAML puede tener un efecto, aunque no estoy seguro de que resuelva su problema. Sin embargo, vale la pena intentarlo.
Parece que está sugiriendo que el botón nunca se habilita, lo cual es sorprendente, ya que esperaría que CommandParameter se establezca poco después de la propiedad Command en su ejemplo. ¿Llamar a CommandManager.InvalidateRequerySuggested () hace que el botón se habilite?
fuente
Se me ocurrió otra opción para solucionar este problema que quería compartir. Debido a que el método CanExecute del comando se ejecuta antes de que se establezca la propiedad CommandParameter, creé una clase auxiliar con una propiedad adjunta que obliga al método CanExecute a ser llamado nuevamente cuando cambia el enlace.
Y luego, en el botón al que desea vincular un parámetro de comando ...
<Button Content="Press Me" Command="{Binding}" helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
Espero que esto ayude a alguien más con el problema.
fuente
Este es un hilo antiguo, pero como Google me trajo aquí cuando tuve este problema, agregaré lo que funcionó para mí para DataGridTemplateColumn con un botón.
Cambiar el enlace de:
a
No estoy seguro de por qué funciona, pero a mí me funcionó.
fuente
Recientemente me encontré con el mismo problema (para mí fue para los elementos del menú en un menú contextual), y aunque puede que no sea una solución adecuada para cada situación, encontré una forma diferente (¡y mucho más corta!) De resolver esto. problema:
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
Ignorando la
Tag
solución alternativa basada en -para el caso especial del menú contextual, la clave aquí es enlazarCommandParameter
regularmente, pero enlazarCommand
con el adicionalIsAsync=True
. Esto retrasará un poco la vinculación del comando real (y por lo tanto suCanExecute
llamada), por lo que el parámetro ya estará disponible. Esto significa, sin embargo, que por un breve momento, el estado habilitado podría estar equivocado, pero para mi caso, eso fue perfectamente aceptable.fuente
Es posible que pueda usar mi
CommandParameterBehavior
que publiqué en los foros de Prism ayer. Agrega el comportamiento faltante donde un cambio en laCommandParameter
causaCommand
se vuelve a consultar.Aquí hay cierta complejidad causada por mis intentos de evitar la pérdida de memoria causada si llamas
PropertyDescriptor.AddValueChanged
sin llamar más tardePropertyDescriptor.RemoveValueChanged
. Intento solucionarlo anulando el registro del controlador cuando se descarga el ekement.Probablemente necesitará eliminar las
IDelegateCommand
cosas a menos que esté usando Prism (y quiera hacer los mismos cambios que yo en la biblioteca de Prism). También tenga en cuenta que generalmente no usamosRoutedCommand
s aquí (usamos Prism'sDelegateCommand<T>
para casi todo) así que por favor no me hagan responsable si mi llamada aCommandManager.InvalidateRequerySuggested
desencadena algún tipo de cascada de colapso de función de onda cuántica que destruye el universo conocido o cualquier cosa.using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace Microsoft.Practices.Composite.Wpf.Commands { /// <summary> /// This class provides an attached property that, when set to true, will cause changes to the element's CommandParameter to /// trigger the CanExecute handler to be called on the Command. /// </summary> public static class CommandParameterBehavior { /// <summary> /// Identifies the IsCommandRequeriedOnChange attached property /// </summary> /// <remarks> /// When a control has the <see cref="IsCommandRequeriedOnChangeProperty" /> /// attached property set to true, then any change to it's /// <see cref="System.Windows.Controls.Primitives.ButtonBase.CommandParameter" /> property will cause the state of /// the command attached to it's <see cref="System.Windows.Controls.Primitives.ButtonBase.Command" /> property to /// be reevaluated. /// </remarks> public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty = DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange", typeof(bool), typeof(CommandParameterBehavior), new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged))); /// <summary> /// Gets the value for the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property. /// </summary> /// <param name="target">The object to adapt.</param> /// <returns>Whether the update on change behavior is enabled.</returns> public static bool GetIsCommandRequeriedOnChange(DependencyObject target) { return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty); } /// <summary> /// Sets the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property. /// </summary> /// <param name="target">The object to adapt. This is typically a <see cref="System.Windows.Controls.Primitives.ButtonBase" />, /// <see cref="System.Windows.Controls.MenuItem" /> or <see cref="System.Windows.Documents.Hyperlink" /></param> /// <param name="value">Whether the update behaviour should be enabled.</param> public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value) { target.SetValue(IsCommandRequeriedOnChangeProperty, value); } private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is ICommandSource)) return; if (!(d is FrameworkElement || d is FrameworkContentElement)) return; if ((bool)e.NewValue) { HookCommandParameterChanged(d); } else { UnhookCommandParameterChanged(d); } UpdateCommandState(d); } private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source) { return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"]; } private static void HookCommandParameterChanged(object source) { var propertyDescriptor = GetCommandParameterPropertyDescriptor(source); propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged); // N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected, // so we need to hook the Unloaded event and call RemoveValueChanged there. HookUnloaded(source); } private static void UnhookCommandParameterChanged(object source) { var propertyDescriptor = GetCommandParameterPropertyDescriptor(source); propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged); UnhookUnloaded(source); } private static void HookUnloaded(object source) { var fe = source as FrameworkElement; if (fe != null) { fe.Unloaded += OnUnloaded; } var fce = source as FrameworkContentElement; if (fce != null) { fce.Unloaded += OnUnloaded; } } private static void UnhookUnloaded(object source) { var fe = source as FrameworkElement; if (fe != null) { fe.Unloaded -= OnUnloaded; } var fce = source as FrameworkContentElement; if (fce != null) { fce.Unloaded -= OnUnloaded; } } static void OnUnloaded(object sender, RoutedEventArgs e) { UnhookCommandParameterChanged(sender); } static void OnCommandParameterChanged(object sender, EventArgs ea) { UpdateCommandState(sender); } private static void UpdateCommandState(object target) { var commandSource = target as ICommandSource; if (commandSource == null) return; var rc = commandSource.Command as RoutedCommand; if (rc != null) { CommandManager.InvalidateRequerySuggested(); } var dc = commandSource.Command as IDelegateCommand; if (dc != null) { dc.RaiseCanExecuteChanged(); } } } }
fuente
Hay una forma relativamente sencilla de "solucionar" este problema con DelegateCommand, aunque requiere actualizar la fuente DelegateCommand y volver a compilar Microsoft.Practices.Composite.Presentation.dll.
1) Descargue el código fuente de Prism 1.2 y abra CompositeApplicationLibrary_Desktop.sln. Aquí hay un proyecto Composite.Presentation.Desktop que contiene la fuente DelegateCommand.
2) En el evento público EventHandler CanExecuteChanged, modifique para que se lea como sigue:
3) Bajo el vacío virtual protegido OnCanExecuteChanged (), modifíquelo de la siguiente manera:
4) Vuelva a compilar la solución, luego navegue a la carpeta Debug o Release donde se encuentran las DLL compiladas. Copie Microsoft.Practices.Composite.Presentation.dll y .pdb (si lo desea) donde hace referencia a sus ensamblados externos y luego vuelva a compilar su aplicación para extraer las nuevas versiones.
Después de esto, CanExecute debe activarse cada vez que la interfaz de usuario muestra elementos vinculados al DelegateCommand en cuestión.
Cuídate Joe
árbitrojoe en gmail
fuente
Después de leer algunas buenas respuestas a preguntas similares, cambié un poco en su ejemplo DelegateCommand para que funcione. En lugar de usar:
Lo cambié a:
Eliminé los siguientes dos métodos porque era demasiado vago para arreglarlos
y
Y eso es todo ... esto parece garantizar que se llamará a CanExecute cuando cambie el enlace y después del método Execute
No se activará automáticamente si se cambia el modelo de vista, pero como se menciona en este hilo, es posible llamar al CommandManager.InvalidateRequerySuggested en el hilo de la GUI
fuente
DispatcherPriority.Normal
es demasiado alto para funcionar de manera confiable (o en absoluto, en mi caso). UsarDispatcherPriority.Loaded
funciona bien y parece más apropiado (es decir, indica explícitamente que no se debe invocar al delegado hasta que los elementos de la interfaz de usuario asociados con el modelo de vista se hayan cargado).Hola Jonas, no estoy seguro de si esto funcionará en una plantilla de datos, pero aquí está la sintaxis de enlace que uso en un menú contextual de ListView para tomar el elemento actual como parámetro de comando:
CommandParameter = "{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"
fuente
He registrado esto como un error contra WPF en .Net 4.0, ya que el problema todavía existe en Beta 2.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
fuente
Algunas de estas respuestas tratan sobre la vinculación al DataContext para obtener el comando en sí, pero la pregunta era sobre el parámetro CommandParameter siendo nulo cuando no debería serlo. También experimentamos esto. Por una corazonada, encontramos una manera muy simple de hacer que esto funcione en nuestro ViewModel. Esto es específicamente para el problema nulo de CommandParameter informado por el cliente, con una línea de código. Tenga en cuenta el Dispatcher.BeginInvoke ().
public DelegateCommand<objectToBePassed> CommandShowReport { get { // create the command, or pass what is already created. var command = _commandShowReport ?? (_commandShowReport = new DelegateCommand<object>(OnCommandShowReport, OnCanCommandShowReport)); // For the item template, the OnCanCommand will first pass in null. This will tell the command to re-pass the command param to validate if it can execute. Dispatcher.BeginInvoke((Action) delegate { command.RaiseCanExecuteChanged(); }, DispatcherPriority.DataBind); return command; } }
fuente
Es una posibilidad remota. para depurar esto, puede intentar:
- comprobar el evento PreviewCanExecute.
- use snoop / wpf mole para mirar dentro y ver cuál es el parámetro de comando.
HTH,
fuente
El commandManager.InvalidateRequerySuggested también funciona para mí. Creo que el siguiente enlace habla de un problema similar, y M $ dev confirmó la limitación en la versión actual, y el commandManager.InvalidateRequerySuggested es la solución. http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/
Lo importante es el momento en que se invoca el comando commandManager.InvalidateRequerySuggested. Esto debe invocarse después de que se notifique el cambio de valor relevante.
fuente
Además de la sugerencia de Ed Ball sobre la configuración de CommandParameter antes de Command , asegúrese de que su método CanExecute tenga un parámetro de tipo de objeto .
Espero que evite que alguien pase la gran cantidad de tiempo que hice para descubrir cómo recibir SelectedItems como parámetro CanExecute
fuente