ComponentModel PropertyChanged绑定不适用于TransparentProxy

时间:2018-04-05 15:49:59

标签: c# wpf reflection

我使用RealProxy创建了一个' Aspect'如果属性被标记为通知,则触发PropertyChanged INotifyPropertyChanged事件。然而,这并不起作用。 调试时,我可以看到事件被触发,ComponentModel位于事件的InvocationList中,但我的UI没有得到更新。我已经在我的视图模型中迷上了这个事件,它会按预期触发,但仍然没有UI更新。

以下是一些用于演示此问题的代码;

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)
        {
            try
            {
                var methodCall = msg as IMethodCallMessage;
                if (methodCall == null) throw new ArgumentException(nameof(msg));

                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:local="clr-namespace:SOSample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:TestViewModel}">
            <local:TestView />
        </DataTemplate>
    </Window.Resources>

    <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>

任何人都可以告诉我出了什么问题,我该如何修理它,或者让我再也不会发布SO了?

0 个答案:

没有答案