2011-01-18 7 views
7

मैं एक एमवीवीएम ढांचे में आरएक्स का उपयोग करने में देख रहा हूं। यह विचार है कि दृश्य मॉडल में डेटा को प्रोजेक्ट करने के लिए इन-मेमोरी डेटासेट पर 'लाइव' LINQ क्वेरीज़ का उपयोग करना है।IObservable (Rx) का उपयोग INOTifyCollection के रूप में MVVM के लिए बदलें प्रतिस्थापन के रूप में?

पहले यह INotifyPropertyChanged/INotifyCollectionChanged और CLINQ नामक एक ओपन सोर्स लाइब्रेरी के उपयोग के साथ संभव हो गया है। आरएक्स और आईओब्सर्वेबल के साथ संभावित स्रोत स्रोतों के माध्यम से स्रोत मॉडल से बदलती घटनाओं को प्रसारित करने के लिए विषय वर्गों का उपयोग करके अधिक घोषणात्मक व्यू मॉडेल में जाना है। IOervervable से नियमित डाटाबेसिंग इंटरफेस में एक रूपांतरण अंतिम चरण के लिए आवश्यक होगा।

समस्या यह है कि आरएक्स अधिसूचना का समर्थन नहीं करता है कि धारा से एक इकाई को हटा दिया गया है। नीचे उदाहरण
कोड एक पीओसीओ दिखाता है जो क्षेत्र राज्य के लिए व्यवहारकर्ता वर्ग का उपयोग करता है। कोड इन इकाइयों का संग्रह बनाने के लिए चला जाता है और फ़िल्टर स्ट्रीम को एक साथ मर्ज करने के लिए कंसैट का उपयोग करता है। इसका मतलब है कि पीओसीओ में किए गए किसी भी बदलाव को एक ही स्ट्रीम में रिपोर्ट किया जाता है।

रेटिंग के लिए फ़िल्टर करने के लिए इस स्ट्रीम के लिए फ़िल्टर फ़िल्टर == 0 है। जब भी ऐसा होता है तो सब्सक्रिप्शन डिबग विंडो में परिणाम को आउटपुट करता है।

सेटिंग्स किसी भी तत्व पर रेटिंग = 0 घटना को ट्रिगर करेगा। लेकिन रेटिंग 5 पर वापस सेट करने से कोई घटना नहीं दिखाई देगी।

CLINQ के मामले में क्वेरी का आउटपुट INotifyCollectionChanged का समर्थन करेगा - ताकि क्वेरी परिणाम से जोड़े गए और हटाए गए आइटम सही परिणाम को इंगित कर सकें कि क्वेरी परिणाम बदल गया है (एक आइटम जोड़ा या हटाया गया है)।

एकमात्र तरीका यह है कि मैं इस पते के बारे में सोच सकता हूं, दो धाराओं को oppossite (दोहरी) क्वेरी के साथ सेट अप करना है। विपरीत धारा में जोड़ा गया एक आइटम परिणामसेट से हटाने का तात्पर्य है। यह विफल होने पर, मैं केवल FromEvent का उपयोग कर सकता हूं और किसी भी इकाई मॉडल को देखने योग्य नहीं बना सकता - जो आरएक्स को केवल एक ईवेंट एग्रीगेटर बनाता है। कोई संकेतक?

using System; 
using System.ComponentModel; 
using System.Linq; 
using System.Collections.Generic; 

namespace RxTest 
{ 

    public class TestEntity : Subject<TestEntity>, INotifyPropertyChanged 
    { 
     public IObservable<string> FileObservable { get; set; } 
     public IObservable<int> RatingObservable { get; set; } 

     public string File 
     { 
      get { return FileObservable.First(); } 
      set { (FileObservable as IObserver<string>).OnNext(value); } 
     } 

