2011-12-06 105 views
1

這一定是我的一個糟糕的谷歌搜索案例,因爲我知道我以前在wbe上看到了這個解決方案,但是我怎麼去實現一個擴展方法,它能夠將INotifyPropertyChanged.PropertyChanged事件轉換爲IObservable<Tuple<TProperty,TProperty>>,其中元組的值代表屬性的oldValue和newValue?Rx INotifyPropertyChanged to IObservable <Tuple <TProperty,TProperty >>

所以我想知道什麼是採取這樣的事情最好的辦法:credit for below to

public static IObservable<TProperty> ObservePropertyChanged<TNotifier, TProperty>(this TNotifier notifier, 
    Expression<Func<TNotifier, TProperty>> propertyAccessor, 
    bool startWithCurrent = false) 
    where TNotifier : INotifyPropertyChanged { 

    // Parse the expression to find the correct property name. 
    MemberExpression member = (MemberExpression)propertyAccessor.Body; 
    string name = member.Member.Name; 

    // Compile the expression so we can run it to read the property value. 
    var reader = propertyAccessor.Compile(); 

    var propertyChanged = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
     handler => (sender, args) => handler(sender, args), 
     x => notifier.PropertyChanged += x, 
     x => notifier.PropertyChanged -= x); 

    // Filter the events to the correct property name, then select the value of the property from the notifier. 
    var newValues = from p in propertyChanged 
        where p.EventArgs.PropertyName == name 
        select reader(notifier); 

    // If the caller wants the current value as well as future ones, use Defer() so that the current value is read when the subscription 
    // is added, rather than right now. Otherwise just return the above observable. 
    return startWithCurrent ? Observable.Defer(() => Observable.Return(reader(notifier)).Concat(newValues)) : newValues; 
} 

並將其轉換爲適合此簽名:

public static IObservable<Tuple<TProperty,TProperty>> ObservePropertyChanged<TNotifier, TProperty>(this TNotifier notifier, 
    Expression<Func<TNotifier, TProperty>> propertyAccessor, 
    bool startWithCurrent = false) 
    where TNotifier : INotifyPropertyChanged { 

    // Parse the expression to find the correct property name. 
    MemberExpression member = (MemberExpression)propertyAccessor.Body; 
    string name = member.Member.Name; 

    // Compile the expression so we can run it to read the property value. 
    var reader = propertyAccessor.Compile(); 

    var propertyChanged = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
     handler => (sender, args) => handler(sender, args), 
     x => notifier.PropertyChanged += x, 
     x => notifier.PropertyChanged -= x); 

    // Filter the events to the correct property name, then select the value of the property from the notifier. 
    var newValues = from p in propertyChanged 
        where p.EventArgs.PropertyName == name 
        select reader(notifier); 

    throw new NotImplementedException(); 
} 

編輯:我想出了一些似乎在嘗試許多不同的運營商後工作的東西。這是完成這個的正確方法嗎?有什麼我失蹤?

public static IObservable<Tuple<TProperty,TProperty>> ObserveValueChanged<TNotifier, TProperty>(this TNotifier notifier, 
    Expression<Func<TNotifier, TProperty>> propertyAccessor, 
    bool startWithCurrent = false) 
    where TNotifier : INotifyPropertyChanged { 
    var observable = ObservePropertyChanged(notifier, propertyAccessor, startWithCurrent); 

    return observable.Scan(new Tuple<TProperty, TProperty>(default(TProperty), default(TProperty)), 
        (acc, p) => new Tuple<TProperty, TProperty>(acc.Item2, p)); 

} 

編輯:我納入基甸的解決方案具有以下落得:

public static IObservable<Tuple<TProperty, TProperty>> ObserveValueChanged2<TNotifier, TProperty>(this TNotifier notifier, 
    Expression<Func<TNotifier, TProperty>> propertyAccessor, 
    bool startWithCurrent = false) 
    where TNotifier : INotifyPropertyChanged { 

    // Compile the expression so we can run it to read the property value. 
    var reader = propertyAccessor.Compile(); 

    var newValues = ObservePropertyChanged(notifier, propertyAccessor, false); 
    if (startWithCurrent) { 
     var capturedNewValues = newValues; //To prevent warning about modified closure 
     newValues = Observable.Defer(() => Observable.Return(reader(notifier)) 
           .Concat(capturedNewValues)); 
    } 

    return Observable.Create<Tuple<TProperty, TProperty>>(obs => { 
     Tuple<TProperty, TProperty> oldNew = null; 
     return newValues.Subscribe(v => { 
       if (oldNew == null) { 
        oldNew = Tuple.Create(default(TProperty), v); 
       } else { 
        oldNew = Tuple.Create(oldNew.Item2, v); 
        obs.OnNext(oldNew); 
       } 
      }, 
      obs.OnError, 
      obs.OnCompleted); 
    }); 
} 

附: 我最終偶然發現了我目前的解決方案,但我不想違反任何針對SO的禮儀,我應該添加一個答案還是關閉該問題(我不想刪除,因爲這可能稍後會有用)?我仍然不確定這是做這個的最佳方式。

+1

INotifyPropertyChanged不允許你訪問舊值。我不認爲這個事件適合你正在嘗試做的事 – cadrell0

+0

我記得有一個關於使用ReplaySubject來捕獲值的討論。所以我認爲我可以以某種方式捕捉初始值,然後始終顯示舊值和當前/新值的交錯窗口。那有意義嗎?我只是不知道如何在Rx中做到這一點。 – Damian

+0

我會創建一個自定義事件,讓生活變得輕鬆。 – cadrell0

回答

3

如果你想堅持現有的運營商,Zip連同Skip可能會最接近你的需要。我可能會自己寫這個(拿起你扔NotImplemented的地方):

if (startWithCurrent) 
{ 
    newValues = Observable.Defer(() => Observable.Return(reader(notifier)) 
          .Concat(newValues)); 
} 

return Observable.Create<Tuple<TProperty, TProperty>>(obs => 
    { 
     Tuple<TProperty, TProperty> oldNew = null; 
     return newValues.Subscribe(v => 
      { 
       if (oldNew == null) 
       { 
        oldNew = Tuple.Create(default(TProperty), v); 
       } 
       else 
       { 
        oldNew = Tuple.Create(oldNew.Item2, v); 
        obs.OnNext(oldNew); 
       } 
      }, 
      obs.OnError, 
      obs.OnCompleted); 
    }); 
+0

謝謝。像這樣的東西正是我所期待的。 – Damian

相關問題