在进程之前禁用WPF按钮,并在进程完成后在WPF MVVM中启用

时间:2019-06-05 13:57:20

标签: c# wpf mvvm

我有一个进行一些处理的按钮,我需要在过程开始之前将其禁用,并在过程完成之后将其启用。我需要以mvvm模式完成此操作。

MainWindow.xaml

<Window x:Class="ButtonCommandBindingMVVM.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:ButtonCommandBindingMVVM"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.Resources>
    <local:ViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="40"/>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="40"/>
    </Grid.RowDefinitions>
    <Button Grid.Row="1"
            x:Name="Button1"
            Width="100"
            Height="27"
            Content="Say Hello"
            Command="{Binding Button1_ClickCommand, Source={StaticResource vm}}"
            />
    <Button Grid.Row="2"
            x:Name="Button2"
            Width="100"
            Height="27"
            Content="Say Welcome"
            Command="{Binding Button2_ClickCommand, Source={StaticResource vm}}"
            />

</Grid>

Command.cs 这是中继命令类。

class Command : ICommand
{
    Action<object> ExecuteMethod;

    Func<object, bool> CanExecuteMethod;

    public Command(Action<object> ExecuteMethod, Func<object, bool> CanExecuteMethod)
    {
        this.ExecuteMethod = ExecuteMethod;
        this.CanExecuteMethod = CanExecuteMethod;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        ExecuteMethod(parameter);
    }

    public event EventHandler CanExecuteChanged;

}

ViewModel.cs

public class ViewModel
{
    public ICommand Button1_ClickCommand { get; set; }
    public ICommand Button2_ClickCommand { get; set; }

    public ViewModel()
    {
        Button1_ClickCommand = new Command(ExecuteMethodButton1_ClickCommand, CanExecuteMethodButton1_ClickCommand);
        Button2_ClickCommand = new Command(ExecuteMethodButton2_ClickCommand, CanExecuteMethodButton2_ClickCommand);
    }

    private bool CanExecuteMethodButton1_ClickCommand(object parameter)
    {
        return true;
    }

    private void ExecuteMethodButton1_ClickCommand(object parameter)
    {
        await Task.Run(() =>
        {
            Thread.Sleep(5000);
        });
        MessageBox.Show("Hello");
    }

    private bool CanExecuteMethodButton2_ClickCommand(object parameter)
    {
        return true;
    }

    private void ExecuteMethodButton2_ClickCommand(object parameter)
    {
        MessageBox.Show("Welcome");
    }
}

禁用Button1并在线程休眠5秒钟后启用它。

2 个答案:

答案 0 :(得分:0)

尝试以异步方式运行的ICommand的这种实现。

public class perRelayCommandAsync : ObservableObject, ICommand
{
    private readonly Func<Task> _execute;
    private readonly Func<bool> _canExecute;

    public perRelayCommandAsync(Func<Task> execute) : this(execute, () => true ) { }

    public perRelayCommandAsync(Func<Task> execute, Func<bool> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    private bool _isExecuting;

    public bool IsExecuting
    {
        get => _isExecuting;
        set
        {
            if(Set(nameof(IsExecuting), ref _isExecuting, value))
                RaiseCanExecuteChanged();
        }
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter) => !IsExecuting 
                                                && (_canExecute == null || _canExecute());

    public async void Execute(object parameter)
    {
        if (!CanExecute(parameter))
            return;

        IsExecuting = true;
        try
        {
            await _execute().ConfigureAwait(true);
        }
        finally
        {
            IsExecuting = false;
        }
    }

    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

ObservableObject来自MVVMLight,但是您可以将其替换为任何INotifyPropertyChanged实现。 _canExecute标志将导致在执行任务时自动禁用按钮。

您的示例将成为

Button1_ClickCommand = new perRelayCommandAsync(()=>Task.Delay(TimeSpan.FromSeconds(5));

不要在异步代码中使用Thread.Sleep()-您只是在阻塞执行线程。

有关WPF命令的更广泛讨论,请查看我的blog post

答案 1 :(得分:0)

异步ICommand的一种非常简单的实现-仅在异步执行过程中禁用命令目标-看起来像这样:

public class AsyncCommand : ICommand
{
    private readonly Func<object, Task> execute;
    private bool canExecute = true;

    public event EventHandler CanExecuteChanged;

    public AsyncCommand(Func<object, Task> execute)
    {
        this.execute = execute;
    }

    public bool CanExecute(object parameter)
    {
        return canExecute;
    }

    public async void Execute(object parameter)
    {
        if (canExecute)
        {
            canExecute = false;
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);

            try
            {
                await execute(parameter);
            }
            finally
            {
                canExecute = true;
                CanExecuteChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }
}

您可以使用以下命令对其进行测试:

public ICommand TestCommand { get; }

private Task TestMethod(object p)
{
    return Task.Delay(1000);
}

...
TestCommand = new AsyncCommand(TestMethod);

当然也可以将execute方法声明为async

private async Task TestMethod(object p)
{
    // do something before awaiting a long running task

    await Task.Run(() =>
    {
        // a long running task
    });

    // do something after awaiting a long running task
}