9

So che ho bisogno di chiamare RemoveValueChanged, ma non sono stato in grado di trovare un posto affidabile per chiamare questo. Sto imparando che probabilmente non ce n'è uno.Come posso correggere DependencyPropertyDescriptor AddValueChanged Memory Leak su AttachedBehavior?

Mi sembra di dover trovare un modo diverso per monitorare la modifica, quindi aggiungere un gestore utilizzando AddValueChanged. Sto cercando consigli sul modo migliore per raggiungere questo obiettivo. Ho visto la raccomandazione di utilizzare un PropertyChangedCallback nel PropertyMetadata, ma non sono sicuro di come farlo quando il mio TextBox e Adorner non sono statici. Inoltre, la proprietà IsFocused non è una proprietà Dependency creata nella mia classe.

Grazie.

public sealed class WatermarkTextBoxBehavior 
{ 
    private readonly TextBox m_TextBox; 
    private TextBlockAdorner m_TextBlockAdorner; 

    private WatermarkTextBoxBehavior(TextBox textBox) 
    { 
     if (textBox == null) 
      throw new ArgumentNullException("textBox"); 

     m_TextBox = textBox; 
    } 

    #region Behavior Internals 

    private static WatermarkTextBoxBehavior GetWatermarkTextBoxBehavior(DependencyObject obj) 
    { 
     return (WatermarkTextBoxBehavior)obj.GetValue(WatermarkTextBoxBehaviorProperty); 
    } 

    private static void SetWatermarkTextBoxBehavior(DependencyObject obj, WatermarkTextBoxBehavior value) 
    { 
     obj.SetValue(WatermarkTextBoxBehaviorProperty, value); 
    } 

    private static readonly DependencyProperty WatermarkTextBoxBehaviorProperty = 
     DependencyProperty.RegisterAttached("WatermarkTextBoxBehavior", 
      typeof(WatermarkTextBoxBehavior), typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(null)); 

    public static bool GetEnableWatermark(TextBox obj) 
    { 
     return (bool)obj.GetValue(EnableWatermarkProperty); 
    } 

    public static void SetEnableWatermark(TextBox obj, bool value) 
    { 
     obj.SetValue(EnableWatermarkProperty, value); 
    } 

    public static readonly DependencyProperty EnableWatermarkProperty = 
     DependencyProperty.RegisterAttached("EnableWatermark", typeof(bool), 
      typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(false, OnEnableWatermarkChanged)); 

    private static void OnEnableWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (e.OldValue != null) 
     { 
      var enabled = (bool)e.OldValue; 

      if (enabled) 
      { 
       var textBox = (TextBox)d; 
       var behavior = GetWatermarkTextBoxBehavior(textBox); 
       behavior.Detach(); 

       SetWatermarkTextBoxBehavior(textBox, null); 
      } 
     } 

