Browse Source

Implemented WeakPropertyChangedProxy to avoid memory leak

pull/4095/head
Giuseppe Lippolis 6 years ago
parent
commit
9270f58a9f
  1. 74
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

74
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<object, bool> canExecute;
readonly Action<object> 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<Metadata.DependsOnAttribute>()
.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<Func<object, bool>>(call, parameter)
.Compile();
}
internal class WeakPropertyChangedProxy
{
readonly WeakReference<PropertyChangedEventHandler> _listener = new WeakReference<PropertyChangedEventHandler>(null);
readonly PropertyChangedEventHandler _handler;
internal WeakReference<INotifyPropertyChanged> Source { get; } = new WeakReference<INotifyPropertyChanged>(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();
}
}
}
}

Loading…
Cancel
Save