     public int Rating 
     { 
      get { return RatingObservable.First(); } 
      set { (RatingObservable as IObserver<int>).OnNext(value); } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     public TestEntity() 
     { 
      this.FileObservable = new BehaviorSubject<string>(string.Empty); 
      this.RatingObservable = new BehaviorSubject<int>(0); 
      this.FileObservable.Subscribe(f => { OnNotifyPropertyChanged("File"); }); 
      this.RatingObservable.Subscribe(f => { OnNotifyPropertyChanged("Rating"); }); 
     } 

     private void OnNotifyPropertyChanged(string property) 
     { 
      if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property)); 
      // update the class Observable 
      OnNext(this); 
     } 

    } 

    public class TestModel 
    { 
     private List<TestEntity> collection { get; set; } 
     private IDisposable sub; 

     public TestModel() 
     { 
      this.collection = new List<TestEntity>() { 
      new TestEntity() { File = "MySong.mp3", Rating = 5 }, 
      new TestEntity() { File = "Heart.mp3", Rating = 5 }, 
      new TestEntity() { File = "KarmaPolice.mp3", Rating = 5 }}; 

      var observableCollection = Observable.Concat<TestEntity>(this.collection.Cast<IObservable<TestEntity>>()); 
      var filteredCollection = from entity in observableCollection 
            where entity.Rating==0 
            select entity; 
      this.sub = filteredCollection.Subscribe(entity => 
       { 
        System.Diagnostics.Debug.WriteLine("Added :" + entity.File); 
       } 
      ); 
      this.collection[0].Rating = 0; 
      this.collection[0].Rating = 5; 
     } 
    }; 
} 
+4

"समस्या यह है कि आरएक्स सूचना है कि एक इकाई धारा से हटा दिया गया समर्थन करने के लिए प्रतीत नहीं होता है" - इस वजह से IObservable एक सतत् संग्रह, केवल मूल्यों का एक अतुल्यकालिक धारा का प्रतिनिधित्व नहीं करता। –

उत्तर

5

वास्तव में मुझे इसके लिए प्रतिक्रियाशील-UI लाइब्रेरी उपयोगी (NuGet में उपलब्ध) मिली। इस पुस्तकालय में संग्रह के लिए विशेष IObservable विषयों और एक पारंपरिक INCC संग्रह पर इन 'प्रतिक्रियाशील कोलेक्शन' में से एक बनाने की सुविधा शामिल है। इसके माध्यम से मेरे पास संग्रह में नए, हटाए गए आइटम और बदलती वस्तुओं के लिए स्ट्रीम हैं।मैं फिर स्ट्रीम को एक साथ मर्ज करने के लिए एक ज़िप का उपयोग करता हूं और एक लक्ष्य ViewModel अवलोकन संग्रह को संशोधित करता हूं। यह स्रोत मॉडल पर एक क्वेरी के आधार पर एक लाइव प्रक्षेपण प्रदान करता है।

निम्नलिखित कोड (इस कोड को भी सरल होगा, लेकिन वहाँ है कि समाधान की जरूरत रिएक्टिव-यूआई के सिल्वरलाइट संस्करण के साथ कुछ समस्याएं भी हैं) समस्या हल हो। कोड आग संग्रह बस उस समूह का तत्वों में से एक पर की 'रेटिंग' मूल्य का समायोजन करके बदले गए ईवेंट:

using System; 
using System.ComponentModel; 
using System.Linq; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using ReactiveUI; 

namespace RxTest 
{ 

    public class TestEntity : ReactiveObject, INotifyPropertyChanged, INotifyPropertyChanging 
    { 
     public string _File; 
     public int _Rating = 0; 
     public string File 
     { 
      get { return _File; } 
      set { this.RaiseAndSetIfChanged(x => x.File, value); } 
     } 

     public int Rating 
     { 
      get { return this._Rating; } 
      set { this.RaiseAndSetIfChanged(x => x.Rating, value); } 
     } 

     public TestEntity() 
     { 
     } 
    } 

    public class TestModel 
    { 
     private IEnumerable<TestEntity> collection { get; set; } 
     private IDisposable sub; 

