WPF CommandParameter เป็น NULL ครั้งแรกที่เรียก CanExecute


86

ฉันพบปัญหาเกี่ยวกับ WPF และคำสั่งที่ผูกไว้กับปุ่มภายใน DataTemplate ของ ItemsControl สถานการณ์ค่อนข้างตรงไปตรงมา ItemsControl ถูกผูกไว้กับรายการของวัตถุและฉันต้องการที่จะสามารถลบแต่ละวัตถุในรายการได้โดยคลิกที่ปุ่ม ปุ่มเรียกใช้คำสั่งและคำสั่งจะดูแลการลบ CommandParameter ถูกผูกไว้กับ Object ที่ฉันต้องการลบ ด้วยวิธีนี้ฉันรู้ว่าผู้ใช้คลิกอะไร ผู้ใช้ควรจะลบอ็อบเจกต์ "ของตัวเอง" ได้เท่านั้นดังนั้นฉันจึงต้องทำการตรวจสอบบางอย่างในการเรียก "CanExecute" ของ Command เพื่อตรวจสอบว่าผู้ใช้มีสิทธิ์ที่ถูกต้อง

ปัญหาคือพารามิเตอร์ที่ส่งไปยัง CanExecute เป็น NULL ในครั้งแรกที่เรียก - ดังนั้นฉันจึงไม่สามารถเรียกใช้ตรรกะเพื่อเปิด / ปิดคำสั่งได้ อย่างไรก็ตามหากฉันเปิดใช้งานทุกอย่างแล้วคลิกปุ่มเพื่อดำเนินการคำสั่ง CommandParameter จะถูกส่งผ่านอย่างถูกต้อง นั่นหมายความว่าการผูกกับ CommandParameter กำลังทำงาน

XAML สำหรับ ItemsControl และ DataTemplate มีลักษณะดังนี้:

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

อย่างที่คุณเห็นฉันมีรายการวัตถุความคิดเห็น ฉันต้องการให้ CommandParameter ของ DeleteCommentCommand ถูกผูกไว้กับวัตถุ Command

ฉันเดาว่าคำถามของฉันคือใครเคยประสบปัญหานี้มาก่อนหรือไม่? CanExecute ถูกเรียกใช้คำสั่งของฉัน แต่พารามิเตอร์เป็นโมฆะเสมอในครั้งแรก - ทำไมถึงเป็นเช่นนั้น

อัปเดต:ฉันสามารถ จำกัด ปัญหาให้แคบลงเล็กน้อย ฉันเพิ่ม Debug ValueConverter ที่ว่างเปล่าเพื่อให้ฉันสามารถส่งออกข้อความเมื่อ CommandParameter ถูกผูกไว้กับข้อมูล ปรากฎว่าปัญหาคือเมธอด CanExecute ถูกดำเนินการก่อนที่ CommandParameter จะผูกไว้กับปุ่ม ฉันได้พยายามตั้งค่า CommandParameter ก่อนคำสั่ง (ตามที่แนะนำ) - แต่ก็ยังไม่ได้ผล เคล็ดลับในการควบคุม

Update2:มีวิธีใดบ้างในการตรวจจับเมื่อการเชื่อม "เสร็จสิ้น" เพื่อที่ฉันจะบังคับให้ประเมินคำสั่งใหม่ได้ นอกจากนี้ - เป็นปัญหาที่ฉันมีหลายปุ่ม (หนึ่งปุ่มสำหรับแต่ละรายการใน ItemsControl) ที่ผูกกับอินสแตนซ์ของ Command-object เดียวกันหรือไม่

อัปเดต 3 :ฉันได้อัปโหลดการจำลองข้อบกพร่องไปยัง SkyDrive ของฉัน: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip


ฉันมีปัญหาเดียวกันกับ ListBox
Hadi Eskandari

ขณะนี้มีรายงานข้อบกพร่องที่เปิดอยู่กับ WPF สำหรับปัญหานี้: github.com/dotnet/wpf/issues/316
UuDdLrLrSs

คำตอบ:


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 แทน. เพื่อให้บรรลุผลเช่นเดียวกัน


2
สิ่งนี้ได้ผลสำหรับฉัน ฉันไม่เข้าใจว่าทำไม ใครช่วยอธิบายหน่อย
TJKjaer

