代码之家  ›  专栏  ›  技术社区  ›  penCsharpener

Winforms上的MVVM:可以悬停禁用的按钮(使用异步/等待)

  •  0
  • penCsharpener  · 技术社区  · 7 年前

    由于我不熟悉WPF,我想尝试在Winforms中使用MVVM模式。我有一个表单,点击一个按钮就会启动一个异步方法,该方法通过API检索MSG。

    该操作正在我的视图模型中运行,其中还有控制某些UI行为的属性。视图模型通过Fody Weaver属性更改实现INotifyOnPropertyChanged,并具有一个BindingSource,该BindingSource将视图模型本身作为数据源接收。

    BindingSource bsViewModel = new BindingSource();
    bsViewModel.DataSource = this; // this being the view model the BindingSource lives in
    

    VM属性通过数据绑定绑定到控件的属性。Add()除此故障外,此操作正常。按钮文本已正确更新,但只有当我将鼠标悬停在按钮上(或调用Refresh())时,按钮的颜色才会正确更新;在用户控件中放置这些按钮的位置)。

    一位同事对WinForms中的BindingSource、MVVM使用了相同的技术,但使用了BackgroundWorker而不是async Wait。他没有那个问题。他的按钮正确禁用,禁用时不显示悬停效果。

    这个半禁用按钮的原因是什么?

    disabled buttons have hover effect

    2 回复  |  直到 7 年前
        1
  •  1
  •   Luis    7 年前

    简单的ReactiveUI示例:

    1.-创建新的Winforms项目(.net 4.5)

    2.-添加reactiveui winforms nuget包(7.4.0)

    3.-添加一个名为“MainViewModel”的新类,并添加以下引用:

    using System;
    using System.Reactive;
    using System.Threading.Tasks;
    using ReactiveUI;
    

    4.-这是类别代码:

    public class MainViewModel : ReactiveObject
    {
        private bool canExecute;
        private string textButton;
    
        public MainViewModel()
        {
            this.CanExecute = true;
            this.TextButton = "Press me"
            // the command creation, this.WhenAnyValue(x => x.CanExecute) is an observable that will change the enabled/disabled command when CanExecute changes
            this.AsyncProcess = ReactiveCommand.CreateFromTask(AsyncProcessImpl, this.WhenAnyValue(x => x.CanExecute));
        }
    
        public bool CanExecute
        {
            get { return this.canExecute; }
            set { this.RaiseAndSetIfChanged(ref this.canExecute, value); }
        }
    
       public string TextButton
       {
            get { return textButton; }
            set { this.RaiseAndSetIfChanged(ref textButton, value); }
       }
    
        public ReactiveCommand<Unit, Unit> AsyncProcess { get; } 
    
        private async Task AsyncProcessImpl()
        {
            // changing CanExecute to false will disable the button
            this.CanExecute = false;
            this.TextButton = "Wait..."
            await Task.Delay(TimeSpan.FromSeconds(5)); // just for testing
            // CanExecute back to true, the button will be enabled
            this.CanExecute = true;
            this.TextButton = "Press me";
        }
    }
    

    5.-打开Form1表单并添加一个名为“btnStart”的按钮。

    6.-打开Form1代码隐藏并添加对ReactiveUI的引用:

    using ReactiveUI;
    

    7.-将类别更改为:

    public partial class Form1: Form, IViewFor<MainViewModel>
    {
        public Form1()
        {
            InitializeComponent();
    
            // ViewModel initialization
            this.ViewModel = new MainViewModel();
    
            // binding creation when the form is activated
            this.WhenActivated(dispose => {
                // the command binding will be disposed when the form is deactivated, no memory leaks
                dispose(this.BindCommand(this.ViewModel, x => x.AsyncProcess, x => x.btnStart));
                dispose(this.OneWayBind(this.ViewModel, x => x.TextButton, x => x.btnStart.Text);
            });
        }
    
        public MainViewModel ViewModel { get;  set; }
    
        object IViewFor.ViewModel
        {
            get { return this.ViewModel; }
            set { this.ViewModel = value as MainViewModel; }
        }
    }
    

    8.-运行应用程序

    编辑:如果要更改引发命令的事件,只需在BindCommand方法中添加最后一个参数:

    dispose(this.BindCommand(this.ViewModel, x => x.AsyncProcess, x => x.btnStart, "TheEventNameYouWant"));
    

    您可以将命令绑定到任何具有Disable属性的控件,只需使用将引发命令的事件的名称即可。

        2
  •  0
  •   penCsharpener    7 年前

    问题出在WinForms本身。它只是不支持MVVM模式,我知道这一点,但无论如何都想尝试一下。

    我尝试了使用后台工作程序的异步方法。按钮的问题也是一样的。只有在异步方法内/从其他线程更改视图模型属性时,才会发生这种情况。

    我注意到的另一件事是,使用视图模型属性和数据绑定根本无法控制可见性。只有通过进度报告的方式设置了启用的属性,启用的属性才起作用。这是一个微不足道的,但我想学习WPF无论如何。。。

    更新: 我在ReactiveUI之外找到的唯一解决方法是使用 IProgress<Action> . 在异步方法中,我在 progress.Report(new Action() => { ... }) 剩下的由数据绑定完成。此外,需要在ViewModel中实现INotifyPropertyChanged(nuget PropertyChanged.Fody将在这里提供帮助)。