我遇到了WPF和命令的问题,这些问题绑定到ItemsControl的DataTemplate中的Button.这种情况很简单.ItemsControl绑定到一个对象列表,我希望能够通过单击一个Button删除列表中的每个对象.Button执行命令,Command负责删除.CommandParameter绑定到我要删除的Object.这样我知道用户点击了什么.用户应该只能删除他们的"自己的"对象 - 所以我需要在Command的"CanExecute"调用中进行一些检查,以验证用户是否具有正确的权限.
问题是传递给CanExecute的参数在第一次被调用时是NULL - 所以我无法运行逻辑来启用/禁用命令.但是,如果我启用了allways,然后单击按钮执行命令,则会正确传入CommandParameter.这意味着对CommandParameter的绑定正在起作用.
ItemsControl和DataTemplate的XAML如下所示:
所以你可以看到我有一个评论对象列表.我希望将DeleteCommentCommand的CommandParameter绑定到Command对象.
所以我想我的问题是:以前有没有人遇到过这个问题?我的命令会调用CanExecute,但第一次参数总是为NULL - 为什么会这样?
更新:我能够将问题缩小一点.我添加了一个空的Debug ValueConverter,以便在CommandParameter是数据绑定时输出消息.事实证明,在CommandParameter绑定到按钮之前执行CanExecute方法.我试图在Command之前设置CommandParameter(如建议的那样) - 但它仍然不起作用.有关如何控制它的任何提示.
Update2:有没有办法检测绑定何时"完成",以便我可以强制重新评估命令?另外 - 我有一个问题,我有多个按钮(ItemsControl中的每个项目一个)绑定到Command对象的同一个实例?
Update3:我已经将错误的副本上传到我的SkyDrive:http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip
尝试绑定到我的视图模型上的命令时,我遇到了同样的问题.
我将其更改为使用相对源绑定,而不是按名称引用元素,这就是诀窍.参数绑定没有改变.
旧代码:
Command="{Binding DataContext.MyCommand, ElementName=myWindow}"
新守则:
Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=Views:MyView}}"
更新:我刚刚遇到这个问题而没有使用ElementName,我绑定到我的视图模型上的命令,按钮的数据上下文是我的视图模型.在这种情况下,我必须简单地在Button声明中的Command属性之前移动CommandParameter属性(在XAML中).
CommandParameter="{Binding Groups}" Command="{Binding StartCommand}"
我发现我设置Command和CommandParameter的顺序有所不同.设置Command属性会导致立即调用CanExecute,因此您希望在该点设置CommandParameter.
我发现在XAML中切换属性的顺序实际上可以产生影响,尽管我不相信它会解决你的问题.不过,值得一试.
您似乎建议按钮永远不会启用,这是令人惊讶的,因为我希望在示例中的Command属性之后不久设置CommandParameter.调用CommandManager.InvalidateRequerySuggested()会导致按钮被启用吗?
我偶然发现了类似的问题,并使用我可靠的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中使用时,它看起来如下所示.
您必须将TriggerConverter添加为某个地方的资源才能使其正常工作.现在,Command属性不是在CommandParameter的值可用之前设置的.您甚至可以绑定到RelativeSource.Self和CommandParameter而不是.达到同样的效果.
我知道这个帖子有点陈旧,但是我想出了另一种方法来解决这个我想分享的问题.因为命令的CanExecute方法在设置CommandParameter属性之前执行,所以我创建了一个带有附加属性的辅助类,该属性强制在绑定更改时再次调用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); } }
然后在按钮上将命令参数绑定到...
我希望这可能有助于其他人解决这个问题.
这是一个老线程,但是当我遇到这个问题时谷歌把我带到了这里,我会用一个按钮为DataGridTemplateColumn添加对我有用的东西.
更改绑定:
CommandParameter="{Binding .}"
至
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
不知道为什么它有效,但它确实适合我.
您可以使用我昨天CommandParameterBehavior
发布到Prism论坛的那个.它添加了缺少的行为,其中对要重新查询的CommandParameter
原因进行了更改Command
.
这里有一些复杂性,这是因为我试图避免在PropertyDescriptor.AddValueChanged
没有以后调用的情况下调用时导致的内存泄漏PropertyDescriptor.RemoveValueChanged
.我尝试通过在卸载ekement时取消注册处理程序来解决这个问题.
你可能需要删除这些IDelegateCommand
东西,除非你使用的是Prism(并希望对我和Prism库进行相同的更改).另请注意,我们一般不会RoutedCommand
在这里使用s(我们DelegateCommand
几乎都使用Prism )所以如果我的召唤CommandManager.InvalidateRequerySuggested
引发某种破坏已知宇宙或任何东西的量子波浪崩溃级联,请不要让我负责.
using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace Microsoft.Practices.Composite.Wpf.Commands { ////// 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. /// public static class CommandParameterBehavior { ////// Identifies the IsCommandRequeriedOnChange attached property /// ////// When a control has the public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty = DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange", typeof(bool), typeof(CommandParameterBehavior), new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged))); ////// attached property set to true, then any change to it's /// property will cause the state of /// the command attached to it's property to /// be reevaluated. /// /// Gets the value for the /// The object to adapt. ///attached property. /// Whether the update on change behavior is enabled. public static bool GetIsCommandRequeriedOnChange(DependencyObject target) { return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty); } ////// Sets the /// The object to adapt. This is typically aattached property. /// , /// or /// Whether the update behaviour should be enabled. 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(); } } } }