Lắng nghe những thay đổi của thuộc tính phụ thuộc


80

Có cách nào để lắng nghe những thay đổi của a DependencyProperty? Tôi muốn được thông báo và thực hiện một số hành động khi giá trị thay đổi nhưng tôi không thể sử dụng ràng buộc. Nó là một DependencyPropertylớp khác.


Tại sao bạn nói bạn không thể sử dụng ràng buộc?
Robert Rossney

Câu trả lời:


59

Nếu đó là một DependencyPropertylớp riêng biệt, cách dễ nhất là liên kết một giá trị với nó và lắng nghe những thay đổi trên giá trị đó.

Nếu DP là DP mà bạn đang triển khai trong lớp của riêng mình, thì bạn có thể đăng ký một PropertyChangedCallback khi tạo DependencyProperty. Bạn có thể sử dụng điều này để lắng nghe những thay đổi của tài sản.

Nếu bạn đang làm việc với một lớp con, bạn có thể sử dụng OverrideMetadata để thêm PropertyChangedCallbackDP của riêng bạn vào DP sẽ được gọi thay vì bất kỳ lớp gốc nào.


11
Theo MSDN và kinh nghiệm của tôi, Một số đặc điểm (của siêu dữ liệu được cung cấp) ... Những đặc điểm khác, chẳng hạn như PropertyChangedCallback, được kết hợp. Vì vậy, PropertyChangedCallback của riêng bạn sẽ được gọi ngoài các lệnh gọi lại hiện có, không phải thay vì .
Marcel Gosselin,

1
liên kết chết? bây giờ có phải là msdn.microsoft.com/library/ms745795%28v=vs.100%29.aspx không?
Simon K.

Có thể thêm phần làm rõ này vào câu trả lời không? Tôi nghĩ rằng OverrideMetadata sẽ thay thế các lệnh gọi lại của cấp độ gốc và điều này khiến tôi không thể sử dụng nó.
tên người dùng

1
Tôi đồng ý, điều này không rõ ràng lắm: "cách dễ nhất là ràng buộc một giá trị với nó, và lắng nghe những thay đổi trên giá trị đó". Một ví dụ sẽ rất hữu ích
UuDdLrLrSs

154

Phương pháp này chắc chắn bị thiếu ở đây:

DependencyPropertyDescriptor
    .FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
    .AddValueChanged(radioButton, (s,e) => { /* ... */ });

67
Hãy hết sức cẩn thận với điều này vì nó có thể dễ dàng dẫn đến rò rỉ bộ nhớ! Luôn xóa trình xử lý một lần nữa sử dụngdescriptor.RemoveValueChanged(...)
CodeMonkey

7
xem chi tiết và một cách tiếp cận khác (xác định tài sản phụ thuộc mới + binding) tại agsmith.wordpress.com/2008/04/07/...
Lu55

2
Điều này hoạt động cho WPF (đó là câu hỏi này là gì). Nếu bạn hạ cánh ở đây để tìm kiếm giải pháp lưu trữ cửa sổ, bạn cần sử dụng thủ thuật ràng buộc. Tìm thấy bài đăng trên blog này có thể giúp ích: blog.msdn.com/b/flaviencharlon/archive/2012/12/07/… Có lẽ cũng hoạt động với WPF (như đã đề cập trong câu trả lời ở trên).
Gordon

2
@Todd: Tôi nghĩ rằng rò rỉ là ngược lại, chế độ xem có thể giữ cho mô hình xem của bạn tồn tại do tham chiếu đến trình xử lý. Khi chế độ xem đang hủy bỏ, đăng ký cũng sẽ biến mất. Tôi nghĩ mọi người hơi hoang tưởng về việc rò rỉ từ các trình xử lý sự kiện, thường thì nó không phải là một vấn đề.
HB

4
@HB Trong trường hợp DependencyPropertyDescriptornày có danh sách tĩnh của tất cả các trình xử lý trong ứng dụng, vì vậy mọi đối tượng được tham chiếu trong trình xử lý sẽ bị rò rỉ. Nó không hoạt động giống như sự kiện thông thường.
ma cà rồng

19

Tôi đã viết lớp tiện ích này:

  • Nó cung cấp cho DependencyPropertyChangedEventArgs với giá trị cũ và mới.
  • Nguồn được lưu trữ trong một tham chiếu yếu trong ràng buộc.
  • Không chắc liệu việc tiết lộ Binding & BindingExpression có phải là một ý kiến ​​hay hay không.
  • Không rò rỉ.
using System;
using System.Collections.Concurrent;
using System.Windows;
using System.Windows.Data;

public sealed class DependencyPropertyListener : DependencyObject, IDisposable
{
    private static readonly ConcurrentDictionary<DependencyProperty, PropertyPath> Cache = new ConcurrentDictionary<DependencyProperty, PropertyPath>();

