2009-02-27 10 views
8

मैं सिल्वरलाइट 2 में एक अनुप्रयोग विकसित कर रहा हूं और मॉडल-व्यू-व्यू मॉडेल पैटर्न का पालन करने की कोशिश कर रहा हूं। मैं ViewModel पर एक बुलियन संपत्ति के लिए कुछ नियंत्रणों पर IsEnabled संपत्ति को बाध्यकारी कर रहा हूं।गणना गुणों के लिए संपत्तिChanged अधिसूचना

मैं उन समस्याओं में भाग रहा हूं जब वे गुण अन्य गुणों से प्राप्त होते हैं। मान लीजिए कि मेरे पास एक सेव बटन है जिसे मैं सहेजना संभव है (डेटा लोड हो गया है, और वर्तमान में डेटाबेस में सामान करने में व्यस्त नहीं हैं) में सक्षम होना चाहता हूं।

private bool m_DatabaseBusy; 
    public bool DatabaseBusy 
    { 
     get { return m_DatabaseBusy; } 
     set 
     { 
      if (m_DatabaseBusy != value) 
      { 
       m_DatabaseBusy = value; 
       OnPropertyChanged("DatabaseBusy"); 
      } 
     } 
    } 

    private bool m_IsLoaded; 
    public bool IsLoaded 
    { 
     get { return m_IsLoaded; } 
     set 
     { 
      if (m_IsLoaded != value) 
      { 
       m_IsLoaded = value; 
       OnPropertyChanged("IsLoaded"); 
      } 
     } 
    } 

अब इस मुझे क्या करना चाहते हैं::

तो मैं इस तरह के गुण के एक जोड़े है

public bool CanSave 
{ 
    get { return this.IsLoaded && !this.DatabaseBusy; } 
} 

लेकिन संपत्ति-बदल अधिसूचना की कमी पर ध्यान दें।

तो सवाल यह है कि: एक बुलियन संपत्ति को उजागर करने का एक साफ तरीका क्या है जिसे मैं बांध सकता हूं, लेकिन स्पष्ट रूप से सेट होने और अधिसूचना प्रदान करने के बजाय गणना की जाती है ताकि UI सही ढंग से अपडेट हो सके?

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

मूल रूप से क्या मैं एक अंतरफलक है कि पकड़ करने के लिए क्या गुण अन्य संपत्तियों पर निर्भर कुंजी-मान जोड़ों की एक सूची परिभाषित किया गया था किया था:

public interface INotifyDependentPropertyChanged 
{ 
    // key,value = parent_property_name, child_property_name, where child depends on parent. 
    List<KeyValuePair<string, string>> DependentPropertyList{get;} 
} 

मैं तो विशेषता बनाया प्रत्येक प्रॉपर्टी पर जाने के लिए:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)] 
public class NotifyDependsOnAttribute : Attribute 
{ 
    public string DependsOn { get; set; } 
    public NotifyDependsOnAttribute(string dependsOn) 
    { 
     this.DependsOn = dependsOn; 
    } 

    public static void BuildDependentPropertyList(object obj) 
    { 
     if (obj == null) 
     { 
      throw new ArgumentNullException("obj"); 
     } 

     var obj_interface = (obj as INotifyDependentPropertyChanged); 

     if (obj_interface == null) 
     { 
      throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name)); 
     } 

     obj_interface.DependentPropertyList.Clear(); 

     // Build the list of dependent properties. 
     foreach (var property in obj.GetType().GetProperties()) 
     { 
      // Find all of our attributes (may be multiple). 
      var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false); 

      foreach (var attribute in attributeArray) 
      { 
       obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name)); 
      } 
     } 
    } 
} 

विशेषता स्वयं केवल एक स्ट्रिंग को स्टोर करती है। आप प्रति संपत्ति एकाधिक निर्भरताओं को परिभाषित कर सकते हैं। विशेषता का गड़बड़ BuildDependentPropertyList स्थिर फ़ंक्शन में है। आपको इसे अपनी कक्षा के निर्माता में कॉल करना होगा। (किसी को पता है कि कक्षा/कन्स्ट्रक्टर विशेषता के माध्यम से ऐसा करने का कोई तरीका है?) मेरे मामले में यह सब एक बेस क्लास में छिपा हुआ है, इसलिए उप-वर्गों में आप गुणों पर गुण डालते हैं। फिर आप किसी भी निर्भरता को देखने के लिए अपने OnPropertyChanged समकक्ष को संशोधित करते हैं। यहां एक उदाहरण के रूप में मेरा व्यूमोडेल बेस क्लास है:

