3

次のクラスがあるとします。

public class Person : ReactiveObject, IEditableObject
{
    private string name;
    private string nameCopy;

    public string Name
    {
        get { return this.name; }
        set { this.RaiseAndSetIfChanged(ref this.name, value); }
    }

    public void BeginEdit()
    {
        this.nameCopy = this.name;
    }

    public void CancelEdit()
    {
        this.name = this.nameCopy;
    }

    public void EndEdit()
    {
    }
}

ここで、変更が にコミットUnitれるたびに「ティック」する監視可能なシーケンス (の) を作成したいとします。つまり、と の呼び出しとそれに続くの呼び出しの間に発生する への変更のみを考慮します。への呼び出し前の変更は無視され、シーケンスはティックされません。NameNameBeginEditEndEditCancelEdit

Rxでこれを行う方法を理解するのに苦労しています。BeginEdit/EndEdit呼び出しのウィンドウ中に変更が発生したかどうかを知るために、パイプラインのどこかで状態が必要になるようです。すべてにタイムスタンプを付けてタイムスタンプを比較できると思いますが、それは厄介なハックのようです。

Subject専用の編集アクションを使用して、かなり近づきましたObservable.Merge

public class Person : ReactiveObject, IEditableObject
{
    private readonly Subject<EditAction> editActions;
    private readonly IObservable<Unit> changedDuringEdit;
    private string name;
    private string nameCopy;

    public Person()
    {
        this.editActions = new Subject<EditAction>();

        var nameChanged = this.ObservableForProperty(x => x.Name).Select(x => x.Value);
        var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
        var editCommitted = this.editActions.Where(x => x == EditAction.End);

        this.changedDuringEdit = nameChanged
            .Buffer(editBeginning, _ => editCommitted)
            .Where(x => x.Count > 0)
            .Select(_ => Unit.Default);
    }

    public IObservable<Unit> ChangedDuringEdit
    {
        get { return this.changedDuringEdit; }
    }

    public string Name
    {
        get { return this.name; }
        set { this.RaiseAndSetIfChanged(ref this.name, value); }
    }

    public void BeginEdit()
    {
        this.editActions.OnNext(EditAction.Begin);
        this.nameCopy = this.name;
    }

    public void CancelEdit()
    {
        this.editActions.OnNext(EditAction.Cancel);
        this.Name = this.nameCopy;
    }

    public void EndEdit()
    {
        this.editActions.OnNext(EditAction.End);
    }

    private enum EditAction
    {
        Begin,
        Cancel,
        End
    }
}

ただし、いくつかの変更がキャンセルされてから 1 つがコミットされた場合、オブザーバブルはコミット時に数回ティックします (前のキャンセルごとに 1 回、コミットごとに 1 回)。List<Unit>実際には必要のないを取得したという事実は言うまでもありません。ある意味では、これでも私のユース ケースは満足できますが、私の好奇心やコードの美的センスは満足できません。

Joinこれをかなりエレガントに解決する必要があると思います:

var nameChanged = this.ObservableForProperty(x => x.Name).Select(_ => Unit.Default);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
var editCancelled = this.editActions.Where(x => x == EditAction.Cancel);
var editCancelledOrCommitted = editCancelled.Merge(editCommitted);

this.changedDuringEdit = editBeginning
    .Join(nameChanged, _ => editCancelledOrCommitted, _ => editCancelledOrCommitted, (editAction, _) => editAction == EditAction.End)
    .Where(x => x)
    .Select(_ => Unit.Default);

しかし、これもうまくいきません。にJoin登録してeditCancelledOrCommittedいないようです。理由がわかりません。

これをきれいに行う方法はありますか?

4

2 に答える 2

3

これが私がそれを行う方法です:

IObservable<Unit> beginEditSignal = ...;
IObservable<Unit> commitSignal = ...;
IObservable<Unit> cancelEditSignal = ...;
IObservable<T> propertyChanges = ...;


// this will yield an array after each commit
// that has all of the changes for that commit.
// nothing will be yielded if the commit is canceled
// or if the changes occur before BeginEdit.
IObservable<T[]> commitedChanges = beginEditSignal
    .Take(1)
    .SelectMany(_ => propertyChanges
        .TakeUntil(commitSignal)
        .ToArray()
        .Where(changeList => changeList.Length > 0)
        .TakeUntil(cancelEditSignal))
    .Repeat();


// if you really only want a `Unit` when something happens
IObservable<Unit> changeCommittedSignal = beginEditSignal
     .Take(1)
     .SelectMany(_ => propertyChanges
         .TakeUntil(commitSignal)
         .Count()
         .Where(c => c > 0)
         .Select(c => Unit.Default)
         .TakeUntil(cancelEditSignal))
     .Repeat();
于 2013-08-28T16:35:11.990 に答える
1

あなたはタイミングの問題を抱えていますが、それはまだ明確にされていないと思います。いつ変化が起こることを望んでいますか?

  1. それらが発生したときのいずれか
  2. コミットが発生したら

1) の明確で明白な問題は、変更がコミットされるかどうかがわからないということです。IMO、これはオプション 2 のみを残します)。変更がキャンセルされた場合、イベントは発生しません。

次の質問は、それぞれの変更を提起したいですか? すなわち。プロセスのために

[Begin]-->[Name="fred"]-->[Name="bob"]-->[Commit]

これは、コミットが行われたときに 1 つまたは 2 つのイベントを発生させる必要がありますか? token type のみをプッシュしUnitているため、2 つの値をプッシュするのは冗長に思えます。これにより、が実行され、値が変更されたUnitときに値をプッシュしたいだけだと思う​​ようになりました。EndEdit()

これにより、非常に単純な実装が残ります。

public class Person : ReactiveObject, IEditableObject
{
    private readonly ISubject<Unit> changedDuringEdit = new Subject<Unit>();
    private string name;
    private string nameCopy;


    public string Name
    {
        get { return this.name; }
        set { this.RaiseAndSetIfChanged(ref this.name, value); }
    }

    public void BeginEdit()
    {
        this.nameCopy = this.name;
    }

    public void CancelEdit()
    {
        this.name = this.nameCopy;
    }

    public void EndEdit()
    {
        if(!string.Equals(this.nameCopy, this.name))
        {
            changedDuringEdit.OnNext(Unit.Default);
        }
    }

    public IObservable<Unit> ChangedDuringEdit
    {
        get { return this.changedDuringEdit.AsObservable(); }
    }
}

これはあなたが探しているものですか?そうでない場合、私が見逃している複雑さを理解するのを手伝ってもらえますか? もしそうなら、私はこれを肉付けしたいと思っているので、Subjectsの使用をお勧めしませんでした:-)

于 2013-08-28T15:26:04.080 に答える