9

Tôi biết tôi cần phải gọi RemoveValueChanged, nhưng tôi đã không thể tìm thấy một nơi đáng tin cậy để gọi điều này. Tôi đang học rằng có lẽ không phải là một.Làm cách nào để khắc phục DependencyPropertyDescriptor AddValueChanged Memory Leak trên AttachedBehavior?

Có vẻ như tôi cần tìm một cách khác để theo dõi thay đổi, sau đó thêm trình xử lý bằng AddValueChanged. Tôi đang tìm lời khuyên về cách tốt nhất để đạt được điều này. Tôi đã nhìn thấy các khuyến nghị của việc sử dụng một PropertyChangedCallback trong PropertyMetadata, nhưng tôi không chắc chắn làm thế nào để làm điều này khi TextBox của tôi và Adorner không tĩnh. Ngoài ra, thuộc tính IsFocused không phải là một DependencyProperty được tạo ra trong lớp của tôi.

Cảm ơn.

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

Trả lời

16

AddValueChanged phụ thuộc kết quả mô tả tài sản trong bộ nhớ bị rò rỉ như bạn đã biết. Vì vậy, như được mô tả here, bạn có thể tạo lớp tùy chỉnh PropertyChangeNotifier để nghe bất kỳ thay đổi thuộc tính phụ thuộc nào.

Hoàn thành triển khai có thể tìm thấy tại đây - PropertyDescriptor AddValueChanged Alternative.


Trích từ liên kết:

lớp này có lợi thế của một thực tế rằng các ràng buộc sử dụng tài liệu tham khảo yếu để quản lý các hiệp hội vì vậy lớp sẽ không nhổ tận gốc các đối tượng người sở hữu thay đổi nó đang theo dõi. Nó cũng sử dụng phương thức W2RWeakReference để duy trì tham chiếu đến đối tượng có thuộc tính mà nó đang xem mà không cần root đối tượng đó. Bằng cách này, bạn có thể duy trì một bộ sưu tập các đối tượng này để bạn có thể hủy thuộc tính thay đổi sau mà không phải lo lắng về việc bộ sưu tập đó khởi động đối tượng có giá trị bạn đang xem.

Cũng vì mục đích hoàn chỉnh câu trả lời, tôi đăng toàn bộ mã ở đây để tránh bất kỳ vấn đề nào trong tương lai.

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

Cảm ơn sự giúp đỡ. Tôi không biết làm thế nào tôi bị mất tài nguyên này! – scuba88

+0

Giải pháp này dường như không hoạt động đối với tôi, tôi không nhận được sự kiện OnPropertyChanged từ PropertyChangedNotifier –

4

Một giải pháp trọng lượng nhẹ hơn cho FrameworkElementsFrameworkContentElements là để đăng ký vào các sự kiện Unloaded và loại bỏ các xử lý. Điều này đòi hỏi một đại biểu không ẩn danh (UpdateAdorner trong trường hợp đó) mặc dù:

focusProp.AddValueChanged(m_TextBox, UpdateAdorner); 
m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner); 
Các vấn đề liên quan