我遇到了WPF和命令的问题,这些命令绑定到ItemsControl的DataTemplate中的Button。这种情况很简单。 ItemsControl绑定到一个对象列表,我希望能够通过单击一个Button删除列表中的每个对象。 Button执行命令,Command负责删除。 CommandParameter绑定到我要删除的Object。这样我知道用户点击了什么。用户应该只能删除他们的“自己的”对象 - 所以我需要在Command的“CanExecute”调用中进行一些检查,以验证用户是否具有正确的权限。
问题是传递给CanExecute的参数在第一次被调用时是NULL - 所以我无法运行逻辑来启用/禁用命令。但是,如果我启用了allways,然后单击按钮执行命令,则会正确传入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对象。
所以我想我的问题是:之前有没有人遇到过这个问题?在我的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
答案 0 :(得分:49)
尝试绑定到我的视图模型上的命令时遇到了同样的问题。
我将其更改为使用相对源绑定,而不是按名称引用元素,这就是诀窍。参数绑定没有改变。
旧代码:
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}"
答案 1 :(得分:27)
我发现我设置Command和CommandParameter的顺序有所不同。设置Command属性会导致立即调用CanExecute,因此您希望在此时设置CommandParameter。
我发现在XAML中切换属性的顺序实际上可以产生影响,尽管我不相信它会解决你的问题。不过,值得一试。
您似乎建议按钮永远不会启用,这是令人惊讶的,因为我希望在示例中的Command属性之后不久设置CommandParameter。调用CommandManager.InvalidateRequerySuggested()会导致按钮被启用吗?
答案 2 :(得分:14)
我偶然发现了一个类似的问题,并使用我可靠的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而不是。达到同样的效果。
答案 3 :(得分:13)
我知道这个帖子有点陈旧,但是我已经想出了另一个解决这个问题的方法,我想分享一下。因为命令的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);
}
}
然后在按钮上将命令参数绑定到...
<Button
Content="Press Me"
Command="{Binding}"
helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
我希望这可能会帮助其他人解决这个问题。
答案 4 :(得分:6)
这是一个旧帖子,但是当我遇到这个问题时谷歌把我带到了这里,我会用一个按钮添加对我来说DataGridTemplateColumn的功能。
更改绑定:
CommandParameter="{Binding .}"
到
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
不确定它为什么会起作用,但它确实适用于我。
答案 5 :(得分:5)
您可以使用我昨天发布到Prism forums的CommandParameterBehavior
。它会添加缺少的行为,其中CommandParameter
的更改会导致重新查询Command
。
由于我在没有稍后调用PropertyDescriptor.AddValueChanged
的情况下调用PropertyDescriptor.RemoveValueChanged
而尝试避免内存泄漏,导致了一些复杂性。我尝试通过在卸载ekement时取消注册处理程序来解决这个问题。
你可能需要删除IDelegateCommand
内容,除非你使用的是Prism(并希望对我的Prism库进行相同的更改)。另请注意,我们一般不会在这里使用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);
// 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();
}
}
}
}
答案 6 :(得分:1)
使用DelegateCommand“修复”此问题的方法相对简单,但需要更新DelegateCommand源并重新编译Microsoft.Practices.Composite.Presentation.dll。
1)下载Prism 1.2源代码并打开CompositeApplicationLibrary_Desktop.sln。这里是一个Composite.Presentation.Desktop项目,它包含DelegateCommand源。
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)在protected virtual void OnCanExecuteChanged()下,按如下所示进行修改:
protected virtual void OnCanExecuteChanged()
{
// add this line
CommandManager.InvalidateRequerySuggested();
WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers );
}
4)重新编译解决方案,然后导航到编译的DLL所在的Debug或Release文件夹。将Microsoft.Practices.Composite.Presentation.dll和.pdb(如果您愿意)复制到引用外部程序集的位置,然后重新编译应用程序以获取新版本。
在此之后,每当UI呈现绑定到相关DelegateCommand的元素时,都应触发CanExecute。
保重, 乔
在gmail上的refereejoe
答案 7 :(得分:1)
在阅读了类似问题的一些好答案之后,我在示例中更改了DelegateCommand以使其工作。而不是使用:
public event EventHandler CanExecuteChanged;
我把它改为:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
我删除了以下两种方法,因为我懒得修理它们
public void RaiseCanExecuteChanged()
和
protected virtual void OnCanExecuteChanged()
这就是全部...这似乎确保在Binding更改时和Execute方法之后将调用CanExecute
如果ViewModel被更改,它将不会自动触发,但是通过在GUI线程上调用CommandManager.InvalidateRequerySuggested可以在此线程中提到
Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested);
答案 8 :(得分:0)
Hey Jonas,不确定这是否适用于数据模板,但这是我在ListView上下文菜单中使用的绑定语法,以将当前项目作为命令参数获取:
CommandParameter =“{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu},Path = PlacementTarget.SelectedItem,Mode = TwoWay}”
答案 9 :(得分:0)
我已将此记录为.Net 4.0中针对WPF的错误,因为问题仍存在于Beta 2中。
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
答案 10 :(得分:0)
其中一些答案是关于绑定到DataContext以获取Command本身,但问题是当CommandParameter不应该是null时。我们也经历过这个。在预感中,我们找到了一种非常简单的方法,可以在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;
}
}
答案 11 :(得分:0)
我最近遇到了一个相同的问题(对我来说,这是上下文菜单中的菜单项),虽然它可能并不适合每种情况,但我发现了一种不同的方法(而且更短!)。解决这个问题的方法:
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
对于上下文菜单的特殊情况,忽略基于Tag
的解决方法,此处的关键是定期绑定CommandParameter
,但将Command
与附加的{{1}绑定}。这将延迟实际命令的绑定(并因此延迟其IsAsync=True
调用),因此该参数将已经可用。但这意味着,在短暂的时间内,启用状态可能是错误的,但就我而言,这是完全可以接受的。
答案 12 :(得分:-1)
这是一个很长的镜头。调试这个你可以尝试:
- 检查PreviewCanExecute事件
- 使用snoop / wpf mole查看内部并查看命令参数是什么。
HTH,
答案 13 :(得分:-1)
commandManager.InvalidateRequerySuggested也适用于我。我相信下面的链接讨论了类似的问题,并且M $ dev确认了当前版本的限制,而commandManager.InvalidateRequerySuggested是解决方法。 http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/
调用commandManager.InvalidateRequerySuggested的时机有多重要。应在通知相关值更改后调用此方法。
答案 14 :(得分:-2)
在命令之前设置 CommandParameter 旁边Ed Ball's suggestion,确保 CanExecute 方法的参数为对象类型。
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your goes heres
}
希望它可以防止有人花费大量时间来弄清楚如何接收SelectedItems作为CanExecute参数