如何修复 AttachedBehavior 上的 DependencyPropertyDescriptor AddValueChanged 内存泄漏?
Posted
技术标签:
【中文标题】如何修复 AttachedBehavior 上的 DependencyPropertyDescriptor AddValueChanged 内存泄漏?【英文标题】:How can I fix the DependencyPropertyDescriptor AddValueChanged Memory Leak on AttachedBehavior? 【发布时间】:2014-07-04 03:16:31 【问题描述】:我知道我需要打电话给RemoveValueChanged
,但我一直找不到可靠的地方打电话。我知道可能没有。
我似乎需要找到一种不同的方法来监控更改,然后使用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);
【问题讨论】:
【参考方案1】:FrameworkElements
和FrameworkContentElements
的更轻量级的解决方案是订阅Unloaded
事件并删除处理程序。这需要一个非匿名委托(在这种情况下为UpdateAdorner
):
focusProp.AddValueChanged(m_TextBox, UpdateAdorner);
m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner);
【讨论】:
这不是正确的方法。如果您将“TextBox”放在弹出窗口或类似内容中,您会看到“TextBox”的 Unloaded 事件将在弹出窗口关闭时触发。 如果您在Loaded
事件中添加处理程序,这是正确的方法。 Loaded
和 Unloaded
应始终成对。【参考方案2】:
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
【讨论】:
感谢您的帮助。我不知道我是怎么错过这个资源的! 这个解决方案似乎对我不起作用,我没有从 PropertyChangedNotifier 获得 OnPropertyChanged 事件 重要的是要注意,这个通知类在超出范围时会停止工作,因此您必须将实例保留在私有字段或其他东西中。以上是关于如何修复 AttachedBehavior 上的 DependencyPropertyDescriptor AddValueChanged 内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章