     if (e.NewValue != null) 
     { 
      var enabled = (bool)e.NewValue; 

      if (enabled) 
      { 
       var textBox = (TextBox)d; 
       var behavior = new WatermarkTextBoxBehavior(textBox); 
       behavior.Attach(); 

       SetWatermarkTextBoxBehavior(textBox, behavior); 
      } 
     } 
    } 

    private void Attach() 
    { 
     m_TextBox.Loaded += TextBoxLoaded; 
     m_TextBox.TextChanged += TextBoxTextChanged; 
     m_TextBox.DragEnter += TextBoxDragEnter; 
     m_TextBox.DragLeave += TextBoxDragLeave; 
     m_TextBox.IsVisibleChanged += TextBoxIsVisibleChanged; 
    } 

    private void Detach() 
    { 
     m_TextBox.Loaded -= TextBoxLoaded; 
     m_TextBox.TextChanged -= TextBoxTextChanged; 
     m_TextBox.DragEnter -= TextBoxDragEnter; 
     m_TextBox.DragLeave -= TextBoxDragLeave; 
     m_TextBox.IsVisibleChanged -= TextBoxIsVisibleChanged; 
    } 

    private void TextBoxDragLeave(object sender, DragEventArgs e) 
    { 
     UpdateAdorner(); 
    } 

    private void TextBoxDragEnter(object sender, DragEventArgs e) 
    { 
     m_TextBox.TryRemoveAdorners<TextBlockAdorner>(); 
    } 

    private void TextBoxIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     UpdateAdorner(); 
    } 

    private void TextBoxTextChanged(object sender, TextChangedEventArgs e) 
    { 
     var hasText = !string.IsNullOrEmpty(m_TextBox.Text); 
     SetHasText(m_TextBox, hasText); 
    } 

    private void TextBoxLoaded(object sender, RoutedEventArgs e) 
    { 
     Init(); 
    } 

    #endregion 

    #region Attached Properties 

    public static string GetLabel(TextBox obj) 
    { 
     return (string)obj.GetValue(LabelProperty); 
    } 

    public static void SetLabel(TextBox obj, string value) 
    { 
     obj.SetValue(LabelProperty, value); 
    } 

    public static readonly DependencyProperty LabelProperty = 
     DependencyProperty.RegisterAttached("Label", typeof(string), typeof(WatermarkTextBoxBehavior)); 

    public static Style GetLabelStyle(TextBox obj) 
    { 
     return (Style)obj.GetValue(LabelStyleProperty); 
    } 

    public static void SetLabelStyle(TextBox obj, Style value) 
    { 
     obj.SetValue(LabelStyleProperty, value); 
    } 

    public static readonly DependencyProperty LabelStyleProperty = 
     DependencyProperty.RegisterAttached("LabelStyle", typeof(Style), 
      typeof(WatermarkTextBoxBehavior)); 

    public static bool GetHasText(TextBox obj) 
    { 
     return (bool)obj.GetValue(HasTextProperty); 
    } 

    private static void SetHasText(TextBox obj, bool value) 
    { 
     obj.SetValue(HasTextPropertyKey, value); 
    } 

    private static readonly DependencyPropertyKey HasTextPropertyKey = 
     DependencyProperty.RegisterAttachedReadOnly("HasText", typeof(bool), 
      typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(false)); 

    public static readonly DependencyProperty HasTextProperty = 
     HasTextPropertyKey.DependencyProperty; 

    #endregion 

    private void Init() 
    { 
     m_TextBlockAdorner = new TextBlockAdorner(m_TextBox, GetLabel(m_TextBox), GetLabelStyle(m_TextBox)); 
     UpdateAdorner(); 

     DependencyPropertyDescriptor focusProp = DependencyPropertyDescriptor.FromProperty(UIElement.IsFocusedProperty, typeof(FrameworkElement)); 
     if (focusProp != null) 
     { 
      focusProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner()); 
     } 

     DependencyPropertyDescriptor containsTextProp = DependencyPropertyDescriptor.FromProperty(HasTextProperty, typeof(TextBox)); 
     if (containsTextProp != null) 
     { 
      containsTextProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner()); 
     } 
    } 

    private void UpdateAdorner() 
    { 
     if (GetHasText(m_TextBox) || 
      m_TextBox.IsFocused || 
      !m_TextBox.IsVisible) 
     { 
      // Hide the Watermark Label if the adorner layer is visible 
      m_TextBox.ToolTip = GetLabel(m_TextBox); 
      m_TextBox.TryRemoveAdorners<TextBlockAdorner>(); 
     } 
     else 
     { 
      // Show the Watermark Label if the adorner layer is visible 
      m_TextBox.ToolTip = null; 
      m_TextBox.TryAddAdorner<TextBlockAdorner>(m_TextBlockAdorner); 
     } 
    } 
} 

risposta

16

AddValueChanged di dipendenza risultati descrittori proprietà in perdita di memoria come già sapete. Pertanto, come descritto in here, è possibile creare una classe personalizzata PropertyChangeNotifier per ascoltare qualsiasi modifica delle proprietà di dipendenza.