มันใช้งานไม่ได้ทำให้ CommandParameter ถูกผูกไว้ก่อน Command หรือไม่? สงสัยจะต้องใช้ตัวแปลง ...
MBoros

2
นี่ไม่ใช่วิธีแก้ปัญหา นี่คือแฮ็ค? เกิดอะไรขึ้น? นี้ใช้ในการทำงาน?
จอร์แดน

สมบูรณ์แบบใช้ได้กับฉัน! เวทมนตร์อยู่ในบรรทัด <Binding /> ซึ่งทำให้การผูกคำสั่งได้รับการอัปเดตเมื่อเทมเพลตข้อมูลเปลี่ยนแปลง (ซึ่งผูกไว้กับพารามิเตอร์คำสั่ง)
Andreas Kahler

57

ฉันประสบปัญหาเดียวกันนี้ขณะพยายามผูกกับคำสั่งบนโมเดลมุมมองของฉัน

ฉันเปลี่ยนให้ใช้การเชื่อมโยงแหล่งที่มาสัมพัทธ์แทนที่จะอ้างถึงองค์ประกอบตามชื่อและนั่นก็เป็นเคล็ดลับ การเชื่อมโยงพารามิเตอร์ไม่เปลี่ยนแปลง

รหัสเก่า:

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

รหัสใหม่:

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

อัปเดต : ฉันเพิ่งเจอปัญหานี้โดยไม่ใช้ ElementName ฉันผูกกับคำสั่งในโมเดลมุมมองของฉันและบริบทข้อมูลของปุ่มคือโมเดลมุมมองของฉัน ในกรณีนี้ฉันต้องย้ายแอตทริบิวต์ CommandParameter ก่อนแอตทริบิวต์ Command ในการประกาศปุ่ม (ใน XAML)

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

42
การย้าย CommandParameter ไปข้างหน้า Command เป็นคำตอบที่ดีที่สุดสำหรับเธรดนี้
BSick7

6
การย้ายลำดับของแอตทริบิวต์ไม่ได้ช่วยเรา ฉันจะแปลกใจถ้ามันมีผลต่อคำสั่งประหารชีวิต
Jack Ukleja

3
ฉันไม่รู้ว่าทำไมถึงได้ผล รู้สึกว่ามันไม่ควร แต่มันทำโดยสิ้นเชิง
RMK

1
ฉันมีปัญหาเดียวกัน - RelativeSource ไม่ได้ช่วยเปลี่ยนลำดับของแอตทริบิวต์ ขอบคุณสำหรับการอัพเดท!
Grant Crofton

14
ในฐานะคนที่ใช้ส่วนขยายอย่างเคร่งครัดเพื่อตกแต่ง XAML โดยอัตโนมัติ (แยกแอตทริบิวต์ข้ามบรรทัดแก้ไขการเยื้องจัดลำดับแอตทริบิวต์ใหม่) ข้อเสนอในการเปลี่ยนลำดับCommandParameterและCommandทำให้ฉันกลัว
Guttsy

29

ฉันพบว่าลำดับที่ฉันตั้งค่า Command และ CommandParameter สร้างความแตกต่าง การตั้งค่าคุณสมบัติ Command ทำให้ CanExecute ถูกเรียกใช้ทันทีดังนั้นคุณต้องการให้ CommandParameter ตั้งค่าไว้แล้ว ณ จุดนั้น

ฉันพบว่าการสลับลำดับคุณสมบัติใน XAML อาจมีผลได้จริงแม้ว่าฉันจะไม่มั่นใจว่าจะแก้ปัญหาของคุณได้ มันคุ้มค่าที่จะลอง

ดูเหมือนคุณจะแนะนำว่าปุ่มไม่เคยเปิดใช้งานซึ่งน่าแปลกใจเนื่องจากฉันคาดว่า CommandParameter จะถูกตั้งค่าในไม่ช้าหลังจากคุณสมบัติ Command ในตัวอย่างของคุณ การเรียก CommandManager.InvalidateRequerySuggested () ทำให้ปุ่มเปิดใช้งานหรือไม่


