为什么即使我调用CommandManager.InvalidateRequerySuggested(),WPF按钮的命令的CanExecute方法也不会被调用?

3

我遇到了与以下问题相同的问题:

当属性更改时,按钮命令CanExecute未被调用

如何强制文本框更改以启用我的WPF命令?

(简而言之:我的命令链接的按钮在应该启用时没有启用),但有一个小区别:我已经尝试调用CommandManager.InvalidateRequerySuggested(),但没有结果

最奇怪的是,只有当我在窗口上“触发”任何事件,比如在窗口的任何位置点击鼠标时,按钮才会启用。

以下是重现此问题的代码:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
    <CommandBinding Command="{x:Static local:Commands.RunTask}" x:Name="cmdRunTask" CanExecute="CanExecuteRunTask" Executed="ExecuteRunTask">    </CommandBinding>
    <CommandBinding Command="{x:Static local:Commands.PushButton}" x:Name="cmdPushButton" CanExecute="CanPushButton" Executed="PushButton"></CommandBinding>
</Window.CommandBindings>
<StackPanel>
    <Button Content="Run task"
                x:Name="runButton"
                Command="{x:Static local:Commands.RunTask}"
                >
    </Button>
    <ProgressBar x:Name="progress"
                     Value="{Binding Path=Value, Mode=OneWay}"
                 Height="30"
                     >
    </ProgressBar>
    <Button Content="Press me if you dare"
                x:Name="pushButton"
                Command="{x:Static local:Commands.PushButton}"
                >
    </Button>
</StackPanel>

using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DataContext = new Model();
            ((Model)DataContext).PropertyChanged += DataContext_PropertyChanged;
            InitializeComponent();
        }

        private void DataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Ready":
                    CommandManager.InvalidateRequerySuggested();
                    break;
            }
        }

        void CanExecuteRunTask(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        void ExecuteRunTask(object sender, ExecutedRoutedEventArgs e)
        {
            ((Model)DataContext).RunLongTask();
        }

        void CanPushButton(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = ((Model)DataContext).Ready;
        }

        void PushButton(object sender, ExecutedRoutedEventArgs e) { }
    }

    public static class Commands
    {
        public static readonly RoutedUICommand RunTask = new     RoutedUICommand();
        public static readonly RoutedUICommand PushButton = new     RoutedUICommand();
    }

    public class Model : INotifyPropertyChanged
    {
        private bool _ready = false;
        public bool Ready
        {
            get { return _ready; }
            set
            {
                _ready = value;
                RaisePropertyChanged("Ready");
            }
        }

        private int _value = 0;
        public int Value
        {
            get { return _value; }
            set
            {
                _value = value;
                RaisePropertyChanged("Value");
            }
        }

        public async void RunLongTask()
        {
            await RunLongTaskAsync();   
        }
        private Task RunLongTaskAsync()
        {
            this.Ready = false;
            Task t = new Task(() => {
                for (int i = 0; i <= 100; i++)
                {
                    Value = i;
                    System.Threading.Thread.Sleep(20);
                }
                this.Ready = true;
            });
            t.Start();
            return t;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

请阅读http://stackoverflow.com/help/how-to-ask。 - DotNetRussell
嗨,安东尼,我想你是指标题不够清晰,希望现在好些了。谢谢。 - Leonardo Spina
Leo,不,我的意思是请阅读如何提问的指南。这不是博客。您不应该在问题中添加答案。您不应该添加笑脸。您不应该发布没有实际代码可供测试的问题。将其视为历史文献。您应尽可能详细地描述您的问题,然后有人会解决它。然后,在未来,它可以用来回答相同的问题。整个格式都是错误的。 - DotNetRussell
我会按照您的要求进行集成,但实际上我并不完全同意您的观点。因为问题添加表单允许您在提问时直接插入答案,这正是因为您只想分享您自己找到的解决方案,而不是真正寻求帮助,只是想分享一个难以找到的解决方案。正如这里所建议的:http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/无论如何,我会像您正确建议的那样添加一些代码来测试。 - Leonardo Spina
1
嗨,安东尼,你是对的,现在看起来好多了。谢谢,再次道歉。 - Leonardo Spina
显示剩余2条评论
1个回答

3
虽然“PropertyChanged”事件会自动进行封送,但对CommandManager.InvalidateRequerySuggested()的调用则不会。
因此,为了解决这个问题,只需确保在Dispatcher线程上进行调用,具体如下:
    private void DataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Ready":
                var d = Application.Current.Dispatcher;
                if (d.CheckAccess())
                    CommandManager.InvalidateRequerySuggested();
                else
                    d.BeginInvoke((Action)(() => { CommandManager.InvalidateRequerySuggested(); }));
                break;
        }
    }

在依赖于异步任务触发的属性更改时要小心。

我希望这可以帮助你们节省一些工作日...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接