diff --git a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs index c638fe56e6..7e7417d2f5 100644 --- a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs +++ b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -17,6 +18,9 @@ namespace Avalonia.Data.Converters .GetProperty(nameof(CultureInfo.CurrentCulture), BindingFlags.Public | BindingFlags.Static); readonly Func canExecute; readonly Action execute; + readonly WeakPropertyChangedProxy weakPropertyChanged; + readonly PropertyChangedEventHandler propertyChangedEventHandler; + readonly string[] dependencyProperties; public MethodToCommandConverter(Delegate action) { @@ -45,28 +49,28 @@ namespace Avalonia.Data.Converters else { canExecute = CreateCanExecute(target, canExecuteMethod); - var dependencyProperties = canExecuteMethod + dependencyProperties = canExecuteMethod .GetCustomAttributes(typeof(Metadata.DependsOnAttribute), true) .OfType() .Select(x => x.Name) .ToArray(); if (dependencyProperties.Any() - && target is System.ComponentModel.INotifyPropertyChanged inpc) + && target is INotifyPropertyChanged inpc) { - System.ComponentModel.PropertyChangedEventHandler invalidateCanExecuteHandler = (s, e) => - { - if (string.IsNullOrWhiteSpace(e.PropertyName) - || dependencyProperties.Contains(e.PropertyName)) - { - Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) - , Threading.DispatcherPriority.Input); - } - }; - inpc.PropertyChanged += invalidateCanExecuteHandler; + propertyChangedEventHandler = OnPropertyChanged; + weakPropertyChanged = new WeakPropertyChangedProxy(inpc, propertyChangedEventHandler); } } + } - + void OnPropertyChanged(object sender,PropertyChangedEventArgs args) + { + if (string.IsNullOrWhiteSpace(args.PropertyName) + || dependencyProperties?.Contains(args.PropertyName) == true) + { + Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) + , Threading.DispatcherPriority.Input); + } } #pragma warning disable 0067 @@ -178,5 +182,49 @@ namespace Avalonia.Data.Converters .Lambda>(call, parameter) .Compile(); } + + + internal class WeakPropertyChangedProxy + { + readonly WeakReference _listener = new WeakReference(null); + readonly PropertyChangedEventHandler _handler; + internal WeakReference Source { get; } = new WeakReference(null); + + public WeakPropertyChangedProxy() + { + _handler = new PropertyChangedEventHandler(OnPropertyChanged); + } + + public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this() + { + SubscribeTo(source, listener); + } + + public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener) + { + source.PropertyChanged += _handler; + + Source.SetTarget(source); + _listener.SetTarget(listener); + } + + public void Unsubscribe() + { + if (Source.TryGetTarget(out INotifyPropertyChanged source) && source != null) + source.PropertyChanged -= _handler; + + Source.SetTarget(null); + _listener.SetTarget(null); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_listener.TryGetTarget(out var handler) && handler != null) + handler(sender, e); + else + Unsubscribe(); + } + + } } }