WPF: 使用命令绑定和线程控制按钮的启用/禁用状态出现问题

5
我有一个WPF应用程序,简单地包含一个按钮和一个文本框以显示一些输出内容。当用户单击按钮时,线程启动禁用按钮,将输出内容打印到文本框,然后线程停止(此时我希望按钮重新启用)。
应用程序似乎正确地禁用了按钮,并更新了文本框。但是,当线程完成时,它总是无法正确地重新启用按钮!有人能告诉我我做错了什么吗?
这是我的XAML片段:
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" HorizontalAlignment="Center" Command="{Binding ExecuteCommand}">E_xecute</Button>
    <Label Grid.Row="1" Content="Output Window:" HorizontalAlignment="Left"/>
    <TextBlock Grid.Row="2" Text="{Binding Output}"/>
</Grid>

这是我的ViewModel代码(我使用Josh Smith的MVVM设计):
public class WindowViewModel : ViewModelBase
{
    private bool _threadStopped;
    private RelayCommand _executeCommand;
    private string _output;

    public WindowViewModel()
    {
        _threadStopped = true;
    }

    public string Output { get { return _output; } set { _output = value; OnPropertyChanged("Output"); } }

    public ICommand ExecuteCommand
    {
        get
        {
            if (_executeCommand == null)
            {
                _executeCommand = new RelayCommand(p => this.ExecuteThread(p), p => this.CanExecuteThread); 
            }
            return _executeCommand;
        }
    }

    public bool CanExecuteThread
    {
        get
        {
            return _threadStopped;
        }
        set
        {
            _threadStopped = value;
        }
    }

    private void ExecuteThread(object p)
    {
        ThreadStart ts = new ThreadStart(ThreadMethod);
        Thread t = new Thread(ts);
        t.Start();
    }

    private void ThreadMethod()
    {
        CanExecuteThread = false;
        Output = string.Empty;
        Output += "Thread Started:  Is the 'Execute' button disabled?\r\n";
        int countdown = 5000;

        while (countdown > 0)
        {
            Output += string.Format("Time remaining: {0}...\r\n", countdown / 1000);
            countdown -= 1000;
            Thread.Sleep(1000);
        }
        CanExecuteThread = true;
        Output += "Thread Stopped:  Is the 'Execute' button enabled?\r\n";
    }
}
2个回答

2

您需要帮助WPF知道命令的可执行状态已更改。简单的方法是:

CommandManager.InvalidateRequerySuggested()

在CanExecute线程内部:

set
{
    _threadStopped = value;
    CommandManager.InvalidateRequerySuggested()
}

编辑(现在我有时间了):实际问题可能是您没有在CanExecuteThread属性更改时进行通知。它应该引发PropertyChanged,以便WPF检测到更改:

public bool CanExecuteThread
{
    get { return _threadStopped; }
    set
    {
        if (_threadStopped != value)
        {
            _threadStopped = value;
            this.OnPropertyChanged(() => this.CanExecuteThread);
        }
    }
}

上面的内容假设你的ViewModel基类有一个OnPropertyChanged方法。
话虽如此,我也想指出,你可以通过简单地使用一个BackgroundWorker来简化事情:
public class WindowViewModel : ViewModel
{
    private readonly BackgroundWorker backgroundWorker;

    public WindowVieWModel()
    {
        backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += delegate
        {
            // do work here (what's currently in ThreadMethod)
        };
        backgroundWorker.RunWorkerCompleted += delegate
        {
            // this will all run on the UI thread after the work is done
            this.OnPropertyChanged(() => this.CanExecuteThread);
        };
    }

    ...

    public bool CanExecuteThread
    {
        get { !this.backgroundWorker.IsBusy; }
    }

    private void ExecuteThread(object p)
    {
        // this will kick off the work
        this.backgroundWorker.RunWorkerAsync();

        // this property will have changed because the worker is busy
        this.OnPropertyChanged(() => this.CanExecuteThread);
    }
}

你可以进一步重构代码,使其更加优雅,但你已经有了这个想法。

我按照你建议的方式放置了代码行,但是当线程完成时按钮仍然显示为禁用状态。只有当我将焦点放在窗口内的某个元素上(例如鼠标单击或快捷键)时,按钮才会重新启用。(注意:即使在你提出解决方案之前,我也遇到了这个问题)。如果有其他建议,将不胜感激。 - Tam Bui
谢谢,肯特!就是这个问题!我会把答案发布出来,让其他人看到解决方案。 - Tam Bui

0

这里是答案,正如Kent Boogaart所建议的那样,它是有效的。基本上,我必须通过将其放在Dispatcher invoke调用中,在UI线程上调用CommandManager.InvalidateRequerySuggested。还请注意,我能够摆脱CanExecuteThread属性上的Set访问器,因为它在此解决方案中不再需要。谢谢,Kent!

public class WindowViewModel : ViewModelBase
{
    private bool _threadStopped;
    private RelayCommand _executeCommand;
    private string _output;
    private Dispatcher _currentDispatcher;
    public WindowViewModel()
    {
        _threadStopped = true;
        _currentDispatcher = Dispatcher.CurrentDispatcher;
    }

    public string Output { get { return _output; } set { _output = value; OnPropertyChanged("Output"); } }

    public ICommand ExecuteCommand
    {
        get
        {
            if (_executeCommand == null)
            {
                _executeCommand = new RelayCommand(p => this.ExecuteThread(p), p => this.CanExecuteThread); 
            }
            return _executeCommand;
        }
    }

    private delegate void ZeroArgDelegate();

    public bool CanExecuteThread
    {
        get
        {
            return _threadStopped;
        }
    }

    private void ExecuteThread(object p)
    {
        ThreadStart ts = new ThreadStart(ThreadMethod);
        Thread t = new Thread(ts);
        t.Start();
    }

    private void ThreadMethod()
    {
        _threadStopped = false;
        Output = string.Empty;
        Output += "Thread Started:  Is the 'Execute' button disabled?\r\n";
        int countdown = 5000;

        while (countdown > 0)
        {
            Output += string.Format("Time remaining: {0}...\r\n", countdown / 1000);
            countdown -= 1000;
            Thread.Sleep(1000);
        }
        _threadStopped = true;
        _currentDispatcher.BeginInvoke(new ZeroArgDelegate(resetButtonState), null);
        Output += "Thread Stopped:  Is the 'Execute' button enabled?\r\n";
    }

    private void resetButtonState()
    {
        CommandManager.InvalidateRequerySuggested();
    }
}

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