WPF CommandParameter首次调用NULL时为NULL


86

WPF和Commands绑定到ItemsControl的DataTemplate内部的Button时,我遇到了问题。该场景非常简单。ItemsControl绑定到对象列表,我希望能够通过单击按钮来删除列表中的每个对象。按钮执行命令,命令负责删除。CommandParameter绑定到我要删除的对象。这样我就知道用户单击了什么。用户只能删除其“自己的”对象-因此,我需要在Command的“ 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>

如您所见,我有一个Comments对象列表。我希望DeleteCommentCommand的CommandParameter绑定到Command对象。

所以我想我的问题是:以前有人遇到过这个问题吗?CanExecute在我的Command上被调用,但是第一次该参数始终为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


我有一个与ListBox完全相同的问题。
哈迪·埃斯坎达里

目前有针对此问题的针对WPF的未解决错误报告:github.com/dotnet/wpf/issues/316
UuDdLrLrSs

Answers:


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的值变为可用之前,不设置Command属性。您甚至可以绑定到RelativeSource.Self和CommandParameter而不是。达到同样的效果。


2
这对我有用。我不理解为什么。谁能解释?
TJKjaer 2013年

它不起作用是因为CommandParameter在Command之前绑定了吗?我怀疑您是否需要转换器...
MBoros 2014年

2
这不是解决方案。这是黑客吗?这到底是怎么回事?这曾经工作吗?
乔丹

完美,为我工作!魔术在<Binding />行中,当数据模板更改时(绑定到命令参数),该命令使命令绑定得到更新
Andreas Kahler

56

尝试绑定到我的视图模型上的命令时遇到了同样的问题。

我将其更改为使用相对源绑定,而不是按名称引用元素,从而达到了目的。参数绑定没有改变。

旧代码:

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

新代码:

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

更新:我只是在不使用ElementName的情况下遇到了此问题,我绑定到了视图模型上的命令,并且按钮的数据上下文是我的视图模型。在这种情况下,我只需要简单地将CommandParameter属性移到Button声明(在XAML中)的Command属性之前。

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

42
将CommandParameter移到Command前面是此线程的最佳答案。
BSick7'2

6
移动属性的顺序对我们没有帮助。如果它对执行顺序有影响,我会感到惊讶。
杰克·乌克丽雅

3
我不知道为什么这样。感觉不应该,但是完全可以。
RMK 2012年

1
我遇到了同样的问题-RelativeSource没有帮助,更改属性的顺序确实没有帮助。感谢更新!
Grant Crofton 2012年

14
至于谁宗教使用扩展自动美化XAML的人(拆分为多行属性,修复凹陷,重新排序属性)变化的顺序的建议CommandParameter,并Command让我害怕。
Guttsy 2014年

29

我发现我设置Command和CommandParameter的顺序有所不同。设置Command属性会导致CanExecute被立即调用,因此您希望此时已设置CommandParameter。

我发现切换XAML中的属性顺序实际上可以产生效果,尽管我不确定它能否解决您的问题。不过,值得一试。

您似乎在暗示该按钮永远不会启用,这令人惊讶,因为在您的示例中,我希望在Command属性之后不久设置CommandParameter。调用CommandManager.InvalidateRequerySuggested()是否会使按钮变为启用状态?


3
尝试在命令之前设置CommandParameter-仍执行CanExecute,但仍传递NULL ... Bummer-但感谢您的提示。另外,调用CommandManager.InvalidateRequerySuggested();。没有任何区别。
乔纳斯·弗勒索(JonasFollesø)

CommandManager.InvalidateRequerySuggested()为我解决了类似的问题。谢谢!
MJS

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

我希望这可能对其他人有所帮助。


做得很好,谢谢。我不敢相信M $在8年后还没有解决这个问题。糟透了!
McGarnagle '16

8

这是一个旧线程,但是由于遇到问题时Google将我带到了这里,所以我将使用按钮添加对DataGridTemplateColumn有用的内容。

从以下更改绑定:

