2011-08-15 12 views
18

हमारे पास एक WPF प्रोजेक्ट है जो एमवीवीएम पैटर्न का पालन करता है।OnProperty को ट्रिगर करने का बेहतर तरीका चेंज

private string m_Fieldname; 
    public string Fieldname 
    { 
     get { return m_Fieldname; } 
     set 
     { 
      m_Fieldname = value; 
      OnPropertyChanged("Fieldname"); 
     } 
    } 

है वहाँ है कि कम कोड की आवश्यकता होगी यह करने के लिए एक तरह से:

देखें मॉडल में वहाँ है कि इस तरह दिखता है कोड का एक बहुत कुछ है?

कुछ इस तरह के साथ अच्छा नहीं होगा:

[NotifyWhenChanged] 
public string Fieldname { get; set ; } 
+1

क्या आपको हमेशा 'if (m_Fieldname! = Value) {...}' की जांच नहीं करनी चाहिए? यह अधिक कोड है, मुझे पता है, लेकिन 'PropertyChanged' को बढ़ाने से संपत्ति ठीक नहीं होती है, तो सही नहीं लगता है। –

+0

@Danko, धन्यवाद अच्छा बिंदु –

+0

व्यक्तिगत रूप से, मैं सिर्फ एक आधार ObservableItem वर्ग (जिसमें से मेरी ViewModelBase निकला है) में एक SetProperty विधि है कि सभी सूचनाएं, समानता की जाँच, संपत्ति की स्थापना, आदि यह अच्छे और साफ है संभालती है, और आप अभी भी केवल एक-लाइनर हो जाता है और सेट करता है। इसके अलावा, उन्हें बनाने के लिए बस एक कोड स्निपेट सेट करें, और इसकी त्वरित, सरल और मानकीकृत। –

उत्तर

11

आप PostSharp पर एक नज़र हो सकता था। उनके पास Data Binding पर एक नमूना भी है। कोड वहाँ से लिया:

[NotifyPropertyChanged] 
public class Shape 
{ 
    public double X { get; set; } 
    public double Y { get; set; } 
} 

उदाहरण PostSharp साइट से लिया जाता है और जवाब को पूरा करने

+0

क्या होगा यदि मैं नहीं चाहता कि एक्स ऑनप्रॉपर्टी को चालू करे, मैं केवल वाई को आग लगाना चाहता हूं? – thenonhacker

+1

@thenonhacker आप इसे अनदेखा करने के लिए 'IgnoreAutoChangeNotificationAttribute' के साथ एक संपत्ति को सजाने के लिए कर सकते हैं। दस्तावेज़ीकरण देखें: http://doc.postsharp.net/##T_PostSharp_Patterns_Model_NotifyPropertyChangedAttribute और http://doc.postsharp.net/##T_PostSharp_Patterns_Model_IgnoreAutoChangeNotificationAttribute – Sascha

+0

अच्छा! धन्यवाद साचा! – thenonhacker

3

जोश स्मिथ एक अच्छा लेख पर है के लिए डाला:

/// <summary> 
/// Aspect that, when apply on a class, fully implements the interface 
/// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to 
/// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>. 
/// </summary> 
[Serializable] 
[IntroduceInterface(typeof(INotifyPropertyChanged), 
        OverrideAction = InterfaceOverrideAction.Ignore)] 
[MulticastAttributeUsage(MulticastTargets.Class, 
          Inheritance = MulticastInheritance.Strict)] 
