使用 WhenAnyObservable 组合和过滤多个事件

我有一个 ViewModel:

  • 一个列表 ReactiveList<MyObject>

  • 单个 MyObject

  • IsBusy 布尔值

当 MyObject 列表或单个 MyObject 中的“Active”属性都不为真或在任何情况下 IsBusy 为真时,我想禁用该命令。

在将 IsBusy 添加到图片之前,我想出的最佳解决方案是:

var canSave = this.WhenAnyObservable(

    x => x.MyObjectsList.ItemChanged, x => x.MyObject.Changed)

    .Where(x => x.PropertyName == "Active")

    .Select(_ => MyObjectsList.Any(x => x.Active) || MyObject.Active);


SaveCommand = ReactiveCommand.Create(Save, canSave);

这个想法是每次 Active 属性更改时都会重新评估。不确定这是否是最好的解决方案(因此欢迎任何改进它的建议),但我绝对无法将 IsBusy 添加到图片中,以便在 IsBusy 时重新评估 Select 子句(包括 IsBusy 状态)变化。


哈士奇WWW
浏览 439回答 2
2回答

回首忆惘然

我认为您的答案可以简化。var itemChangedObs = this.WhenAnyValue(x => x.MyObject.Active);var isBusyObs = this.WhenAnyValue(x => x.IsBusy);var listItemChangedObs = this.WhenAnyObservable(x => x.MyObectsList.ItemChanged).Where(x => x.PropertyName == "Active").Select(_ => MyObjectsList.Any(x => x.Active)).StartsWith(false)var canRunCommand = itemChangedObs.CombineLatest(listItemChangedObs, isBusyObs, (itemActive, listItemActive, isBusy) => (itemActive || listItemActive) && !isBusy);这个版本本质上使用了一个 CombineLatest ,它在将两个 observable 组合在一起后采用您想要的值的 Lambda。在两个 Observables 都发出一个值之前,CombineLatest() 不会产生值,因此为什么 listItemChanged 在前面有一个 StartsWith(false)。默认情况下,WhenAnyValue() 将始终发出值的 default(T) 作为其初始值,因此您不需要这些语句的 StartsWith。

慕妹3242003