public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyname) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyname)); 

      // fire for dependent properties 
      foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname))) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(p.Value)); 
      } 
     } 
    } 

    private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>(); 
    public List<KeyValuePair<string, string>> DependentPropertyList 
    { 
     get { return m_DependentPropertyList; } 
    } 

    public ViewModel() 
    { 
     NotifyDependsOnAttribute.BuildDependentPropertyList(this); 
    } 
} 

अंत में, आप प्रभावित गुणों पर विशेषताओं को सेट करते हैं। मुझे इस तरह से पसंद है क्योंकि व्युत्पन्न संपत्ति उन गुणों को रखती है जो यह दूसरी तरफ की बजाय निर्भर करती हैं।

[NotifyDependsOn("Session")] 
    [NotifyDependsOn("DatabaseBusy")] 
    public bool SaveEnabled 
    { 
     get { return !this.Session.IsLocked && !this.DatabaseBusy; } 
    } 

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

उत्तर

6

यह करने के लिए परंपरागत तरीके से गुण अपने गणना एक को प्रभावित कर सकता है कि में से प्रत्येक के लिए एक OnPropertyChanged कॉल जोड़ने के लिए, इस तरह है:

public bool IsLoaded 
{ 
    get { return m_IsLoaded; } 
    set 
    { 
     if (m_IsLoaded != value) 
     { 
      m_IsLoaded = value; 
      OnPropertyChanged("IsLoaded"); 
      OnPropertyChanged("CanSave"); 
     } 
    } 
} 

यह उदाहरण के लिए यदि एक सा गन्दा प्राप्त कर सकते हैं (,, CanSave में आपकी गणना बदलती है)।

एक (क्लीनर मैं नहीं पता है?) इस के आसपास पाने के लिए जिस तरह से OnPropertyChanged ओवरराइड और वहाँ कॉल करने के लिए होगा:

protected override void OnPropertyChanged(string propertyName) 
{ 
    base.OnPropertyChanged(propertyName); 
    if (propertyName == "IsLoaded" /* || propertyName == etc */) 
    { 
     base.OnPropertyChanged("CanSave"); 
    } 
} 
+0

मैं OnPropertyChanged ओवरराइड के लिए जा रहा हूँ। मुझे आश्चर्य है कि निर्भरताओं को गुण के रूप में घोषित किया जा सकता है? – geofftnz

+0

CanSave पर निर्भर करता है कि संपत्ति नामों का ट्रैक रखने के लिए आप एक स्थैतिक सूची सेट अप कर सकते हैं। फिर यह एक एकल होगा "अगर (_canSaveProperties.Contains (propertyName))"। –

+0

अच्छा विचार, धन्यवाद! – geofftnz

2

आप हर जगह CanSave संपत्ति बदलाव के लिए एक अधिसूचना जोड़ने के लिए संपत्ति में से एक यह परिवर्तन निर्भर करता है की जरूरत है:

OnPropertyChanged("DatabaseBusy"); 
OnPropertyChanged("CanSave"); 

और

OnPropertyChanged("IsEnabled"); 
OnPropertyChanged("CanSave"); 
+0

यह सही ढंग से काम होगा, लेकिन यह मतलब है कि आप CanSave अमान्य हो जाएगा आवश्यक से अधिक, संभवतः शेष एप्लिकेशन को आवश्यकतानुसार अधिक काम करना है। – KeithMahoney

+0

क्या होगा यदि मेरे पास संपत्ति की सेट विधि तक पहुंच न हो? – geofftnz

1

इस समाधान के बारे में कैसे?

private bool _previousCanSave; 
private void UpdateCanSave() 
{ 
    if (CanSave != _previousCanSave) 
    { 
     _previousCanSave = CanSave; 
     OnPropertyChanged("CanSave"); 
    } 
} 

फिर IsLoaded और DatabaseBavey के सेटर्स में UpdateCanSave() को कॉल करें?

यदि आप IsLoaded और DatabaseBusy के सेटर्स को संशोधित नहीं कर सकते हैं क्योंकि वे अलग-अलग कक्षाओं में हैं, तो आप IsCoaded और DatabaseBusy को परिभाषित करने वाले ऑब्जेक्ट के लिए PropertyChanged ईवेंट हैंडलर में UpdateCanSave() को कॉल करने का प्रयास कर सकते हैं।

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