public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect, 
                INotifyPropertyChanged 
{ 

    /// <summary> 
    /// Field bound at runtime to a delegate of the method <c>OnPropertyChanged</c>. 
    /// </summary> 
    [ImportMember("OnPropertyChanged", IsRequired = false)] 
    public Action<string> OnPropertyChangedMethod; 

    /// <summary> 
    /// Method introduced in the target type (unless it is already present); 
    /// raises the <see cref="PropertyChanged"/> event. 
    /// </summary> 
    /// <param name="propertyName">Name of the property.</param> 
    [IntroduceMember(Visibility = Visibility.Family, IsVirtual = true, 
         OverrideAction = MemberOverrideAction.Ignore)] 
    public void OnPropertyChanged(string propertyName) 
    { 
     if (this.PropertyChanged != null) 
     { 
      this.PropertyChanged(this.Instance, 
            new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    /// <summary> 
    /// Event introduced in the target type (unless it is already present); 
    /// raised whenever a property has changed. 
    /// </summary> 
    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)] 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// Method intercepting any call to a property setter. 
    /// </summary> 
    /// <param name="args">Aspect arguments.</param> 
    [OnLocationSetValueAdvice, 
    MulticastPointcut(Targets = MulticastTargets.Property, 
     Attributes = MulticastAttributes.Instance)] 
    public void OnPropertySet(LocationInterceptionArgs args) 
    { 
     // Don't go further if the new value is equal to the old one. 
     // (Possibly use object.Equals here). 
     if (args.Value == args.GetCurrentValue()) return; 

     // Actually sets the value. 
     args.ProceedSetValue(); 

     // Invoke method OnPropertyChanged (our, the base one, or the overridden one). 
     this.OnPropertyChangedMethod.Invoke(args.Location.Name); 

    } 
} 

प्रयोग तो यह जितना आसान है यह here

मूल रूप से इसमें डायनामिक ऑब्जेक्ट से विरासत में शामिल होना और फिर TrySetMember में शामिल होना शामिल है। सीएलआर 4.0 केवल, दुर्भाग्यवश, हालांकि यह पिछले संस्करणों में ContextBoundObject का उपयोग करना भी संभव हो सकता है लेकिन यह शायद प्रदर्शन को नुकसान पहुंचाएगा, मुख्य रूप से \ WCF को रिमोट करने के लिए उपयुक्त है।

+2

कोई प्रदर्शन नुकसान नहीं है? गतिशील काफी धीमी है और संपत्ति विनिमय अक्सर हिट हो सकता है। या मैं गलत हूँ? –

+0

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

5

ऐसा लगता है मानो फ्रेमवर्क 4.5 थोड़ा सरल लग रहा है इस:

private string m_Fieldname; 
public string Fieldname 
{ 
    get { return m_Fieldname; } 
    set 
    { 
     m_Fieldname = value; 
     OnPropertyChanged(); 
    } 
} 

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed") 
{ 
    // ... do stuff here ... 
} 

यह काफी हद तक आप देख रहे हैं के लिए चीजों को स्वचालित नहीं है, लेकिन CallerMemberNameAttribute का उपयोग कर एक के रूप में संपत्ति के नाम पर गुजर बनाता है स्ट्रिंग अनावश्यक।

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

0

ठीक है यह कोड को साफ़ नहीं करता है लेकिन यह इस कोड को लिखने में कितना समय कम करता है। अब मैं कुछ मिनटों में 20+ गुणों की सूची के माध्यम से उड़ सकता हूं।

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

उदाहरण के लिए:

private int something1 = 0; 
private int something2 = 0; 
private int something3 = 0; 
private int something4 = 0; 
private int something5 = 0; 
private int something6 = 0; 

फिर उस लाइन पर कहीं न कहीं अपना कर्सर रखें और इस मैक्रो चलाते हैं। फिर यह एक सार्वजनिक संपत्ति के साथ लाइन को प्रतिस्थापित करता है, इसलिए सुनिश्चित करें कि आपके पास अपनी कक्षा में पहले से ही वही निजी सदस्य चर परिभाषित किया गया है।

मुझे यकीन है कि इस स्क्रिप्ट को साफ़ किया जा सकता है, लेकिन इसने मुझे आज कड़ी मेहनत के काम बचाए।

Sub TemporaryMacro() 
    DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText) 
    DTE.ActiveDocument.Selection.Delete(7) 
    DTE.ActiveDocument.Selection.Text = "public" 
    DTE.ActiveDocument.Selection.CharRight() 
    DTE.ExecuteCommand("Edit.Find") 
    DTE.Find.FindWhat = " " 
    DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument 
    DTE.Find.MatchCase = False 
    DTE.Find.MatchWholeWord = False 
    DTE.Find.Backwards = False 
    DTE.Find.MatchInHiddenText = False 
    DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral 
    DTE.Find.Action = vsFindAction.vsFindActionFind 
    If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then 
     Throw New System.Exception("vsFindResultNotFound") 
    End If 
    DTE.ActiveDocument.Selection.CharRight() 
    DTE.ActiveDocument.Selection.WordRight(True) 
    DTE.ActiveDocument.Selection.CharLeft(True) 
    DTE.ActiveDocument.Selection.Copy() 
    DTE.ActiveDocument.Selection.CharLeft() 
    DTE.ActiveDocument.Selection.CharRight(True) 
    DTE.ActiveDocument.Selection.ChangeCase(VsCaseOptions.VsCaseOptionsUppercase) 
    DTE.ActiveDocument.Selection.EndOfLine() 
    DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText) 
    DTE.ExecuteCommand("Edit.Find") 
    DTE.Find.FindWhat = " = " 
    DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument 
    DTE.Find.MatchCase = False 
    DTE.Find.MatchWholeWord = False 
    DTE.Find.Backwards = False 
    DTE.Find.MatchInHiddenText = False 
    DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral 
    DTE.Find.Action = vsFindAction.vsFindActionFind 
    If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then 
     Throw New System.Exception("vsFindResultNotFound") 
    End If 
    DTE.ActiveDocument.Selection.CharLeft() 
    DTE.ActiveDocument.Selection.EndOfLine(True) 
    DTE.ActiveDocument.Selection.Delete() 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "{" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "get { return " 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = "; }" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "set" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "{" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "if(" 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = " != value)" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "{" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = " = value;" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "OnPropertyChanged(""" 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = """);" 
    DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText) 
    DTE.ExecuteCommand("Edit.Find") 
    DTE.Find.FindWhat = """" 
    DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument 
    DTE.Find.MatchCase = False 
    DTE.Find.MatchWholeWord = False 
    DTE.Find.Backwards = False 
    DTE.Find.MatchInHiddenText = False 
    DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral 
    DTE.Find.Action = vsFindAction.vsFindActionFind 
    If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then 
     Throw New System.Exception("vsFindResultNotFound") 
    End If 
    DTE.ActiveDocument.Selection.CharRight() 
    DTE.ActiveDocument.Selection.CharRight(True) 
    DTE.ActiveDocument.Selection.ChangeCase(VsCaseOptions.VsCaseOptionsUppercase) 
    DTE.ActiveDocument.Selection.Collapse() 
    DTE.ActiveDocument.Selection.EndOfLine() 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "}" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "}" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "}" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.LineDown() 
    DTE.ActiveDocument.Selection.EndOfLine() 
