I have used RealProxy
to create an 'Aspect' of sorts to fire the PropertyChanged
event of INotifyPropertyChanged
if the property is marked to notify. This, however, doesn't work. When debugging, I can see that the event is triggered and that the ComponentModel is in the InvocationList of the event, but my UI does not get updated. I have hooked into the event in my view model and it fires there as expected, but still no UI updates.
Here is some code to demonstrate the issue;
C#
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Windows;
using System.Windows.Controls;
namespace SOSample
{
public abstract class NotifyBase : MarshalByRefObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[AttributeUsage(AttributeTargets.Property)]
public class NotifyPropertyChangedAttribute : Attribute { }
public class PropertyChangeAspect<T> : RealProxy where T : NotifyBase
{
private readonly T _decorated = default(T);
private PropertyChangeAspect(T decorated) : base(typeof(T))
{
_decorated = decorated;
}
public static T Create(T decorated)
{
var aspect = new PropertyChangeAspect<T>(decorated);
return (T)aspect.GetTransparentProxy();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
if (methodCall == null) throw new ArgumentException(nameof(msg));
try
{
var result = typeof(T).InvokeMember(methodCall.MethodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, _decorated, methodCall.Args);
var methodInfo = methodCall.MethodBase as MethodInfo;
if (methodInfo != null)
{
var prop = FindProperty(methodInfo.Name);
var attr = prop?.GetCustomAttribute<NotifyPropertyChangedAttribute>(true);
if (attr != null) NotifyPropertyChanged(prop.Name);
}
return new ReturnMessage(result, methodCall.Args, methodCall.Args.Length, methodCall.LogicalCallContext, methodCall);
}
catch (Exception ex)
{
return new ReturnMessage(ex, methodCall);
}
}
private PropertyInfo FindProperty(string name)
{
var props = _decorated.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
var prop = props.FirstOrDefault(p => (p.SetMethod?.Name ?? string.Empty).Equals(name));
return prop;
}
private void NotifyPropertyChanged(string propName)
{
var method = _decorated.GetType().GetMethod("NotifyPropertyChanged", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
method.Invoke(_decorated, new object[] { propName });
}
}
public class TestViewModel : NotifyBase
{
[NotifyPropertyChanged]
public string Name { get; set; }
// If I switch to this and don't use the proxy, it works fine
//public string _name;
//public string Name
//{
// get => _name;
// set { _name = value; NotifyPropertyChanged(); }
//}
public TestViewModel()
{
PropertyChanged += PropChanged;
}
private void PropChanged(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine(e.PropertyName); // This writes to the output window, so I know the event is firing
}
}
public partial class MainWindow : Window
{
public NotifyBase ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
//ViewModel = new TestViewModel(); // Using this works fine
ViewModel = PropertyChangeAspect<TestViewModel>.Create(new TestViewModel()); // Using the proxy is problematic
DataContext = this;
}
}
public partial class TestView : UserControl
{
public TestView()
{
InitializeComponent();
}
}
}
Xaml
<Window x:Class="SOSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<ContentPresenter Content="{Binding ViewModel}" />
</Window>
<UserControl x:Class="SOSample.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Name}" Margin="5" />
<TextBox Grid.Row="1" Text="{Binding Name}" Margin="5" />
</Grid>
</UserControl>
Can anyone tell me what is wrong, how I can fix it, or ask me to never post on SO again?
Aucun commentaire:
Enregistrer un commentaire