在处理函数期间禁用控件

Disabling controls during processing a function

我在处理函数期间禁用 WPF 应用程序中的控件时遇到问题。这是一个通过串口发送数据的简单应用程序。当端口是 "listening" (SerialPort.ReadChar();) 我希望所有控件都去 gray/disable 它们。

这样:

private void startTransmissionButton_Click(object sender, RoutedEventArgs e)
    {
        ComboBox.IsEnabled = false;
        Button1.IsEnabled = false;
        Button2.IsEnabled = false;
        Button3.IsEnabled = false;

        SerialPort com = new SerialPort("COM1");

        com.Open();
        c = (char)com.ReadChar();
        com.Close();

        ComboBox.IsEnabled = true;
        Button1.IsEnabled = true;
        Button2.IsEnabled = true;
        Button3.IsEnabled = true;
    }

禁用似乎只在函数内部起作用,所以在 window 中实际上没有发生任何事情。当我在函数结束时取消启用时,所有控件都会变灰,但不是在 *.IsEnabled = false 指令的那一刻,而是在函数结束时。是我做错了什么,还是一切正常,这需要以不同的方式完成吗?

欢迎使用 Whosebug!

由于您的代码是同步的,因此它是阻塞的,因此您会得到这种行为。还需要考虑使用 Dispatcher 但幸运的是你没有遇到这样的问题。

建议:

  • 使用 ViewModel
  • 将其中的某些属性绑定到 enable/disable 您的 UI
  • 这样做可以分散关注点并简化您的总体工作

示例:禁用 UI 的 5 秒工作(非常简单!)

我代码中的兴趣点:

  • 通过将所有必须禁用的控件放在 StackPanel 中并绑定它的 IsEnabled 属性 模型的 IsAvailable 属性 我有效地简化了这个过程
  • 没有从代码隐藏修改控件
  • 视图(您的 window)只是呈现,您所有的逻辑都在一个模型中,该模型与您的 window 无关,可以在其他地方重复使用

XAML:

<Window x:Class="WpfApplication1.MainView"
        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:wpfApplication1="clr-namespace:WpfApplication1"
        Title="MainView"
        Width="525"
        Height="350"
        d:DataContext="{d:DesignInstance wpfApplication1:MainViewModel,
                                         d:IsDesignTimeCreatable=True}"
        mc:Ignorable="d">
    <Grid>
        <StackPanel>
            <Button Command="{Binding DoSomeWork}" Content="Do some long work" />
            <StackPanel IsEnabled="{Binding IsAvailable}">
                <CheckBox Content="Test control 1" />
                <RadioButton Content="Test control 2" />
            </StackPanel>
            <TextBlock Text="Overall progress:" />
            <ProgressBar Height="10" Value="{Binding CurrentProgress}" />
        </StackPanel>
    </Grid>
</Window>

代码隐藏:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainView : Window
    {
        public MainView()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }

    // put classes shown below here

}

您的模型:

internal class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {
        // set-up environment
        DoSomeWork = new DelegateCommand(DoSomeWorkExecute, DoSomeWorkCanExecute);
        IsAvailable = true;
    }

    public int CurrentProgress
    {
        get { return _currentProgress; }
        set
        {
            _currentProgress = value;
            OnPropertyChanged();
        }
    }

    #region IsAvailable

    private bool _isAvailable;
    private int _currentProgress;

    public bool IsAvailable
    {
        get { return _isAvailable; }
        set
        {
            _isAvailable = value;
            OnPropertyChanged();
        }
    }

    #endregion

    #region DoSomeWork

    public DelegateCommand DoSomeWork { get; private set; }

    private bool DoSomeWorkCanExecute(object arg)
    {
        return true;
    }

    private async void DoSomeWorkExecute(object o)
    {
        await Task.Run(() =>
        {
            IsAvailable = false;

            var steps = 20;
            var time = 5000;
            var length = time/steps;
            for (var i = 0; i < steps; i++)
            {
                Thread.Sleep(length);
                var currentProgress = (int) (((((double) i + 1)*length)/time)*100);
                CurrentProgress = currentProgress;
            }
            IsAvailable = true;
        });
    }

    #endregion

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

以及 DoSomeWork 的简单命令基础:

internal class DelegateCommand : ICommand
{
    private readonly Func<object, bool> _canExecute;
    private readonly Action<object> _execute;

    public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public DelegateCommand(Action<object> execute)
        : this(execute, s => true)
    {
    }

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

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

    public event EventHandler CanExecuteChanged;
}

待办事项

熟悉:

第一次使用这些概念时,您会感到有些痛苦,但随着时间的推移,您会发​​现这些是前进的道路,尤其是。使用 WPF。

如果您对我的回答感到满意,请将其标记为答案,否则如果您需要一些说明,请在下面添加评论,我或其他人会尽力提供进一步帮助。

请阅读 Aybe 提供的完整答案。遵循最佳实践总是好的。但是当涉及到小的快速测试项目时,我认为有时它可能有点矫枉过正。

如果您需要快速解决这个问题,那么您可以尝试使用以下方法:

private async void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
    ComboBox.IsEnabled = false;
    Button1.IsEnabled = false;
    Button2.IsEnabled = false;
    Button3.IsEnabled = false;

    await
        Task.Factory.StartNew(
            () =>
            {
                SerialPort com = new SerialPort("COM1");

                com.Open();
                c = (char)com.ReadChar();
                com.Close();
            }
        );

    ComboBox.IsEnabled = true;
    Button1.IsEnabled = true;
    Button2.IsEnabled = true;
    Button3.IsEnabled = true;
}

请注意,为 c 变量赋值发生在另一个线程中。

希望我的回答对您有所帮助。