disable a WPF button before a process and enable a

2019-12-16 17:20发布

I have a button that does some processing and I need to disable it before the process starts and enable it after the process completes. I need to accomplish this in mvvm pattern.

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 This is a relay command class.

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

Disable the Button1 and enable it after the thread sleeps for 5 secs.

标签: c# wpf mvvm
2条回答
Rolldiameter
2楼-- · 2019-12-16 17:41

Try this implementation of ICommand, that runs in an async manner.

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 is from MVVMLight, but you can replace this with any INotifyPropertyChanged implementation. The _canExecute flag will cause the button to automatically be disabled while the Task is being executed.

Your example would then become

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

Don't use Thread.Sleep() in async code - you're just blocking the executing thread.

For a wider discussion about WPF commands look at my blog post.

查看更多
Rolldiameter
3楼-- · 2019-12-16 17:51

An extremely simple implementation of an async ICommand - that only disables the command target during its asynchronous execution - might look like this:

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

You may test it with a command like this:

public ICommand TestCommand { get; }

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

...
TestCommand = new AsyncCommand(TestMethod);

The execute method may of course also be declared 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
}
查看更多
登录 后发表回答