这是另一种替代方法,它避免了对复杂度为 O(n) 的 MyObjectList.Any() 的需要。此解决方案涉及更多,但具有提高效率的潜力。除了计算listItemChangedObs observable的方式外,它与 Glenn 的CombineLatest方法相同。此版本保持列表中活动对象数的运行总数。因此,每次触发 ItemChanged 时,它只需执行 +1 或 -1 操作。然后它只检查它是否大于 0。public MyViewModel(){&nbsp; &nbsp; var itemChangedObs = this.WhenAnyValue(x => x.MyObject.Active);&nbsp; &nbsp; var isBusyObs = this.WhenAnyValue(x => x.IsBusy);&nbsp; &nbsp; // Recalculate the # of active objects each time ObjectList is reassigned.&nbsp; &nbsp; var activeListItemCountInitializedObs = this&nbsp; &nbsp; &nbsp; &nbsp; .WhenAnyValue(x => x.ObjectList)&nbsp; &nbsp; &nbsp; &nbsp; .Select(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; list =>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Return 0 if ObjectList is null.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return list == null ? Observable.Return(0) : list&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .ToObservable()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Otherwise, increment by 1 for each active object.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Select(x => x.Active ? 1 : 0)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // We use Aggregate, which is a single value sequence, because&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // we're only interested in the final result.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Aggregate((acc, current) => acc + current);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })&nbsp; &nbsp; &nbsp; &nbsp; // We no longer need the inner observable from the last time active item count&nbsp; &nbsp; &nbsp; &nbsp; // was initialized. So unsubscribe from that one and subscribe to this most recent one.&nbsp; &nbsp; &nbsp; &nbsp; .Switch();&nbsp; &nbsp; var activeListItemCountChangedObs = this&nbsp; &nbsp; &nbsp; &nbsp; .WhenAnyObservable(x => x.ObjectList.ItemChanged)&nbsp; &nbsp; &nbsp; &nbsp; .Where(x => x.PropertyName == "Active")&nbsp; &nbsp; &nbsp; &nbsp; // Increment or decrement the number of active objects in the list.&nbsp; &nbsp; &nbsp; &nbsp; .Select(x => x.Sender.Active ? 1 : -1);&nbsp; &nbsp; // An IObservable<bool> that signals if *any* of objects in the list are active.&nbsp; &nbsp; var anyListItemsActiveObs = activeListItemCountInitializedObs&nbsp; &nbsp; &nbsp; &nbsp; .Select(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Use the initialized count as the starting value for the Scan accumulator.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; initialActiveCount =>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return activeListItemCountChangedObs&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Scan(initialActiveCount, (acc, current) => acc + current)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Return true if one or more items are active.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Select(x => x > 0)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .StartWith(initialActiveCount > 0);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })&nbsp; &nbsp; &nbsp; &nbsp; // ObjectList was completely reassigned, so the previous Scan accumulator is&nbsp; &nbsp; &nbsp; &nbsp; // no longer valid. So we "reset" it by "switching" to the new one.&nbsp; &nbsp; &nbsp; &nbsp; .Switch();&nbsp; &nbsp; var canRunCommand = itemChangedObs&nbsp; &nbsp; &nbsp; &nbsp; .CombineLatest(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; anyListItemsActiveObs,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isBusyObs,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (itemActive, listItemActive, isBusy) => (itemActive || listItemActive) && !isBusy);&nbsp; &nbsp; Save = ReactiveCommand.CreateFromObservable(() => Observable.Return(Unit.Default), canRunCommand);}这是我运行代码时通过的单元测试。它基本上检查 ReactiveCommand 的CanExecute更改状态的次数,以及它是真还是假,每次变量之一发生变化时。[Fact]public void TestMethod1(){&nbsp; &nbsp; var objectList = new ReactiveList<IMyObject>(&nbsp; &nbsp; &nbsp; &nbsp; initialContents: new[] { new MyObject(), new MyObject() },&nbsp; &nbsp; &nbsp; &nbsp; resetChangeThreshold: 0.3,&nbsp; &nbsp; &nbsp; &nbsp; scheduler: ImmediateScheduler.Instance);&nbsp; &nbsp; objectList.ChangeTrackingEnabled = true;&nbsp; &nbsp; IMyViewModel myViewModel = new MyViewModel&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; ObjectList = objectList,&nbsp; &nbsp; &nbsp; &nbsp; MyObject = new MyObject()&nbsp; &nbsp; };&nbsp; &nbsp; var canExecute = myViewModel.Save&nbsp; &nbsp; &nbsp; &nbsp; .CanExecute&nbsp; &nbsp; &nbsp; &nbsp; .CreateCollection(scheduler: ImmediateScheduler.Instance);&nbsp; &nbsp; Assert.Equal(1, canExecute.Count);&nbsp; &nbsp; Assert.False(canExecute[0]);&nbsp; &nbsp; myViewModel.ObjectList[0].Active = true;&nbsp; &nbsp; Assert.Equal(2, canExecute.Count);&nbsp; &nbsp; Assert.True(canExecute[1]);&nbsp; &nbsp; myViewModel.MyObject.Active = true;&nbsp; &nbsp; Assert.Equal(2, canExecute.Count);&nbsp; &nbsp; myViewModel.IsBusy = true;&nbsp; &nbsp; Assert.Equal(3, canExecute.Count);&nbsp; &nbsp; Assert.False(canExecute[2]);&nbsp; &nbsp; myViewModel.IsBusy = false;&nbsp; &nbsp; Assert.Equal(4, canExecute.Count);&nbsp; &nbsp; Assert.True(canExecute[3]);&nbsp; &nbsp; myViewModel.MyObject.Active = false;&nbsp; &nbsp; Assert.Equal(4, canExecute.Count);&nbsp; &nbsp; var object1 = new MyObject { Active = true };&nbsp; &nbsp; var object2 = new MyObject { Active = true };&nbsp; &nbsp; myViewModel.ObjectList = new ReactiveList<IMyObject>(&nbsp; &nbsp; &nbsp; &nbsp; initialContents: new[] { object1, object2 },&nbsp; &nbsp; &nbsp; &nbsp; resetChangeThreshold: 0.3,&nbsp; &nbsp; &nbsp; &nbsp; scheduler: ImmediateScheduler.Instance);&nbsp; &nbsp; Assert.Equal(4, canExecute.Count);&nbsp; &nbsp; object1 = new MyObject { Active = false };&nbsp; &nbsp; object2 = new MyObject { Active = false };&nbsp; &nbsp; myViewModel.ObjectList = new ReactiveList<IMyObject>(&nbsp; &nbsp; &nbsp; &nbsp; initialContents: new[] { object1, object2 },&nbsp; &nbsp; &nbsp; &nbsp; resetChangeThreshold: 0.3,&nbsp; &nbsp; &nbsp; &nbsp; scheduler: ImmediateScheduler.Instance);&nbsp; &nbsp; Assert.Equal(5, canExecute.Count);&nbsp; &nbsp; Assert.False(canExecute[4]);}
打开App,查看更多内容
随时随地看视频慕课网APP