     public TestModel() 
     { 
      this.collection = new ObservableCollection<TestEntity>() { 
      new TestEntity() { File = "MySong.mp3", Rating = 5 }, 
      new TestEntity() { File = "Heart.mp3", Rating = 5 }, 
      new TestEntity() { File = "KarmaPolice.mp3", Rating = 5 }}; 

      var filter = new Func<int, bool>(Rating => (Rating == 0)); 

      var target = new ObservableCollection<TestEntity>(); 
      target.CollectionChanged += new NotifyCollectionChangedEventHandler(target_CollectionChanged); 
      var react = new ReactiveCollection<TestEntity>(this.collection); 
      react.ChangeTrackingEnabled = true; 

      // update the target projection collection if an item is added 
      react.ItemsAdded.Subscribe(v => { if (filter.Invoke(v.Rating)) target.Add(v); }); 
      // update the target projection collection if an item is removed (and it was in the target) 
      react.ItemsRemoved.Subscribe(v => { if (filter.Invoke(v.Rating) && target.Contains(v)) target.Remove(v); }); 

      // track items changed in the collection. Filter only if the property "Rating" changes 
      var ratingChangingStream = react.ItemChanging.Where(i => i.PropertyName == "Rating").Select(i => new { Rating = i.Sender.Rating, Entity = i.Sender }); 
      var ratingChangedStream = react.ItemChanged.Where(i => i.PropertyName == "Rating").Select(i => new { Rating = i.Sender.Rating, Entity = i.Sender }); 
      // pair the two streams together for before and after the entity has changed. Make changes to the target 
      Observable.Zip(ratingChangingStream,ratingChangedStream, 
       (changingItem, changedItem) => new { ChangingRating=(int)changingItem.Rating, ChangedRating=(int)changedItem.Rating, Entity=changedItem.Entity}) 
       .Subscribe(v => { 
        if (filter.Invoke(v.ChangingRating) && (!filter.Invoke(v.ChangedRating))) target.Remove(v.Entity); 
        if ((!filter.Invoke(v.ChangingRating)) && filter.Invoke(v.ChangedRating)) target.Add(v.Entity); 
       }); 

      // should fire CollectionChanged Add in the target view model collection 
      this.collection.ElementAt(0).Rating = 0; 
      // should fire CollectionChanged Remove in the target view model collection 
      this.collection.ElementAt(0).Rating = 5; 
     } 

     void target_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      System.Diagnostics.Debug.WriteLine(e.Action); 
     } 
    } 
} 
+0

आरएक्सयूआई का कूल उपयोग! एक बात मैंने देखी है, प्रतिक्रियाशील कोलेक्शन हमेशा व्युत्पन्न संग्रह नहीं होता है, यह पर्यवेक्षण चयन का उप-वर्ग है, इसलिए आप इसे सीधे उपयोग कर सकते हैं। –

+0

धन्यवाद पॉल। कुछ बग्स को नोटिस किया गया, जो मुझे लगता है कि सिल्वरलाइट विशिष्ट हैं। 'वैल्यू' संपत्ति आइटम चेंजिंग/चेंज (यह पूर्ण पर सेट है) के लिए एक प्रतिक्रियाशील ऑब्जेक्ट से भरा नहीं है।मुझे नियमित आईएनपीसी ऑब्जेक्ट्स पर बदलावों को ट्रैक करने के लिए प्रतिक्रियाशील कोलेक्शन प्राप्त करने में भी परेशानी थी - एक रेएक्टिव ऑब्जेक्ट का उपयोग करके तय किया गया। –

+0

यह perf कारणों के लिए है - ItemChanging.Value() आपको मानों की एक धारा देगा –

2

ObservableCollection<T> का उपयोग करने में क्या गड़बड़ है? आरएक्स अत्यधिक उपयोग करने के लिए एक बहुत ही आसान ढांचा है; मुझे लगता है कि यदि आप खुद को एक एसिंक्रोनस स्ट्रीम के मूल आधार के खिलाफ लड़ते हैं, तो शायद आपको उस विशेष समस्या के लिए आरएक्स का उपयोग नहीं करना चाहिए।

