종속성 속성의 변경 사항 수신


80

의 변경 사항을들을 수있는 방법이 DependencyProperty있습니까? 값이 변경되면 알림을 받고 몇 가지 작업을 수행하고 싶지만 바인딩을 사용할 수 없습니다. 그것은이다 DependencyProperty다른 클래스의.


바인딩을 사용할 수없는 이유는 무엇입니까?
Robert Rossney 2011 년

답변:


59

그것이 있다면 DependencyProperty별도의 클래스의, 가장 쉬운 방법은에 값을 결합, 그 값의 변경을들을 것입니다.

DP가 자체 클래스에서 구현하는 경우 .NET Framework 를 만들 때 PropertyChangedCallback을 등록 할 수 있습니다 DependencyProperty. 이를 사용하여 속성의 변경 사항을 수신 할 수 있습니다.

하위 클래스로 작업하는 경우 OverrideMetadata 를 사용 하여 PropertyChangedCallback원본 대신 호출 될 DP에 자신 을 추가 할 수 있습니다 .


11
MSDN 과 내 경험 에 따르면 (제공된 메타 데이터의) 일부 특성 ... PropertyChangedCallback과 같은 기타 특성이 결합됩니다. 따라서 자체 PropertyChangedCallback이 대신 기존 콜백 에 추가 로 호출 됩니다 .
Marcel Gosselin 2011


이 설명을 답변에 추가 할 수 있습니까? OverrideMetadata가 부모의 콜백을 대체 할 것이라고 생각했고 이로 인해 사용을 방해했습니다.
사용자 이름

1
나는 이것이 매우 명확하지 않다는 데 동의합니다. "가장 쉬운 방법은 값을 여기에 묶고 그 값에 대한 변화를 듣는 것입니다". 예가 매우 도움이 될 것입니다
UuDdLrLrSs

154

이 방법은 여기에서 확실히 누락되었습니다.

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

67
메모리 누수를 쉽게 유발할 수 있으므로 매우 조심하십시오! 항상 사용하여 다시 핸들러를 제거descriptor.RemoveValueChanged(...)
CodeMonkey


2
이것은 WPF에서 작동합니다 (이 질문의 대상입니다). Windows 스토어 솔루션을 찾고있는 경우 바인딩 트릭을 사용해야합니다. 도움이 될 수있는이 블로그 게시물을 찾았습니다. blogs.msdn.com/b/flaviencharlon/archive/2012/12/07/… 아마도 WPF에서도 작동합니다 (위의 답변에서 언급했듯이).
Gordon

2
@Todd : 누수가 반대의 경우라고 생각합니다. 뷰는 핸들러에 대한 참조로 인해 뷰 모델을 살아있게 유지할 수 있습니다. 뷰가 폐기 될 때 구독도 어쨌든 사라져야합니다. 사람들은 내가 생각하기에 이벤트 핸들러의 누출에 대해 너무 편집증 적이며 일반적으로 문제가되지 않습니다.
HB

4
@HB이 경우 DependencyPropertyDescriptor응용 프로그램의 모든 핸들러에 대한 정적 목록이 있으므로 핸들러에서 참조되는 모든 객체가 누출됩니다. 일반적인 이벤트처럼 작동하지 않습니다.
ghord

19

이 유틸리티 클래스를 작성했습니다.

  • 이전 및 새 값으로 DependencyPropertyChangedEventArgs를 제공합니다.
  • 소스는 바인딩의 약한 참조에 저장됩니다.
  • Binding & BindingExpression을 노출하는 것이 좋은 생각인지 확실하지 않습니다.
  • 누출이 없습니다.
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);
    }
}

바인딩이 OneWay 인 경우 UpdateSourceTrigger를 설정하는 이유는 무엇입니까?
Maslow

6

이를 달성하는 방법에는 여러 가지가 있습니다. 다음은 System.Reactive 를 사용하여 구독 할 수 있도록 종속 속성을 Observable로 변환하는 방법입니다 .

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

용법

메모리 누수를 방지하려면 구독을 폐기해야합니다.

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

듣고 자하는 컨트롤을 상속받은 다음 다음에 직접 액세스 할 수 있습니다.

protected void OnPropertyChanged(string name)

메모리 누수의 위험이 없습니다.

표준 OO 기술을 두려워하지 마십시오.


1

그렇다면 One hack. .NET Framework를 사용하여 정적 클래스를 도입 할 수 DependencyProperty있습니다. 소스 클래스도 해당 dp에 바인딩하고 대상 클래스도 DP에 바인딩합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.