【问题标题】:ReactiveUI command unsubscriptionReactiveUI 命令退订
【发布时间】:2017-12-06 12:26:05
【问题描述】:

我有 2 个视图和 2 个视图模型:

第一个视图:

public partial class FirstView : Page
{
    FirstViewModel ViewModel;
    public FirstView()
    {
        ViewModel = new FirstViewModel();
        ViewModel.ShowSecondView.Subscribe(_ =>
        {              
            NavigationService.Navigate(new SecondView(ViewModel.ChildViewModel));
        });            

        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

第一个 ViewModel:

public class FirstViewModel
{
    SecondViewModel ChildViewModel;
    public ReactiveCommand<Unit, Unit> ShowSecondView { get; set; }
    public FirstViewModel()
    {
        ChildViewModel = new SecondViewModel();
        ShowSecondView = ReactiveCommand.Create(() => 
        {
             ChildViewModel.Reconfigure(...);  
        });          
    }        
}

第二个视图:

public partial class SecondView : Page
{
    SecondViewModel ViewModel;
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        ViewModel.GoBack.Subscribe(_ => 
        {
            DoSomethingHard();
            if(NavigationService != null)  NavigationService.GoBack();
        });
        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

第二个视图模型:

public class SecondViewModel
{
    public ReactiveCommand<Unit, Unit> GoBack { get; set; }
    public FirstViewModel()
    {
        VeryLongInitialization();
        GoBack = ReactiveCommand.Create(() => { });          
    }        
    public void Reconfigure(...)
    { ... }
}

所以,当我多次运行FirstViewModel.ShowSecondView 并多次运行SecondViewModel.GoBack 时,DoSomethingHard() 也会在每个创建的 SecondView 上执行多次。

为什么我要在FilstViewModel 中创建一次ChildViewModel?因为SecondViewModel 的创建需要很长时间。而且我不会每次都重新创建SecondViewModel,而只是重新配置它。

我的问题是如何在SecondView 中取消订阅ViewModel.GoBack.Subscribe

附:也许我不应该在FirstView 中重新创建SecondView,而是重新配置它以及SecondViewModel

更新 1(感谢 Julien Mialon)

我添加IDisposable goBackSubscr 并且它有效!我执行它是否正确?

public partial class SecondView : Page
{
    SecondViewModel ViewModel;
    IDisposable goBackSubscr;
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        goBackSubscr = ViewModel.GoBack.Subscribe(_ => 
        {
            DoSomethingHard();
            if(NavigationService != null)  NavigationService.GoBack();
            goBackSubscr.Dispose();
        });
        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

【问题讨论】:

    标签: c# .net wpf mvvm reactiveui


    【解决方案1】:

    在您的视图上使用WhenAcitvated: 在页面的构造函数中(必须是 IViewFor):

     this.WhenActivated(
         disposables =>
         {
             ViewModel.Command.Subscribe(...).(disposables);
         });
    

    【讨论】:

    • 我读过关于 IViewFor 的文章,但我不明白(绝对)
    【解决方案2】:

    subscribe 方法返回一个 IDisposable,你应该存储它并在你想取消订阅时释放它。

    【讨论】:

    • 我在问题中添加了 UPDATE1
    【解决方案3】:

    感谢@Krzysztof Skowronek

    我再次阅读了有关 IViewFor 并解决了我的问题

    public partial class SecondView : Page, IViewFor<SecondViewModel>
    {
        public SecondViewModel ViewModel { get; set; }
        object IViewFor.ViewModel { get => ViewModel; set { ViewModel = (SecondViewModel)value; } }
        public SecondView(SecondViewModel viewModel)
        {
            ViewModel = viewModel;
            this.WhenActivated(disposables =>
            {
                ViewModel.GoBack.Subscribe(_ =>
                    {
                        DoSomethingHard();
                        if (NavigationService != null) NavigationService.GoBack();
                    })
                    .DisposeWith(disposables);               
    
                this.WhenAnyValue(p => p.ViewModel)
                    .BindTo(this, x => x.DataContext)
                    .DisposeWith(disposables);
            });        
        InitializeComponent();            
        }        
    }
    

    【讨论】:

      【解决方案4】:

      我想我知道你想做什么。如果我是对的,您正在尝试创建某种向导或显示一个可以移动到下一个视图模型的东西,然后可以先移动?如果情况如此,那么也许你想重新考虑你是如何做到的。 例如,为什么不管理包含两个视图模型的视图模型中的前后按钮,并让子视图模型只在第一次需要时创建一次。然后,您的导航按钮可以根据逻辑确定当前打开和启用哪些视图模型。 在向导类型场景中,考虑将您的子视图模型基于可能具有称为 CanMoveNext() 和 CanMovePrior() 之类的属性或方法的基本视图模型。在内部,如果它准备好了,它可以返回 true。容易得多。

      【讨论】:

      • 没有?这不是巫师。它是带有页面导航的单窗口应用程序。例如,FirstViewModel 是项目列表,SecondViewModel 是项目的编辑器
      【解决方案5】:

      我还建议通过 ViewModel 的激活和停用来启用和禁用命令。

      使用小助手:

          public static class DisabledCommand
          {
              public static readonly ReactiveCommand<Unit, Unit> Instance
                  = ReactiveCommand.Create<Unit, Unit>(_ => Unit.Default, Observable.Return(false));
          }
      

      你可以这样做:

      public sealed class MyViewModel : ReactiveObject, IActivateableViewModel
      {
      
          public MyViewModel()
          {
              this.WhenActivated(d =>
              {
                  // Observable that returns boolean to define if button is enabled
                  // ie. Observable.Create() or this.WhenAnyValue(...)
                  var canDoSomething = ...;
      
                  // enable command when VM gets activated
                  DoSomething = ReactiveCommand.Create<Unit, Unit>(_ => Unit.Default, canDoSomething);
      
                  // disable command when VM gets disabled
                  Disposable.Create(() => DoSomething = DisabledCommand.Instance).DisposeWith(d);
      
                  DoSomething
                      .Select(_ => this) // select ViewModel
                      .Do(vm =>
                      { 
                          // ...
                      })
                      .ObserveOn(RxApp.MainThreadScheduler)
                      .ExecuteOn(RxApp.TaskPoolScheduler)
                      .Subscribe()
                      .DisposeWith(d);
              });
          }
      
          [Reactive] public ReactiveCommand<Unit, Unit> DoSomething { get; set; } = DisabledCommand.Instance;
          public ViewModelActivator Activator { get; } = new ViewModelActivator();
      }
      

      那么在你看来:

      public partial class MyView : Page, IViewFor<MyViewModel>
      {
          public MyView()
          {
              InitializeComponent();
      
              this.WhenActivated(d =>
              {
                  if(ViewModel == null) return;
      
                  IDisposable current = Disposable.Empty;
                  ViewModel.WhenAnyValue(vm => vm.DoSomething)
                      .Select(_ => ViewModel)
                      .Subscribe(vm => 
                      {
                          current.Dispose();
                          current = vm.DoSomething
                              .Select(_ => vm)
                              .Do(vm_ =>
                              {
                                  // do something on the UI
                              })
                              .ObserveOn(RxApp.MainThreadScheduler)
                              .SubscribeOn(RxApp.MainThreadScheduler)
                              .Subscribe();
                      })
                      .ObserveOn(RxApp.MainThreadScheduler)
                      .SubscribeOn(RxApp.MainThreadScheduler)
                      .Subscribe()
                      .DisposeWith(d);
      
                    Disposable.Create(() => current.Dispose()).DisposeWith(d);
              });
          }
      
          object IViewFor.ViewModel
          {
              get => ViewModel;
              set => ViewModel = value as MyViewModel;
          }
      
          public MyViewModel ViewModel
          {
              get => DataContext as MyViewModel;
              set => DataContext = value;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-15
        • 1970-01-01
        • 2013-01-11
        • 1970-01-01
        相关资源
        最近更新 更多