+0

आरएक्स एक मॉडल से व्यूमोडेल में व्यू के माध्यम से परिवर्तनों को प्रसारित करने के लिए आदर्श है। आरएक्स में थ्रेड मार्शलिंग, कन्फ्लेशन इत्यादि जैसी विशेषताएं इसे आदर्श बनाती हैं। –

+0

अनुभव के आधार पर (मैं एक उत्पादन WPF आवेदन में आरएक्स का उपयोग किया है), मैं, इलाज के रूप में "यूआई" (INotifyPropertyChanged) ViewModel गुण है कि एक पृष्ठभूमि धागे से बदला नहीं जाना चाहिए कि में सलाह देते हैं। –

+0

आरएक्स में थ्रेड मार्शलिंग, कन्फ्लेशन, विषय इत्यादि जैसी विशेषताएं इसे आदर्श बनाती हैं। घटनाओं के लिए आरएक्स का उपयोग करने से स्वयं इस उपयोग को सीमित कर देता है और इसका मतलब है कि आपके कोड में दो प्रतिमानों का समर्थन करना है। मुझे लगता है कि यहां मौलिक मुद्दा यह है कि IObservable संग्रह के लिए उपयुक्त नहीं है, केवल संग्रह से ईवेंट। मुझे अभी भी लगता है कि एक सामान्य समाधान संभव है, यदि संग्रह से ईवेंट स्ट्रीम संग्रह की सामग्री से कॉन्सट स्ट्रीम के साथ 'ज़िप' है। –

0

मेरे दिमाग में यह आरएक्स का उपयुक्त उपयोग नहीं है। एक आरएक्स वेधशाला योग्य 'घटनाओं' की एक धारा है जिसे आप सब्सक्राइब कर सकते हैं। आप अपने दृश्य मॉडल में इन घटनाओं पर प्रतिक्रिया कर सकते हैं, उदाहरण के लिए उन्हें एक अवलोकन करने योग्य चयन में जोड़ना जो आपके विचार से जुड़ा हुआ है। हालांकि, एक पर्यवेक्षक का उपयोग उन वस्तुओं के एक निश्चित सेट का प्रतिनिधित्व करने के लिए नहीं किया जा सकता है, जिन्हें आप आइटम जोड़ते/हटाते हैं।

+0

नहीं, लेकिन ऑब्जर्वेबल कोलेक्शन का बिंदु यह है कि यह कई विषयों को उजागर करता है जो संग्रह पर आप जो संचालन कर सकते हैं उसका प्रतिनिधित्व करते हैं। यह एक बहुत ही सुरुचिपूर्ण समाधान है। – DanH

0

समस्या यह है कि आप TestEntity की सूची से अधिसूचनाओं को देख रहे हैं, टेस्टएन्टिटी से नहीं। तो आप जोड़ते हैं, लेकिन किसी भी टेस्टएन्टिटी में बदलाव नहीं करते हैं। इस टिप्पणी को देखने के लिए:

 if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property)); 

और आप देखेंगे कि प्रोग्राम वही चलता है! आपकी टेस्टएन्टीटी में आपकी अधिसूचनाएं किसी भी चीज़ से वायर्ड नहीं हैं। जैसा कि दूसरों ने बताया है, एक पर्यवेक्षण चयन का उपयोग करके यह तार आपके लिए जोड़ देगा।

+0

एफवाईआई, आपको हमेशा इसे लागू करने से पहले एक स्थानीय चर को एक ईवेंट असाइन करना चाहिए। अन्यथा आप दौड़ की स्थिति में भाग सकते हैं जो एक NullReferenceException –

+0

फेंक सकता है, केवल कोड को सरल रखने की कोशिश कर रहा है (हालांकि आईएनपीसी समर्थन वास्तव में आवश्यक नहीं है)। –

