XAML中用于绑定issubject 的标记扩展

本文关键字:扩展 string 用于 绑定 issubject XAML | 更新日期: 2023-09-27 18:09:29

如果我有以下视图模型

class Foo : INotifyPropertyChanged {
    ISubject<string> Name { ... }
} 

和一些想象中的XAML代码

<TextBox Text="{my:Subscribe Path=Name}/>

我希望双向绑定的行为

  • 主题。在UI
  • 中更新文本框时调用onNext。
  • 文本框通过订阅主题更新。订阅

由于WPF只直接支持INPC,我的想法是创建一个代理INPC对象通过一个标记扩展名

class WPFSubjectProxy : INotifyPropertyChanged{
    string Value { ... }
}

代理将连接到主题,如下

subject.Subscribe(v=>proxy.Value=v);
proxy
    .WhenAny(p=>p.Value, p.Value)
    .Subscribe(v=>subject.OnNext(v))

注意WhenAny是用于订阅的ReactiveUI helperINPC事件。

但是我需要生成一个绑定并返回通过标记扩展名

我知道我想做什么,但不知道该怎么做

XAML中用于绑定issubject <string>的标记扩展

如果没有看到你在挣扎什么,很难说,但也许这有帮助?

编辑

我(bradgonesurfing)想出的解决方案是下面感谢指针在正确答案

节点

和实现代码。它依赖于ReactiveUI和私有库中的辅助函数,用于将issubject绑定到INPC支持对象上的可变属性

using ReactiveUI.Subjects;
using System;
using System.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
    [MarkupExtensionReturnType(typeof(BindingExpression))]
    public class SubscriptionExtension : MarkupExtension
    {
        [ConstructorArgument("path")]
        public PropertyPath Path { get; set; }
        public SubscriptionExtension() { }
        public SubscriptionExtension(PropertyPath path)
        {
            Path = path;
        }
        class Proxy : ReactiveObject
        {
            string _Value;
            public string Value
            {
                get { return _Value; }
                set { this.RaiseAndSetIfChanged(value); }
            }
        }
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var pvt = serviceProvider as IProvideValueTarget;
            if (pvt == null)
            {
                return null;
            }
            var frameworkElement = pvt.TargetObject as FrameworkElement;
            if (frameworkElement == null)
            {
                return this;
            }

            object propValue = GetProperty(frameworkElement.DataContext, Path.Path);
            var subject = propValue as ISubject<string>;
            var proxy = new Proxy();
            Binding binding = new Binding() 
            {
                Source = proxy,
                Path = new System.Windows.PropertyPath("Value")
            };
            // Bind the subject to the property via a helper ( in private library )
            var subscription = subject.ToMutableProperty(proxy, x => x.Value);
            // Make sure we don't leak subscriptions
            frameworkElement.Unloaded += (e,v) => subscription.Dispose(); 
            return binding.ProvideValue(serviceProvider);
        }
        private static object GetProperty(object context, string propPath)
        {
            object propValue = propPath
                .Split('.')
                .Aggregate(context, (value, name)
                    => value.GetType() 
                        .GetProperty(name)
                        .GetValue(value, null));
            return propValue;
        }
    }
}