9

我知道我需要調用RemoveValueChanged,但是我一直無法找到一個可靠的地方來調用它。我正在學習可能沒有一個。如何修復AttachedBehavior上的DependencyPropertyDescriptor AddValueChanged內存泄漏?

我看起來像我需要找到一種不同的方式來監視更改,然後使用AddValueChanged添加處理程序。我正在尋找實現這一目標的最佳方法。我已經看到在PropertyMetadata中使用PropertyChangedCallback的建議,但我不確定如何在TextBox和Adorner不是靜態時執行此操作。另外,IsFocused屬性不是在我的類中創建的DependencyProperty。

謝謝。

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); 
     } 
    } 
} 
依賴

回答

16

AddValueChanged屬性描述導致內存泄漏,你已經知道了。因此,如here所述,您可以創建自定義類PropertyChangeNotifier以偵聽任何依賴項屬性更改。

完整的實現可以在這裏找到 - PropertyDescriptor AddValueChanged Alternative。從鏈接


報價:

這個類接收的事實,即綁定使用弱 引用管理協會這樣的類就不會根的 對象誰屬性更改是看優勢。它還使用WeakReference來維護對其屬性 正在觀察的對象的引用,而不生成該對象。通過這種方式,您可以維護這些對象的集合,以便您可以稍後解除屬性 的更改,而不用擔心該對象的集合會根據您正在監視其值的對象 。

爲了完整答案的緣故,我在此發佈完整的代碼以避免將來出現任何腐爛問題。

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

感謝您的幫助。我不知道我是如何錯過這個資源的! – scuba88

+0

此解決方案似乎不適用於我,我沒有從PropertyChangedNotifier中獲取OnPropertyChanged事件 –

4

FrameworkElementsFrameworkContentElements更輕便的解決方案是訂閱Unloaded事件和刪除的處理程序。這需要一個非匿名代表(UpdateAdorner在這種情況下):

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