1

INPC कार्यान्वयन है कि मैंने कभी देखा है सबसे अच्छा शॉर्टकट या हैक्स के रूप में चिह्नित किया जा सकता है सब के सब। हालांकि, मैं वास्तव में डेवलपर्स को आईएनपीसी तंत्र के बाद से दोष नहीं दे सकता क्योंकि .NET निर्माता समर्थन करना चुनते हैं। इसके साथ ही, मैंने हाल ही में, मेरी राय में, आईएनपीसी का सबसे अच्छा कार्यान्वयन और आसपास के किसी भी एमवीवीएम ढांचे के लिए सबसे अच्छी तारीफ की खोज की है। दर्जनों बेहद सहायक कार्यों और एक्सटेंशन प्रदान करने के अलावा, यह मैंने देखा है कि सबसे सुरुचिपूर्ण आईएनपीसी पैटर्न भी खेलता है। यह कुछ हद तक ReactiveUI ढांचे जैसा दिखता है, लेकिन इसे एक व्यापक एमवीवीएम मंच के रूप में डिजाइन नहीं किया गया था। आईएनपीसी का समर्थन करने वाले व्यूमोडेल को बनाने के लिए, इसे किसी भी बेस क्लास या इंटरफेस की आवश्यकता नहीं है, हां अभी भी पूर्ण परिवर्तन अधिसूचना और दो मार्ग बाध्यकारी का समर्थन करने में सक्षम है, और सबसे अच्छा, आपकी सभी संपत्तियां स्वचालित हो सकती हैं!

यह PostSharp या NotifyPropertyWeaver के रूप में एक उपयोगिता का उपयोग नहीं है, लेकिन रिएक्टिव एक्सटेंशन ढांचे आसपास बनाया गया है। इस नए ढांचे का नाम प्रतिक्रियाशील प्रॉपर्टी है। मैं प्रोजेक्ट साइट पर जाने का सुझाव देता हूं (codeplex पर), और NuGet पैकेज को नीचे खींच रहा हूं। साथ ही, स्रोत कोड को देखता है, क्योंकि यह वास्तव में एक इलाज है।

मैं डेवलपर के साथ जुड़े किसी भी तरह से कर रहा हूँ, और परियोजना अभी भी काफी नया है। मैं जो पेशकश करता हूं उसके बारे में मैं वास्तव में उत्साहित हूं।

+0

प्रतिक्रियाशील प्रॉपर्टी शांत दिखती है, लेकिन शामिल नमूने में से कोई भी व्यूमोडल्स या मास्टर-विवरण दृश्य का संग्रह नहीं करता है, इसलिए यह स्पष्ट नहीं है कि यह लाइब्रेरी कैसे लागू होती है इस प्रश्न के लिए (या असली दुनिया में यह उपयोगी है, जहां हमें ऑब्जेक्ट्स के संग्रह को संपादित करने के लिए अक्सर यूआई की आवश्यकता होती है)। – Qwertie

+0

पुस्तकालय कुछ हद तक नया है, इसलिए कुछ दस्तावेज गायब समझ में आता है। माना जाता है कि जब मैंने अपनी प्रतिक्रिया यहां पोस्ट की, तो मैं सिर्फ सिल्वरलाइट/एक्सएमएल स्टैक उठा रहा था। अन्य तरीकों से अधिक शोध के बाद, मैं लाइब्रेरी में लौट आया, और फिर भी मेरी मूल पोस्ट से सहमत हूं। जिसने अधिकांश समय आईएनपीसी कार्यान्वयन की आलोचना की, लेकिन फिर भी इस चर्चा के दायरे में। स्रोत कोड को देखते हुए, एक प्रकार को ReactiveCollection कहा जाता है जिसे ओपी के संबंध में सीधे मेरे विचारों को जोड़ना और ठोस बनाना चाहिए। –

संबंधित मुद्दे