2013-08-28 162 views
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)認爲,「滴答」每當一個變化致力於Name。也就是說,我只關心NameBeginEdit的呼叫和對EndEdit的後續呼叫之間發生的更改。在調用CancelEdit之前的任何更改都應該被忽略,並且順序不應該勾選。

我正在努力讓自己的腦袋圍繞如何與Rx做到這一點。看來我需要在某個地方的流水線狀態,以便知道這個變化是否發生在BeginEdit/EndEdit調用窗口期間。我想我可以給所有的東西打上時間戳,比較時間戳,但這似乎是一個令人討厭的黑客攻擊。

我就八九不離十使用編輯操作的專用SubjectObservable.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 
    } 
} 

然而,如果一些變化被取消,然後一個承諾,可觀察到的蜱上犯了幾次(一次每個事先取消,並再次提交)。更何況我得到了一個我並不需要的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,原因我不明白。

任何人有任何想法如何去幹淨這個?

回答

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(); 
+0

尼斯布蘭登。這就像我認爲_you_想要的那樣:在「編輯」過程中發生的每個變化都會在您提交時發送給您。然而,看起來肯特只想要一個「單位」,不知道爲什麼。 –

+0

@LeeCampbell是的,我也有點困惑。我會添加一個變化來做到這一點。 – Brandon

+0

是的,我只是想讓一個單位發出變化信號 - 我不在乎具體發生了什麼變化,也沒有在編輯過程中更改了多少屬性。實際上,我的視圖模型代表配置,另一個視圖模型需要在配置發生任何更改時自刷新。我期待着嘗試一下。稍後我會這樣做。謝謝。 –

1

你有時間問題,我不認爲你已經闡明瞭;你希望什麼時候修改?

  1. 要麼因爲它們發生
  2. 一旦提交發生

1的清晰和明顯的問題)是你不知道的變化將被提交,爲什麼你會提高他們。國際海事組織,這隻剩下選項2)。如果更改被取消,則不會引發任何事件。

我的下一個問題是,你想每個提出的改變?即。爲這個過程

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

當提交時,這應該引發1或2個事件嗎?由於您只推送令牌類型Unit,推送兩個值似乎是多餘的。現在,這使我認爲只有在執行EndEdit()時,您只需要推一個Unit值,並且值已更改。

這給我們留下了一個痛苦簡單的實現:

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小號:-)

+0

感謝李。您已正確推斷我的要求:僅在提交時打勾,並且最好只顯示一次以指示事情已更改。但是,我的例子被簡化了:我有很多可以更改的屬性。因此,我想利用我現有的觀察數據,並以某種方式將它們連接在一起,以獲得一個觀察值,該觀察值在包含對任何屬性進行更改的提交時進行一次滴答。 –

+0

請有一個upvote,部分是爲了您的有用迴應,部分原因是因爲[神奇的書](http://www.introtorx.com/)讓我在Rx中獲得了一半的勝任能力。乾杯! –