CommandParameter="{Binding .}"

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

不知道为什么它起作用,但是对我有用。


我已经尝试了以上两个高分答案,但是这个仅对我有用。似乎控制本身不是绑定的内部问题,但是仍然有很多人使它与上面的答案一起工作。谢谢!
哈维丹

6

最近,我遇到了同样的问题(对我来说,这是上下文菜单中的菜单项),尽管它可能并不适合每种情况,但我发现了另一种解决方法(而且更短!)。问题:

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

忽略Tag上下文菜单的特殊情况的基于变通办法,此处的关键是CommandParameter定期绑定,但将Command附加绑定IsAsync=True。这将延迟实际命令(及其CanExecute调用)的绑定,因此该参数将已经可用。但这意味着,在短暂的时间内,启用状态可能是错误的,但就我而言,这是完全可以接受的。


5

您也许可以使用我昨天CommandParameterBehavior棱镜论坛上发布的我的文章。它在重新查询CommandParameter原因更改的位置添加了缺失的行为Command

我尝试避免在PropertyDescriptor.AddValueChanged不调用以后再调用导致的内存泄漏,这会造成一些复杂性PropertyDescriptor.RemoveValueChanged。我尝试通过在ekement卸载时注销处理程序来解决此问题。

IDelegateCommand除非您正在使用Prism(并且想对Prism库进行与我相同的更改),否则您可能需要删除这些内容。还要注意,我们RoutedCommand在这里通常不使用s(DelegateCommand<T>几乎所有东西都使用Prism ),因此,如果我要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();
            }

        }
    }
}

在连接时遇到了您的错误报告。您是否有可能在此用您的最新代码更新您的帖子?还是您找到了更好的解决方法?
MarkusHütter

一个更简单的解决方案是使用绑定而不是属性描述符来观察CommandParameter属性。否则,一个很好的解决方案!这实际上解决了根本问题,而不仅仅是引入了笨拙的hack或解决方法。
塞巴斯蒂安·内格拉苏斯

1

尽管需要更新DelegateCommand源并重新编译Microsoft.Practices.Composite.Presentation.dll,但是有一种相对简单的方法可以通过“ DelegateCommand”“修复”此问题。

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)在受保护的虚拟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


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

我发现这DispatcherPriority.Normal太高了,无法可靠地工作(或者就我而言)。使用DispatcherPriority.Loaded效果很好,并且似乎更合适(即,明确指示在与视图模型关联的UI元素实际上已加载之前,将不调用委托)。
彼得·杜尼奥

0

嗨,乔纳斯,不确定这是否可以在数据模板中使用,但是这是我在ListView上下文菜单中使用的绑定语法,它将当前项目作为命令参数:

CommandParameter =“ {Binding RelativeSource = {RelativeSource AncestorType = ContextMenu},Path = PlacementTarget.SelectedItem,Mode = TwoWay}”


我在列表视图中执行完全相同的操作。在这种情况下,它是一个ItemsControl,因此没有明显的属性可对(在可视树中)进行“绑定”。我想我必须找到一种方法来检测绑定何时完成,并重新评估CanExecute(因为CommandParameter被绑定了,直到很晚)
JonasFollesø08年


0

其中一些答案与绑定到DataContext以获得命令本身有关,但问题是有关CommandParameter在不应该为null时为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;
        }
    }

-1

它的远射。要调试此错误,您可以尝试:
-检查PreviewCanExecute事件。
-使用snoop / wpf mole窥视内部,看看commandparameter是什么。

HTH,


使用Snoop进行了尝试-但实际上很难调试,因为它在初始加载时只有NULL。如果我在其上运行Snoop,则Command和CommandParameter都是seth ...这与在DataTemplate中使用Commands有关。
乔纳斯·弗勒索


-2

除了Ed Ball关于Command之前设置CommandParameter的建议之外,请确保您的CanExecute方法具有对象类型的参数。

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

希望它可以防止某人花费大量时间来弄清楚如何将CannedParameters作为SelectedExecutes来接收

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.