2011-08-15 8 views
18

Tenemos un proyecto WPF que sigue el patrón MVVM. noMejor manera de activar OnPropertyChanged

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

es una manera de hacer esto que requeriría menos código:

En el Modelo Vista hay una gran cantidad de código que se parece a esto?

sería bueno con algo como esto:

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

¿No debería siempre verificar siempre 'if (m_Fieldname! = Value) {...}'? Es más código, lo sé, pero subir 'PropertyChanged' no parece correcto si la propiedad no cambia. –

+0

@Danko, gracias buen punto –

+0

Personalmente, tengo un método SetProperty en una clase base de ObservableItem (de la cual deriva mi ViewModelBase) que maneja todas las notificaciones, comprobación de igualdad, configuración de propiedades, etc. Es agradable y limpio, y usted todavía solo tiene juegos y conspiraciones de una sola línea. Además, solo configure un fragmento de código para crearlos, y es rápido, simple y estandarizado. –

Respuesta

11

Usted puede echar un vistazo a PostSharp. Incluso tienen una muestra en Data Binding. El código tomado de allí:

/// <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); 

    } 
} 

El uso es tan simple como esto:

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

ejemplos tomados de PostSharp sitio y insertados para completar la respuesta

+0

¿Qué sucede si no quiero que X dispare OnPropertyChanged, solo deseo que lo dispare? – thenonhacker

+1

@thenonhacker Puede decorar una propiedad con 'IgnoreAutoChangeNotificationAttribute' para que se ignore. Ver documentación: http://doc.postsharp.net/##P_PostSharp_Patterns_Model_NotifyPropertyChangedAttribute y http://doc.postsharp.net/##PostSharp_Patterns_Model_IgnoreAutoChangeNotificationAttribute – Sascha

+0

¡Agradable! Gracias Sascha! – thenonhacker

3

Josh Smith tiene un buen artículo sobre utilizando DynamicObject para hacer esto here

Básicamente se trata de heredar de DynamicObject y enganchar en TrySetMember. CLR 4.0 solo, lamentablemente, aunque también es posible usar ContextBoundObject en versiones anteriores, pero eso probablemente perjudicaría el rendimiento, ya que es principalmente adecuado para la comunicación remota \ WCF.

+2

¿No hay una pérdida de rendimiento? La dinámica es bastante lenta y el cambio de propiedad puede afectar muy a menudo. ¿O estoy equivocado? –

+0

Definitivamente es más lento que hacerlo de la manera clásica, especialmente porque hay un bloque try/catch involucrado. Como de costumbre, hay una compensación. –

5

Parece como si el Framework 4.5 simplifica un poco esto:

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 ... 
} 

Esto no acaba de automatizar las cosas en la medida en que usted está buscando, pero utilizando el CallerMemberNameAttribute hace pasar el nombre de la propiedad como una cadena innecesaria.

Si usted está trabajando en Framework 4.0 con KB2468871 instalado, puede instalar el Microsoft BCL Paquete de compatibilidad través nuget, que también proporciona este atributo.

0

Ok, esto no limpia el código pero acorta la cantidad de tiempo escribiendo todo este código. Ahora puedo revisar una lista de más de 20 propiedades en un par de minutos.

Primero necesita definir todas sus variables privadas, supongo que su primer caracter es una minúscula. Ahora copie esas variables en otra lista ya que la macro elimina la línea original.

Por ejemplo:

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

a continuación, poner el cursor en algún lugar de esa línea y ejecutar esta macro. De nuevo, esto reemplaza la línea con una propiedad pública, así que asegúrese de tener la misma variable de miembro privado definida antes de esto en su clase.

Estoy seguro de que esta secuencia de comandos podría ser limpiada, pero me ahorró horas de trabajo tedioso hoy.

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

en mi humilde opinión, el enfoque PostSharp, como en la respuesta aceptada, es muy agradable y es por supuesto la respuesta directa a la pregunta formulada.

Sin embargo, para aquellos que no pueden o no usarán una herramienta como PostSharp para extender la sintaxis del lenguaje C#, se puede obtener la mayor ventaja de evitar la repetición del código con una clase base que implementa INotifyPropertyChanged. Hay muchos ejemplos por ahí, pero ninguno hasta ahora han sido incluidos en esta pregunta útil y bien víctimas de ella, así que aquí es la versión generalmente utilizo:

/// <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)); 
    } 
} 

utilizar, por ejemplo, así:

private int _value; 
public int Value 
{ 
    get { return _value; } 
    set { UpdatePropertyField(ref _value, value); } 
} 

No es tan conciso como poder aplicar un atributo de código a una propiedad implementada automáticamente como en el enfoque PostSharp, pero aún ayuda a acelerar la implementación de modelos de vista y otros tipos similares.

Las características principales anteriores que lo distinguen de otras implementaciones:

  1. igualdad se compararon mediante EqualityComparer<T>.Default. Esto asegura que los tipos de valores se pueden comparar sin encasillar (una alternativa común sería object.Equals(object, object)). La instancia IEqualityComparer<T> está en caché, por lo que después de la primera comparación para cualquier tipo dado T, es muy eficiente.
  2. El método OnPropertyChanged() es virtual. Esto permite que los tipos derivados manejen fácil y eficientemente los eventos cambiados de propiedad de forma centralizada, sin tener que suscribirse al evento PropertyChanged mismo (por ejemplo, para múltiples niveles de herencia) y también le da al tipo derivado un mejor control sobre cómo y cuándo maneja la propiedad cambió el evento relativo al aumento del evento real PropertyChanged.
Cuestiones relacionadas