如何修復attachdencypropertydescriptor AddValueChanged內存泄漏?

[英]How can I fix the DependencyPropertyDescriptor AddValueChanged Memory Leak on AttachedBehavior?


I know I need to call RemoveValueChanged, but I have not been able to find a reliable place to call this. I'm learning that there probably isn't one.

我知道我需要調用RemoveValueChanged,但是我還沒有找到一個可靠的地方來調用它。我知道可能沒有。

I looks like I need to find a different way to monitor the change then adding a handler using AddValueChanged. I'm looking for advice on the best way to achieve this. I've seen the recommendation of using a PropertyChangedCallback in the PropertyMetadata, but I'm not sure how to do this when my TextBox and Adorner are not static. Also, the IsFocused property is not a DependencyProperty created in my class.

我似乎需要找到一種不同的方法來監視更改,然后使用AddValueChanged添加一個處理程序。我在尋找實現這一目標的最佳方法。我看到了在PropertyMetadata中使用PropertyChangedCallback的建議,但是當我的TextBox和Adorner不是靜態的時候,我不確定如何做到這一點。而且,isfocus屬性不是在我的類中創建的依賴項屬性。

Thanks.

謝謝。

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

2 个解决方案

#1


16  

AddValueChanged of dependency property descriptor results in memory leak as you already know. So, as described here, you can create custom class PropertyChangeNotifier to listen to any dependency property changes.

附件屬性描述符的AddValueChanged會導致內存泄漏。因此,正如這里描述的,您可以創建自定義類PropertyChangeNotifier來偵聽任何依賴項屬性更改。

Complete implementation can be found here - PropertyDescriptor AddValueChanged Alternative.

完整的實現可以在這里找到——PropertyDescriptor AddValueChanged替代方案。


Quote from the link:

引用鏈接:

This class takes advantage of the fact that bindings use weak references to manage associations so the class will not root the object who property changes it is watching. It also uses a WeakReference to maintain a reference to the object whose property it is watching without rooting that object. In this way, you can maintain a collection of these objects so that you can unhook the property change later without worrying about that collection rooting the object whose values you are watching.

這個類利用了綁定使用弱引用來管理關聯這一事實,這樣類就不會根起正在監視的屬性更改的對象。它還使用一個弱引用來維護對對象的引用,該對象的屬性是在不支持該對象的情況下進行監視的。通過這種方式,您可以維護這些對象的集合,以便您可以在以后解開屬性更改的鈎子,而不必擔心這個集合會影響到您正在監視的對象的值。

Also for sake of completeness of answer I am posting complete code here to avoid any rot issue in future.

為了完整的回答,我在這里發布了完整的代碼,以避免將來出現任何問題。

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
}

#2


4  

A more lightweight solution for FrameworkElements and FrameworkContentElements is to subscribe to the Unloaded event and remove the handler. This requires a non-anonymous delegate (UpdateAdorner in that case) though:

一個針對FrameworkElements和frameworkcontentelement的更輕量級的解決方案是訂閱卸載事件並刪除處理程序。這需要一個非匿名委托(在本例中為UpdateAdorner)):

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

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2014/05/15/7253fdae8bb5345efbe092bd816eeb12.html



 
粤ICP备14056181号  © 2014-2021 ITdaan.com