12

我知道我需要打电话RemoveValueChanged,但我一直找不到可靠的地方打电话。我正在学习可能没有。

我看起来需要找到一种不同的方法来监视更改,然后使用AddValueChanged. 我正在寻找实现这一目标的最佳方法的建议。我已经看到在 中使用 a 的建议PropertyChangedCallback,但是当我的和不是静态的PropertyMetadata时我不确定如何执行此操作。此外,该属性不是在我的班级中创建的。TextBoxAdornerIsFocusedDependencyProperty

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

2 回答 2

19

AddValueChanged如您所知,依赖属性描述符会导致内存泄漏。因此,如此所述,您可以创建自定义类PropertyChangeNotifier来监听任何依赖属性的变化。

可以在这里找到完整的实现 - PropertyDescriptor AddValueChanged Alternative


引用链接:

这个类利用了绑定使用弱引用来管理关联的事实,因此该类不会根它正在观察的属性更改的对象。它还使用 Wea​​kReference 来维护对正在监视其属性的对象的引用,而无需根该对象。通过这种方式,您可以维护这些对象的集合,以便以后可以取消属性更改,而不必担心该集合会根植您正在查看其值的对象。

同样为了回答的完整性,我在这里发布了完整的代码,以避免将来出现任何腐烂问题。

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
}
于 2014-05-15T19:52:11.167 回答
8

一个更轻量级的解决方案是订阅事件FrameworkElements并删除处理程序。但是,这需要一个非匿名代表(在这种情况下):FrameworkContentElementsUnloadedUpdateAdorner

focusProp.AddValueChanged(m_TextBox, UpdateAdorner);
m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner);
于 2017-07-18T07:16:54.763 回答