仅在成功提交的窗口内观察更改

时间:2013-08-28 06:06:54

标签: c# .net system.reactive reactiveui

假设我有以下课程:

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),只要提交Name,就会“滴答”。也就是说,我只关心在调用Name和随后调用BeginEdit之间发生的EndEdit更改。在调用CancelEdit之前的任何更改都应该被忽略,序列不应该打勾。

我很难理解如何用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
    }
}

但是,如果取消了多个更改,然后提交了一个更改,则observable会在提交时多次滴答(每次事先取消一次,再次提交一次)。更不用说我得到一个我不需要的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

任何人都有任何想法如何干净利落地去做?

2 个答案:

答案 0 :(得分: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();

答案 1 :(得分:1)

你有一个时间问题,我认为你还没有表达出来;你什么时候希望改变??

  1. 或者发生
  2. 一旦提交发生
  3. 1)明显而明显的问题是你不知道是否会提交更改,那么为什么要提出这些更改。 IMO,这只留下选项2)。如果更改被取消,则不会引发任何事件。

    我的下一个问题是,您是否希望每次提出更改?即。为过程

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

    提交提交时是否应该引发1或2个事件?由于您只是推送令牌类型Unit,因此推送两个值似乎是多余的。现在这让我觉得你只想在执行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(); }
        }
    }
    

    这是你在找什么?如果没有,你能帮助我理解我所缺少的复杂性吗?如果是,那么我会热衷于将其充实,以便我不建议使用Subject s: - )