    private static readonly DependencyProperty ProxyProperty = DependencyProperty.Register(
        "Proxy",
        typeof(object),
        typeof(DependencyPropertyListener),
        new PropertyMetadata(null, OnSourceChanged));

    private readonly Action<DependencyPropertyChangedEventArgs> onChanged;
    private bool disposed;

    public DependencyPropertyListener(
        DependencyObject source, 
        DependencyProperty property, 
        Action<DependencyPropertyChangedEventArgs> onChanged = null)
        : this(source, Cache.GetOrAdd(property, x => new PropertyPath(x)), onChanged)
    {
    }

    public DependencyPropertyListener(
        DependencyObject source, 
        PropertyPath property,
        Action<DependencyPropertyChangedEventArgs> onChanged)
    {
        this.Binding = new Binding
        {
            Source = source,
            Path = property,
            Mode = BindingMode.OneWay,
        };
        this.BindingExpression = (BindingExpression)BindingOperations.SetBinding(this, ProxyProperty, this.Binding);
        this.onChanged = onChanged;
    }

    public event EventHandler<DependencyPropertyChangedEventArgs> Changed;

    public BindingExpression BindingExpression { get; }

    public Binding Binding { get; }

    public DependencyObject Source => (DependencyObject)this.Binding.Source;

    public void Dispose()
    {
        if (this.disposed)
        {
            return;
        }

        this.disposed = true;
        BindingOperations.ClearBinding(this, ProxyProperty);
    }

    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var listener = (DependencyPropertyListener)d;
        if (listener.disposed)
        {
            return;
        }

        listener.onChanged?.Invoke(e);
        listener.OnChanged(e);
    }

    private void OnChanged(DependencyPropertyChangedEventArgs e)
    {
        this.Changed?.Invoke(this, e);
    }
}

using System;
using System.Windows;

public static class Observe
{
    public static IDisposable PropertyChanged(
        this DependencyObject source,
        DependencyProperty property,
        Action<DependencyPropertyChangedEventArgs> onChanged = null)
    {
        return new DependencyPropertyListener(source, property, onChanged);
    }
}

nếu ràng buộc là OneWay, tại sao bạn lại đặt UpdateSourceTrigger?
Maslow

6

Có nhiều cách để đạt được điều này. Đây là một cách để chuyển đổi một thuộc tính phụ thuộc thành một thuộc tính có thể quan sát được, để nó có thể được đăng ký bằng cách sử dụng System.Reactive :

public static class DependencyObjectExtensions
{
    public static IObservable<EventArgs> Observe<T>(this T component, DependencyProperty dependencyProperty)
        where T:DependencyObject
    {
        return Observable.Create<EventArgs>(observer =>
        {
            EventHandler update = (sender, args) => observer.OnNext(args);
            var property = DependencyPropertyDescriptor.FromProperty(dependencyProperty, typeof(T));
            property.AddValueChanged(component, update);
            return Disposable.Create(() => property.RemoveValueChanged(component, update));
        });
    }
}

Sử dụng

Hãy nhớ loại bỏ các đăng ký để tránh rò rỉ bộ nhớ:

public partial sealed class MyControl : UserControl, IDisposable 
{
    public MyControl()
    {
        InitializeComponent();

        // this is the interesting part 
        var subscription = this.Observe(MyProperty)
                               .Subscribe(args => { /* ... */}));

        // the rest of the class is infrastructure for proper disposing
        Subscriptions.Add(subscription);
        Dispatcher.ShutdownStarted += DispatcherOnShutdownStarted; 
    }

    private IList<IDisposable> Subscriptions { get; } = new List<IDisposable>();

    private void DispatcherOnShutdownStarted(object sender, EventArgs eventArgs)
    {
        Dispose();
    }

    Dispose(){
        Dispose(true);
    }

    ~MyClass(){
        Dispose(false);
    }

    bool _isDisposed;
    void Dispose(bool isDisposing)
    {
        if(_disposed) return;

        foreach(var subscription in Subscriptions)
        {
            subscription?.Dispose();
        }

        _isDisposed = true;
        if(isDisposing) GC.SupressFinalize(this);
    }
}

4

Bạn có thể kế thừa Điều khiển mà bạn đang cố gắng lắng nghe và sau đó có quyền truy cập trực tiếp vào:

protected void OnPropertyChanged(string name)

Không có nguy cơ rò rỉ bộ nhớ.

Đừng sợ các kỹ thuật OO tiêu chuẩn.


1

Nếu đó là trường hợp, Một hack. Bạn có thể giới thiệu một lớp Tĩnh với một DependencyProperty. Lớp nguồn của bạn cũng liên kết với dp đó và lớp đích của bạn cũng liên kết với DP.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.