End Sub 
2

IMHO, स्वीकार किए जाते हैं जवाब में के रूप में PostSharp दृष्टिकोण, बहुत अच्छा है और निश्चित रूप से प्रश्न पूछा का सीधा जवाब है।

हालांकि, उन लोगों के लिए जो सी # भाषा सिंटैक्स का विस्तार करने के लिए पोस्टशर्प जैसे टूल का उपयोग नहीं कर सकते हैं या नहीं करेंगे, किसी को बेस क्लास के साथ कोड पुनरावृत्ति से बचने का अधिक लाभ मिल सकता है जो INotifyPropertyChanged लागू करता है।

/// <summary> 
/// Base class for classes that need to implement <see cref="INotifyPropertyChanged"/> 
/// </summary> 
public class NotifyPropertyChangedBase : INotifyPropertyChanged 
{ 
    /// <summary> 
    /// Raised when a property value changes 
    /// </summary> 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// Updates a field for a named property 
    /// </summary> 
    /// <typeparam name="T">The type of the field</typeparam> 
    /// <param name="field">The field itself, passed by-reference</param> 
    /// <param name="newValue">The new value for the field</param> 
    /// <param name="propertyName">The name of the associated property</param> 
    protected void UpdatePropertyField<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null) 
    { 
     if (!EqualityComparer<T>.Default.Equals(field, newValue)) 
     { 
      field = newValue; 
      OnPropertyChanged(propertyName); 
     } 
    } 

    /// <summary> 
    /// Raises the <see cref="PropertyChanged"/> event. 
    /// </summary> 
    /// <param name="propertyName">The name of the property that has been changed</param> 
    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChanged?.DynamicInvoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

, प्रयुक्त उदाहरण के लिए, इस तरह::

ऐसे कई उदाहरण चारों ओर झूठ बोल रहे हैं, लेकिन कोई भी अब तक इस उपयोगी और अच्छी तरह से पाठकों वाले प्रश्न में शामिल किया गया है, इसलिए यहाँ संस्करण मैं आम तौर पर इस्तेमाल करते हैं
private int _value; 
public int Value 
{ 
    get { return _value; } 
    set { UpdatePropertyField(ref _value, value); } 
} 

काफी के रूप में सिर्फ PostSharp दृष्टिकोण में के रूप में एक स्वत: लागू किया संपत्ति के लिए एक कोड विशेषता लागू करने में सक्षम होने के रूप में संक्षिप्त, लेकिन अभी भी नहीं किए गए दृश्य मॉडल और अन्य समान प्रकार के कार्यान्वयन में तेजी के लिए एक लंबा रास्ता जाता है।

कि ऊपर प्रमुख विशेषताओं कुछ अन्य कार्यान्वयन से इसकी अलग पहचान:

  1. समानता EqualityComparer<T>.Default का उपयोग कर तुलना में है। यह सुनिश्चित करता है कि बॉक्स प्रकारों के बिना मूल्य प्रकारों की तुलना की जा सकती है (एक सामान्य विकल्प object.Equals(object, object) होगा)। IEqualityComparer<T> उदाहरण कैश किया गया है, इसलिए किसी दिए गए प्रकार T के लिए पहली तुलना के बाद, यह बहुत ही कुशल है।
  2. OnPropertyChanged() विधि virtual है। यह व्युत्पन्न प्रकारों को PropertyChanged ईवेंट (उदाहरण के लिए विरासत के कई स्तरों के लिए) की सदस्यता लेने के बिना, केंद्रीकृत तरीके से संपत्ति परिवर्तित घटनाओं को आसानी से और कुशलता से संभालने की अनुमति देता है और निश्चित रूप से व्युत्पन्न प्रकार को नियंत्रित करता है कि यह कैसे और कब संभालता है संपत्ति ने वास्तविक PropertyChanged ईवेंट को बढ़ाने के सापेक्ष घटना को बदल दिया।
संबंधित मुद्दे