3
พยายามตั้งค่า CommandParameter ก่อน Command - ยังคงเรียกใช้ CanExecute แต่ยังคงส่งผ่านเป็น NULL ... แย่จัง - แต่ขอบคุณสำหรับเคล็ดลับ นอกจากนี้ยังเรียก CommandManager.InvalidateRequerySuggested (); ไม่สร้างความแตกต่างใด ๆ
Jonas Follesø

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

8

นี่เป็นกระทู้เก่า แต่เนื่องจาก Google พาฉันมาที่นี่เมื่อฉันมีปัญหานี้ฉันจะเพิ่มสิ่งที่ใช้ได้ผลสำหรับฉันสำหรับ DataGridTemplateColumn ด้วยปุ่ม

เปลี่ยนการผูกจาก:

CommandParameter="{Binding .}"

ถึง

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

ไม่แน่ใจว่าทำไมถึงใช้งานได้ แต่สำหรับฉัน


ฉันได้ลองทั้งสองคำตอบที่ได้คะแนนสูงข้างต้นแล้ว แต่คำตอบนี้ใช้ได้กับฉันเท่านั้น ดูเหมือนว่าจะเป็นปัญหาภายในของการควบคุมเองไม่ใช่การผูกมัด แต่ก็ยังมีคนจำนวนมากที่ใช้งานได้กับคำตอบข้างต้น ขอบคุณ!
Javidan

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ที่ฉันโพสต์ในฟอรัม Prismเมื่อวานนี้ เพิ่มลักษณะการทำงานที่ขาดหายไปซึ่งการเปลี่ยนแปลงCommandParameterสาเหตุที่Commandต้องสอบถามอีกครั้ง

มีความซับซ้อนบางอย่างที่นี่เกิดจากความพยายามของฉันที่จะหลีกเลี่ยงการรั่วไหลของหน่วยความจำที่เกิดถ้าคุณโทรเป็นโดยไม่ต้องโทรในภายหลังPropertyDescriptor.AddValueChanged PropertyDescriptor.RemoveValueChangedฉันพยายามแก้ไขโดยการยกเลิกการลงทะเบียนตัวจัดการเมื่อยกเลิกการโหลด ekement

คุณอาจต้องลบข้อมูลออกIDelegateCommandเว้นแต่คุณจะใช้ Prism (และต้องการทำการเปลี่ยนแปลงเช่นเดียวกับฉันในไลบรารี Prism) โปรดทราบว่าโดยทั่วไปเราไม่ได้ใช้RoutedCommands ที่นี่ (เราใช้ 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();
            }

        }
    }
}

พบรายงานข้อบกพร่องของคุณที่ Connect มีโอกาสที่คุณจะอัปเดตโพสต์ของคุณที่นี่ด้วยรหัสสุดท้ายของสิ่งนี้หรือไม่ หรือคุณเคยพบวิธีแก้ไขที่ดีกว่านี้หรือไม่?
Markus Hütter

วิธีแก้ปัญหาที่ง่ายกว่าคือการสังเกตคุณสมบัติ CommandParameter โดยใช้การโยงแทนตัวบอกคุณสมบัติ มิฉะนั้นทางออกที่ดี! อันนี้แก้ไขปัญหาพื้นฐานได้จริงแทนที่จะแค่แนะนำการแฮ็กหรือวิธีแก้ปัญหาที่น่าอึดอัดใจ
Sebastian Negraszus

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) ภายใต้โมฆะเสมือนที่มีการป้องกัน OnCanExecuteChanged () ให้แก้ไขดังต่อไปนี้:

protected virtual void OnCanExecuteChanged()
{
     // add this line
     CommandManager.InvalidateRequerySuggested();
     WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers );
}

4) รวบรวมโซลูชันใหม่จากนั้นไปที่โฟลเดอร์ Debug หรือ Release ที่ DLL ที่คอมไพล์อยู่ คัดลอก Microsoft.Practices.Composite.Presentation.dll และ. pdb (ถ้าคุณต้องการ) ไปยังตำแหน่งที่คุณอ้างอิงแอสเซมบลีภายนอกของคุณจากนั้นคอมไพล์แอปพลิเคชันของคุณใหม่เพื่อดึงเวอร์ชันใหม่

หลังจากนี้ CanExecute ควรเริ่มทำงานทุกครั้งที่ UI แสดงผลองค์ประกอบที่ผูกไว้กับ 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 จะถูกเรียกใช้เมื่อ Binding เปลี่ยนไปและหลังจาก Execute

