XAML markup extension for binding to ISubject <string>
If I have the following presentation model
class Foo : INotifyPropertyChanged { ISubject<string> Name { ... } } and some supposed XAML code
<TextBox Text="{my:Subscribe Path=Name}/> I want the two-way binding to behave so that
- Subject.onNext is called when the text field is updated in the user interface.
- the text field is updated by subscribing to Subject.Subscribe
Since WPF only supports INPC, my idea is to create an INPC proxy object through a markup extension
class WPFSubjectProxy : INotifyPropertyChanged{ string Value { ... } } The proxy server will connect to the object this way
subject.Subscribe(v=>proxy.Value=v); proxy .WhenAny(p=>p.Value, p.Value) .Subscribe(v=>subject.OnNext(v)) Note. When Any is a ReactiveUI helper to subscribe to INPC Events.
But then I will need to create a binding and return that through the markup extension.
I know what I want, but I canβt understand the Magic of markup extension to bring it all together.
Itβs hard to say, without seeing specifically what you are struggling with, but maybe this helps?
EDIT
The solution I (bradgonesurfing) appeared below, thanks to the pointer in assigned the correct answer.
Knots
and implementing code. It has a dependency on ReactiveUI and a helper function in a private library to bind ISubject to a mutable property on a supporting INPC object
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; } } }