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

时间:2018-07-31 00:13:38

标签: c# .net system.reactive reactiveui

我有一个ViewModel:

  • 列表ReactiveList
  • 单个MyObject
  • 一个IsBusy布尔值

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

在将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状态)变化。

2 个答案:

答案 0 :(得分: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,该方法在将两个可观察对象组合在一起后,会获取您想要的Lambda值。

CombineLatest()在两个Observable都发出一个值之前不会产生值,因此为什么listItemChanged的前面具有StartsWith(false)。

默认情况下,WhenAnyValue()始终将默认值(T)作为初始值发出,因此您不需要带有这些语句的StartsWith。

答案 1 :(得分:1)

这是避免使用O(n)复杂度的MyObjectList.Any()的另一种选择。该解决方案涉及更多,但有可能提高效率。它与Glenn的 CombineLatest 方法相同,不同之处在于他的 listItemChangedObs 可观察值的计算方式。此版本将列表中活动对象的总数保持运行状态。因此,每次触发ItemChanged时只需执行+1或-1。然后它只是检查它是否大于0。

try {
        connection= DaoConnection.getDeclicConnection();

        st = connection.createStatement();
        rs = st.executeQuery(request);

        boolean autoComit = connection.getAutoCommit();
        connection.setAutoCommit(false);
        //Treatment
        connection.commit();
        connection.setAutoCommit(autoComit);
 } catch (Exception e) {
        logger.error(e);

 } finally {
        //close All;
 }

这是我运行代码时通过的单元测试。基本上,它会检查ReactiveCommand的 CanExecute 更改状态的次数,以及每次变量之一更改时,状态为true还是false。

public MyViewModel()
{
    var itemChangedObs = this.WhenAnyValue(x => x.MyObject.Active);
    var isBusyObs = this.WhenAnyValue(x => x.IsBusy);

    // Recalculate the # of active objects each time ObjectList is reassigned.
    var activeListItemCountInitializedObs = this
        .WhenAnyValue(x => x.ObjectList)
        .Select(
            list =>
            {
                // Return 0 if ObjectList is null.
                return list == null ? Observable.Return(0) : list
                    .ToObservable()
                    // Otherwise, increment by 1 for each active object.
                    .Select(x => x.Active ? 1 : 0)
                    // We use Aggregate, which is a single value sequence, because
                    // we're only interested in the final result.
                    .Aggregate((acc, current) => acc + current);
            })
        // We no longer need the inner observable from the last time active item count
        // was initialized. So unsubscribe from that one and subscribe to this most recent one.
        .Switch();

    var activeListItemCountChangedObs = this
        .WhenAnyObservable(x => x.ObjectList.ItemChanged)
        .Where(x => x.PropertyName == "Active")
        // Increment or decrement the number of active objects in the list.
        .Select(x => x.Sender.Active ? 1 : -1);

    // An IObservable<bool> that signals if *any* of objects in the list are active.
    var anyListItemsActiveObs = activeListItemCountInitializedObs
        .Select(
            // Use the initialized count as the starting value for the Scan accumulator.
            initialActiveCount =>
            {
                return activeListItemCountChangedObs
                    .Scan(initialActiveCount, (acc, current) => acc + current)
                    // Return true if one or more items are active.
                    .Select(x => x > 0)
                    .StartWith(initialActiveCount > 0);
            })
        // ObjectList was completely reassigned, so the previous Scan accumulator is
        // no longer valid. So we "reset" it by "switching" to the new one.
        .Switch();

    var canRunCommand = itemChangedObs
        .CombineLatest(
            anyListItemsActiveObs,
            isBusyObs,
            (itemActive, listItemActive, isBusy) => (itemActive || listItemActive) && !isBusy);

    Save = ReactiveCommand.CreateFromObservable(() => Observable.Return(Unit.Default), canRunCommand);
}