WPF CommandParameter为NULL第一次CanExecute被称为

问题描述 投票:79回答:15

我遇到了与WPF和命令的问题绑定到一个ItemsControl的DataTemplate中内按钮。该方案是非常简单的。该ItemControl绑定到对象的列表,我希望能够通过点击一个按钮来删除列表中的每个对象。该按钮执行命令,并且命令采取删除的照顾。该CommandParameter必然会我要删除的对象。这样,我知道用户点击了什么。用户应该只能够删除“自己”的对象 - 所以我需要做一些检查,在命令中的“CanExecute”电话以验证用户具有正确的权限。

问题是,传递给CanExecute参数为NULL第一次,它被称为 - 所以我不能运行启用/禁用命令的逻辑。但是,如果我让永诺启用,然后单击按钮执行命令时,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被称为我的命令,但参数总是空的第一次 - 这是为什么?

更新:我能缩小问题下来了一点。我增加了一个空的调试ValueConverter这样我可以输出,当CommandParameter是数据绑定的消息。原来,问题是,之前CommandParameter被绑定到按钮时,执行该方法CanExecute。我曾尝试命令(如建议)之前设置CommandParameter - 但它仍然无法正常工作。如何控制它的任何提示。

UPDATE2:有什么方法来检测时绑定为“完成”,这样我可以强制命令的重新评估?此外 - 难道是我有多个按钮的问题(一个用于ItemsControl中的每个项目),其结合到一个命令对象相同的实例?

UPDATE3:我已上载该bug的再现我的SkyDrive:http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip

wpf data-binding xaml .net-3.5 command
15个回答
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作为资源添加的地方了这个工作。现在命令属性设置为CommandParameter的价值已经变得可用而不是之前。你甚至可以绑定到RelativeSource.Self和CommandParameter代替。要达到同样的效果。


0
投票

我已经登录这是在.NET 4.0中对WPF的错误,因为这个问题在Beta 2中仍然存在。

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976


0
投票

有些答案都是关于绑定到DataContext得到命令本身,但问题是关于CommandParameter被空时,它不应该。我们也经历了这一点。在上一个预感,我们发现了一个非常简单的方法来得到这个在我们的视图模型的工作。这是专门为客户报告的CommandParameter空问题,用一行代码。注意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;
        }
    }

0
投票

我最近遇到了同样的问题来了(对我来说是在上下文菜单中的菜单项),NAD虽然它可能不适合所有情况合适的解决方案,我发现了一个不同的(和短得多!)解决这个方式问题:

<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />

忽视了上下文菜单中的特殊情况,基于Tag-解决方法,这里的关键是要定期CommandParameter绑定,但绑定额外CommandIsAsync=True。这将延迟实际的命令(因此,其CanExecute调用)位的结合,所以该参数将已经可用。这意味着,虽然,短暂的片刻,启用状态可能是错的,但我的情况下,这是完全可以接受的。


-1
投票

它是一种长镜头。调试此您可以尝试: - 检查PreviewCanExecute事件。 - 使用探听/ WPF摩尔里面偷看,看看commandparameter是什么。

HTH,


-1
投票

该commandManager.InvalidateRequerySuggested对我的作品也是如此。我相信,关于类似的问题下面的链接会谈,M $ dev的证实在当前版本的限制,以及commandManager.InvalidateRequerySuggested是解决办法。 http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/

什么重要的是调用commandManager.InvalidateRequerySuggested的时机。相关价值变动通知后,应调用此方法。


-2
投票

除了在设置CommandParameter之前命令Ed Ball's suggestion,请确保您的CanExecute方法有对象类型的参数。

private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
{
    // Your goes heres
}

希望它可以防止有人花费的时间,我做了巨额弄清楚如何获得SelectedItems为CanExecute参数


51
投票

我有同样的问题,而试图绑定到我的视图模型的命令。

我改成了使用相对源绑定,而不是按名称指的是元素和没有的伎俩。参数绑定并没有改变。

旧代码:

Command="{Binding DataContext.MyCommand, ElementName=myWindow}"

新代码:

Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=Views:MyView}}"

更新:我只是碰到这个问题,而无需使用的ElementName,我对我的视图模型绑定到一个命令按钮的我的数据上下文为我的视图模型。在这种情况下,我曾在巴顿声明(在XAML)命令属性之前,只需移动CommandParameter属性。

CommandParameter="{Binding Groups}"
Command="{Binding StartCommand}"

28
投票

我发现,我在其中设置命令和CommandParameter顺序有差别。设置Command属性导致CanExecute被立即调用,所以你要CommandParameter已在该点设置。

我发现,在XAML切换属性的顺序其实可以有效果,虽然我不相信它会解决你的问题。这是值得一试,虽然。

你似乎在暗示按钮永远不会成为启用,这是令人惊讶的,因为我预计CommandParameter要在你的榜样Command属性后不久设置。是否调用CommandManager.InvalidateRequerySuggested()导致按钮变成启用?


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}" />

我希望这有助于也许别人有问题。


6
投票

这是一个古老的线程,但由于谷歌把我带到这里的时候,我有这个问题,我会添加什么工作对我来说有一个按钮DataGridTemplateColumn。

改变从绑定:

CommandParameter="{Binding .}"

CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"

不知道为什么它的工作原理,但它确实对我来说。


5
投票

您可以使用我的CommandParameterBehavior我昨天发布到Prism forums。它增加了丢失的行为,其中一个变化的CommandParameter导致Command重新查询。

这里也有一些复杂这里由我试图避免,如果你打电话PropertyDescriptor.AddValueChanged而没有后续调用PropertyDescriptor.RemoveValueChanged内存泄漏引起的。我试图解决由当ekement卸载注销处理。

你可能需要除非你使用棱镜(并且希望做出棱镜库相同的变化我)删除IDelegateCommand东西。另外请注意,我们一般不使用RoutedCommands这里(我们用棱镜的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();
            }

        }
    }
}

1
投票

有一个比较简单的方法来“解决”这个问题,DelegateCommand,虽然它需要更新DelegateCommand源,并重新编译Microsoft.Practices.Composite.Presentation.dll。

1)下载的棱镜1.2源代码和打开CompositeApplicationLibrary_Desktop.sln。在这里是一个Composite.Presentation.Desktop项目包含DelegateCommand源。

2)在公共事件事件处理程序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)重新编译该溶液中,然后导航到Debug或释放其中编译的DLL住文件夹。复制Microsoft.Practices.Composite.Presentation.dll和.PDB(如果你愿意的话)到你引用您的外部组件,然后重新编译应用程序,以拉动新版本。

在此之后,CanExecute应每用户界面呈现必然有问题的DelegateCommand元素时发射。

小心,乔

refereejoe在Gmail中


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()

而这一切......这似乎是确保CanExecute将被调用时绑定的变化和Execute方法后

如果视图模型被更改,但在这个线程中提到可以通过调用CommandManager.InvalidateRequerySuggested GUI线程上它不会自动触发

Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested);

0
投票

嘿乔纳斯,不知道这是否会在数据模板工作,但这里是绑定语法我在ListView上下文菜单用来抓取当前项目为命令参数:

CommandParameter = “{结合的RelativeSource = {的RelativeSource AncestorType =文本菜单},路径= PlacementTarget.SelectedItem,模式=双向}”

© www.soinside.com 2019 - 2024. All rights reserved.