ReactiveUI 命令取消订阅
ReactiveUI command unsubscription
我有 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();
}
}
第一个视图模型:
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
?
P.S。也许我不应该在 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();
}
}
在您的视图中使用 WhenAcitvated:
在页面的构造函数中(必须是 IViewFor):
this.WhenActivated(
disposables =>
{
ViewModel.Command.Subscribe(...).(disposables);
});
subscribe 方法return一个IDisposable,你应该存储它并在你想取消订阅时释放它。
我想我知道你想做什么。如果我是对的,您正在尝试创建某种向导或显示一个视图模型的东西,该视图模型可以移动到下一个,然后可以先移动?如果是这种情况,那么也许您想重新考虑自己的做法。
例如,为什么不在包含两个视图模型的视图模型中管理前后按钮,并让子视图模型仅在首次需要时创建一次。然后,您的导航按钮可以根据逻辑确定当前打开和启用的视图模型。
在 Wizard 类型的场景中,考虑将您的子视图模型基于可能具有 属性 或名为 CanMoveNext() 和 CanMovePrior() 之类的方法的 Base 视图模型。在内部,如果准备就绪,这可以 return 为真。容易多了。
感谢@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();
}
}
我还建议通过激活和停用 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;
}
}
我有 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();
}
}
第一个视图模型:
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
?
P.S。也许我不应该在 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();
}
}
在您的视图中使用 WhenAcitvated: 在页面的构造函数中(必须是 IViewFor):
this.WhenActivated(
disposables =>
{
ViewModel.Command.Subscribe(...).(disposables);
});
subscribe 方法return一个IDisposable,你应该存储它并在你想取消订阅时释放它。
我想我知道你想做什么。如果我是对的,您正在尝试创建某种向导或显示一个视图模型的东西,该视图模型可以移动到下一个,然后可以先移动?如果是这种情况,那么也许您想重新考虑自己的做法。 例如,为什么不在包含两个视图模型的视图模型中管理前后按钮,并让子视图模型仅在首次需要时创建一次。然后,您的导航按钮可以根据逻辑确定当前打开和启用的视图模型。 在 Wizard 类型的场景中,考虑将您的子视图模型基于可能具有 属性 或名为 CanMoveNext() 和 CanMovePrior() 之类的方法的 Base 视图模型。在内部,如果准备就绪,这可以 return 为真。容易多了。
感谢@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();
}
}
我还建议通过激活和停用 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;
}
}