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

C# Winforms上的MVVM:禁用的按钮可以悬停(使用异步/等待),c#,winforms,mvvm,async-await,C#,Winforms,Mvvm,Async Await,因为我不熟悉WPF,所以我想尝试在Winforms中使用MVVM模式。我有一个表单,点击一个按钮就会启动一个异步方法,通过API检索MSG 该操作正在我的视图模型中运行,其中还有控制某些UI行为的属性。视图模型通过Fody Weaver Property Changed实现InotifyOnProperty Changed,并具有一个BindingSource,该BindingSource将视图模型本身作为数据源接收 BindingSource bsViewModel = new Binding

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

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

BindingSource bsViewModel = new BindingSource();
bsViewModel.DataSource = this; // this being the view model the BindingSource lives in
VM属性通过DataBinding.Add()绑定到控件的属性。除此故障外,此操作正常。按钮文本已正确更新,但只有当我将鼠标悬停在按钮上(或在放置这些按钮的用户控件中调用Refresh();时,按钮的颜色才会正确更新

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

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


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

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

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


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

一个简单的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";
    }
}
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; }
    }
}

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

我还在Winforms中实现了MVVM,而没有您遇到的问题。。。您可以发布一个小的代码示例,这样我们就可以重现您的问题……您可以在Winforms中完美地实现MVVM,可能您的实现还不够好。试试ReactiveUI,使用MVVM和ReactiveUI的我的Winforms项目运行良好。我可以绑定启用的和可见的属性而不会出现问题。而您正在使用异步方法更新这些属性,是吗?异步和同步方法。好的,那么,与其投票否决我的答案(这对于没有反应UI的WinForms实际上是正确的),您是否可以提供自己的答案,并提供工作代码示例以支持您的声明!我对使用ReactiveUI很感兴趣,但他们的网站在WinForms方面似乎并不完整。我没有投票反对你;)我将尝试将一个示例上载到github。这非常有效。感谢您提供了广泛的示例。但我从未使用过可观测数据,所以如果您有更多指向样本的链接,那将非常有用!例如,我想知道如何绑定到按钮文本。
BindCommand
似乎会自动引发Click事件并更改按钮的Enabled属性。我甚至可以说哪些属性和事件是绑定的吗?请检查编辑。可观察性很好,只需将IEnumerable(包括linq)想象成pull集合,将IObservable想象成push集合。
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; }
    }
}
dispose(this.BindCommand(this.ViewModel, x => x.AsyncProcess, x => x.btnStart, "TheEventNameYouWant"));