มันจะไม่ทริกเกอร์โดยอัตโนมัติหาก ViewModel มีการเปลี่ยนแปลง แต่ตามที่กล่าวไว้ในเธรดนี้สามารถทำได้โดยการเรียก CommandManager.InvalidateRequerySuggested บนเธรด GUI

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

ฉันพบว่าDispatcherPriority.Normalสูงเกินไปที่จะทำงานได้อย่างน่าเชื่อถือ (หรือในกรณีของฉัน) การใช้DispatcherPriority.Loadedงานได้ผลดีและดูเหมาะสมกว่า (กล่าวคือเป็นการระบุอย่างชัดเจนว่าจะไม่เรียกผู้รับมอบสิทธิ์จนกว่าจะโหลดองค์ประกอบ UI ที่เกี่ยวข้องกับโมเดลมุมมอง)
Peter Duniho

0

เฮ้ Jonas ไม่แน่ใจว่าจะใช้งานได้ในเทมเพลตข้อมูลหรือไม่ แต่นี่คือไวยากรณ์การผูกที่ฉันใช้ในเมนูบริบท ListView เพื่อดึงรายการปัจจุบันเป็นพารามิเตอร์คำสั่ง:

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


ฉันทำสิ่งเดียวกันในมุมมองรายการของฉัน ในกรณีนี้เป็น ItemsControl ดังนั้นจึงไม่มีคุณสมบัติที่ชัดเจนในการ "ผูก" กับ (ในแผนผังภาพ) ฉันเดาว่าฉันต้องหาวิธีตรวจจับเมื่อการผูกเสร็จสิ้นและประเมิน CanExecute อีกครั้ง (เนื่องจาก CommandParameter ถูกผูกไว้ถึงช้า)
Jonas Follesø


0

คำตอบบางส่วนเกี่ยวกับการเชื่อมโยงกับ DataContext เพื่อรับ Command เอง แต่คำถามเกี่ยวกับ CommandParameter เป็นโมฆะเมื่อไม่ควรเป็น นอกจากนี้เรายังมีประสบการณ์นี้ ในลางสังหรณ์เราพบวิธีง่ายๆในการทำให้สิ่งนี้ใช้งานได้ใน ViewModel ของเรา นี่เป็นปัญหาเฉพาะสำหรับปัญหาค่า null CommandParameter ที่ลูกค้ารายงานโดยมีโค้ดหนึ่งบรรทัด หมายเหตุผู้มอบหมายงาน 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 เพื่อมองเข้าไปข้างในและดูว่าพารามิเตอร์คำสั่งคืออะไร

HTH,


พยายามใช้ Snoop - แต่มันยากมากที่จะดีบักเนื่องจากเป็นเพียง NULL เมื่อโหลดครั้งแรก ถ้าฉันเรียกใช้ Snoop มัน Command และ CommandParameter เป็นทั้ง seth ... มันเกี่ยวข้องกับการใช้ Commands ใน DataTemplate
Jonas Follesø

-1

commandManager.InvalidateRequerySuggested ใช้งานได้สำหรับฉันเช่นกัน ฉันเชื่อว่าลิงก์ต่อไปนี้พูดถึงปัญหาที่คล้ายกันและ M $ dev ยืนยันข้อ จำกัด ในเวอร์ชันปัจจุบันและ commandManager.InvalidateRequerySuggested เป็นวิธีแก้ปัญหา http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/

สิ่งสำคัญคือระยะเวลาในการเรียกใช้ commandManager.InvalidateRequerySuggested ควรเรียกใช้สิ่งนี้หลังจากแจ้งการเปลี่ยนแปลงค่าที่เกี่ยวข้องแล้ว


ลิงก์นั้นใช้ไม่ได้อีกต่อไป
Peter Duniho

-2

นอกเหนือจากคำแนะนำของ Ed Ballเกี่ยวกับการตั้งค่าCommandParameterก่อนCommandโปรดตรวจสอบว่าเมธอดCanExecuteของคุณมีพารามิเตอร์ของประเภทวัตถุ

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

หวังว่าจะป้องกันไม่ให้ใครบางคนใช้เวลาจำนวนมากในการหาวิธีรับพารามิเตอร์ SelectedItems เป็น CanExecute

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.