第一次调用CanExecute时,WPF CommandParameter为NULL
我遇到了绑定到一个ItemsControl的DataTemplate中的Button的WPF和命令的问题。 情况非常简单。 ItemsControl绑定到一个对象列表,我希望能够通过单击button来删除列表中的每个对象。 该button执行一个命令,并且该命令负责删除。 CommandParameter绑定到我想要删除的对象。 这样我就知道用户点击了什么。 用户应该只能删除他们的“自己的”对象 – 所以我需要在命令的“CanExecute”调用中进行一些检查,以validation用户是否具有正确的权限。
问题是传递给CanExecute的参数在第一次被调用时是NULL,所以我不能运行逻辑来启用/禁用命令。 但是,如果我启用了allways,然后单击button执行该命令,CommandParameter正确传入。 这意味着对CommandParameter的绑定正在工作。
ItemsControl和DataTemplate的XAML如下所示:
<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>
所以你可以看到我有一个评论对象的列表。 我想DeleteCommentCommand的CommandParameter绑定到Command对象。
所以我想我的问题是:有没有人遇到过这个问题? CanExecute在我的Command上被调用,但是这个参数第一次总是NULL – 为什么?
更新:我能够把问题缩小一点。 我添加了一个空的Debug ValueConverter,以便在CommandParameter数据绑定时输出消息。 原来问题是CanExecute方法在CommandParameter绑定到button之前执行。 我试图设置CommandParameter之前的命令(如build议) – 但它仍然无法正常工作。 有关如何控制它的任何提示。
Update2:有没有什么办法来检测绑定是否“完成”,以便我可以强制重新评估命令? 另外 – 这是一个问题,我有多个button(ItemsControl中的每个项目)绑定到一个Command对象的同一个实例?
Update3:我已经上传了一个bug的复制到我的SkyDrive: http : //cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip
我偶然发现了一个类似的问题,并使用我可信的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 }
该值转换器可以获取任意数量的参数,并将其中的第一个作为转换后的值传回。 在你的情况下,在MultiBinding中使用时,它看起来像下面的样子。
<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>
你将不得不添加TriggerConverter作为资源的地方这个工作。 现在,Command属性在CommandParameter的值变为可用之前不会被设置。 你甚至可以绑定到RelativeSource.Self和CommandParameter而不是。 达到同样的效果。
我在尝试绑定到视图模型上的命令时遇到了同样的问题。
我改变它使用相对源绑定,而不是通过名称引用元素,并做了伎俩。 参数绑定没有改变。
旧代码:
Command="{Binding DataContext.MyCommand, ElementName=myWindow}"
新代码:
Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=Views:MyView}}"
更新 :我刚刚遇到这个问题,而不使用ElementName,我绑定到我的视图模型上的命令,我的button的数据上下文是我的视图模型。 在这种情况下,我只需要在Button声明的Command属性之前(在XAML中)移动CommandParameter属性。
CommandParameter="{Binding Groups}" Command="{Binding StartCommand}"
我发现我设置Command和CommandParameter的顺序有所不同。 设置Command属性会导致CanExecute立即被调用,所以你需要CommandParameter已经被设置。
我发现在XAML中切换属性的顺序实际上可以起作用,尽pipe我并不确定它会解决您的问题。 不过值得一试。
你似乎暗示,button从来没有被启用,这是令人惊讶的,因为我希望CommandParameter在你的例子中的Command属性后不久设置。 调用CommandManager.InvalidateRequerySuggested()是否导致button被启用?
我知道这个线程是旧的,但我想出了另一个选项来解决这个问题,我想分享。 因为命令的CanExecute方法在CommandParameter属性被设置之前被执行,所以我创build了一个附加属性的助手类,该属性强制在绑定更改时再次调用CanExecute方法。
public static class ButtonHelper { public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached( "CommandParameter", typeof(object), typeof(ButtonHelper), new PropertyMetadata(CommandParameter_Changed)); private static void CommandParameter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { var target = d as ButtonBase; if (target == null) return; target.CommandParameter = e.NewValue; var temp = target.Command; // Have to set it to null first or CanExecute won't be called. target.Command = null; target.Command = temp; } public static object GetCommandParameter(ButtonBase target) { return target.GetValue(CommandParameterProperty); } public static void SetCommandParameter(ButtonBase target, object value) { target.SetValue(CommandParameterProperty, value); } }
然后在你想要绑定一个命令参数的button…
<Button Content="Press Me" Command="{Binding}" helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
我希望这可能会帮助别人解决这个问题。
您可以使用我昨天发布给Prism论坛的 CommandParameterBehavior
。 它增加了对CommandParameter
的更改导致Command
被重新查询的缺失行为。
这里有一些复杂性,这是由于我尝试避免由于调用PropertyDescriptor.AddValueChanged
引起的内存泄漏,而没有稍后调用PropertyDescriptor.RemoveValueChanged
所致。 我试图通过在卸载ekement时取消注册处理程序来解决这个问题。
你可能需要删除IDelegateCommand
东西,除非你使用Prism(并且想对Prism库做与我一样的修改)。 另外请注意,我们通常RoutedCommand
这里使用RoutedCommand
(我们使用Prism的DelegateCommand<T>
几乎所有的东西),所以如果我对CommandManager.InvalidateRequerySuggested
调用引发某种量子波函数崩溃级联,请不要让我负责破坏已知的宇宙或任何东西。
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); // NB 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(); } } } }
这是一个古老的线程,但由于谷歌带我到这里,当我有这个问题,我会添加什么为我的DataGridTemplateColumn用一个button。
从以下位置更改绑定:
CommandParameter="{Binding .}"
至
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
不知道为什么它的作品,但它为我做。
有一个相对简单的方法来解决这个问题与DelegateCommand,虽然它需要更新DelegateCommand源并重新编译Microsoft.Practices.Composite.Presentation.dll。
1)下载Prism 1.2源代码并打开CompositeApplicationLibrary_Desktop.sln。 在这里是一个包含DelegateCommand源的Composite.Presentation.Desktop项目。
2)在公共事件EventHandler CanExecuteChanged下修改如下:
public event EventHandler CanExecuteChanged { add { WeakEventHandlerManager.AddWeakReferenceHandler( ref _canExecuteChangedHandlers, value, 2 ); // add this line CommandManager.RequerySuggested += value; } remove { WeakEventHandlerManager.RemoveWeakReferenceHandler( _canExecuteChangedHandlers, value ); // add this line CommandManager.RequerySuggested -= value; } }
3)在受保护的虚拟无效OnCanExecuteChanged(),修改它如下:
protected virtual void OnCanExecuteChanged() { // add this line CommandManager.InvalidateRequerySuggested(); WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers ); }
4)重新编译解决scheme,然后导航到编译的DLL所在的Debug或Release文件夹。 将Microsoft.Practices.Composite.Presentation.dll和.pdb(如果需要)复制到引用外部程序集的位置,然后重新编译应用程序以拉取新版本。
在此之后,每当UI呈现绑定到相关DelegateCommand的元素时,CanExecute应该被触发。
保重,乔
在Gmail的refereejoe
在阅读了类似问题的一些很好的答案之后,我改变了你的例子中的DelegateCommand以使其工作。 而不是使用:
public event EventHandler CanExecuteChanged;
我把它改成:
public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }
我删除了以下两种方法,因为我懒得修复它们
public void RaiseCanExecuteChanged()
和
protected virtual void OnCanExecuteChanged()
这就是所有…这似乎确保CanExecute将在绑定更改和Execute方法后调用
它不会自动触发,如果ViewModel被改变,但正如在这个线程中提到的可能通过调用GUI线程上的CommandManager.InvalidateRequerySuggested
Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested);
嘿乔纳斯,不知道这是否会在数据模板中工作,但这里是我在ListView上下文菜单中使用的绑定语法来获取当前项作为命令参数:
CommandParameter =“{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu},Path = PlacementTarget.SelectedItem,Mode = TwoWay}”
我已经logging了这个作为对.net 4.0中的WPF的错误,因为问题仍然存在于Beta 2中。
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
其中一些答案是关于绑定到DataContext获取命令本身,但问题是关于CommandParameter不应该是null。 我们也经历过这个。 在预感上,我们find了一个非常简单的方法来让我们的ViewModel工作。 这是专门针对客户报告的CommandParameter null问题,只用一行代码。 请注意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; } }
它是一个远射。 debugging这个你可以试试:
– 检查PreviewCanExecute事件。
– 使用snoop / wpf mole来查看内部并查看命令参数是什么。
HTH,
commandManager.InvalidateRequerySuggested也适用于我。 我相信下面的链接会谈到类似的问题,M $ dev证实了当前版本的限制,commandManager.InvalidateRequerySuggested是解决方法。 http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/
调用commandManager.InvalidateRequerySuggested的时机非常重要。 这应该在通知相关的值更改之后调用。
除了Ed Ball在Command之前设置CommandParameter 的build议之外,请确保您的CanExecute方法具有对象types的参数。
私人布尔OnDeleteSelectedItemsCanExecute(对象SelectedItems)
{
// Your goes heres
}
希望它可以防止有人花费我花了大量的时间来弄清楚如何接收SelectedItems作为CanExecute参数