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