L'implementazione completa è disponibile qui - PropertyDescriptor AddValueChanged Alternative.


Citazione dal link:

Questa classe sfrutta il fatto che associazioni utilizzano deboli riferimenti per gestire le associazioni in modo che la classe non sradicherà l'oggetto che proprietà cambia sta guardando. Utilizza anche un WeakReference per mantenere un riferimento all'oggetto la cui proprietà è che sta guardando senza il rooting di quell'oggetto. In questo modo, è possibile mantenere una raccolta di questi oggetti in modo da poter sganciare la proprietà cambiare in seguito senza preoccuparsi di quella raccolta di rooting dell'oggetto cui valori si sta guardando.

Anche per completezza di risposta sto postando il codice completo qui per evitare eventuali problemi di stampa in futuro.

public sealed class PropertyChangeNotifier : DependencyObject, IDisposable 
{ 
    #region Member Variables 

    private readonly WeakReference _propertySource; 

    #endregion // Member Variables 

    #region Constructor 
    public PropertyChangeNotifier(DependencyObject propertySource, string path) 
     : this(propertySource, new PropertyPath(path)) 
    { 
    } 
    public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property) 
     : this(propertySource, new PropertyPath(property)) 
    { 
    } 
    public PropertyChangeNotifier(DependencyObject propertySource, PropertyPath property) 
    { 
     if (null == propertySource) 
      throw new ArgumentNullException("propertySource"); 
     if (null == property) 
      throw new ArgumentNullException("property"); 
     _propertySource = new WeakReference(propertySource); 
     Binding binding = new Binding 
     { 
      Path = property, 
      Mode = BindingMode.OneWay, 
      Source = propertySource 
     }; 
     BindingOperations.SetBinding(this, ValueProperty, binding); 
    } 
    #endregion // Constructor 

    #region PropertySource 
    public DependencyObject PropertySource 
    { 
     get 
     { 
      try 
      { 
       // note, it is possible that accessing the target property 
       // will result in an exception so i’ve wrapped this check 
       // in a try catch 
       return _propertySource.IsAlive 
       ? _propertySource.Target as DependencyObject 
       : null; 
      } 
      catch 
      { 
       return null; 
      } 
     } 
    } 
    #endregion // PropertySource 

    #region Value 
    /// <summary> 
    /// Identifies the <see cref="Value"/> dependency property 
    /// </summary> 
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", 
    typeof(object), typeof(PropertyChangeNotifier), new FrameworkPropertyMetadata(null, OnPropertyChanged)); 

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     PropertyChangeNotifier notifier = (PropertyChangeNotifier)d; 
     if (null != notifier.ValueChanged) 
      notifier.ValueChanged(notifier, EventArgs.Empty); 
    } 

    /// <summary> 
    /// Returns/sets the value of the property 
    /// </summary> 
    /// <seealso cref="ValueProperty"/> 
    [Description("Returns/sets the value of the property")] 
    [Category("Behavior")] 
    [Bindable(true)] 
    public object Value 
    { 
     get 
     { 
      return GetValue(ValueProperty); 
     } 
     set 
     { 
      SetValue(ValueProperty, value); 
     } 
    } 
    #endregion //Value 

    #region Events 
    public event EventHandler ValueChanged; 
    #endregion // Events 

    #region IDisposable Members 

    public void Dispose() 
    { 
     BindingOperations.ClearBinding(this, ValueProperty); 
    } 

    #endregion 
} 
+1

Grazie per l'aiuto. Non so come ho perso questa risorsa! – scuba88

+0

Questa soluzione non sembra funzionare per me, non sto ottenendo l'evento OnPropertyChanged da PropertyChangedNotifier –

4

Una soluzione più leggera per FrameworkElements e FrameworkContentElements è quello di sottoscrivere l'evento Unloaded e rimuovere il gestore. Ciò richiede un delegato non anonimo (UpdateAdorner in tal caso) però:

focusProp.AddValueChanged(m_TextBox, UpdateAdorner); 
m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner);