Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ReactiveUI's BindTo() to update a XAML property generates a warning

I'm trying to update a property of an element in the XAML of a view:

this.WhenAnyValue(x => x.ViewModel.IsEnabled).BindTo(this, x => x.MyButton.IsEnabled);

This works as expected, however, it generates a warning at runtime:

POCOObservableForProperty: rx_bindto_test.MainWindow is a POCO type and won't send change notifications, WhenAny will only return a single value!

I can get rid of the warning by changing the expression to:

this.WhenAnyValue(x => x.ViewModel.IsEnabled).Subscribe(b => MyButton.IsEnabled = b);

but I'm still wondering why it doesn't work properly with BindTo().

Edit: it appears even the regular Bind and OneWayBind generate this warning.

  1. What am I doing wrong here?
  2. And is it really necessary to define ViewModel as a dependency property on the View to be able to observe it? (when I declare it as a regular property on the View, ReactiveUI generates the same POCO warning) I can't simply make it inherit from ReactiveObject because C# doesn't support multiple inheritance.

MainWindow.xaml.cs

public partial class MainWindow : Window, IViewFor<MyViewModel>, IEnableLogger {
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel",
        typeof(MyViewModel), typeof(MainWindow));

    public MyViewModel ViewModel {
        get { return (MyViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    object IViewFor.ViewModel {
        get { return ViewModel; }
        set { ViewModel = (MyViewModel)value; }
    }

    public MainWindow() {
        InitializeComponent();

        this.WhenAnyValue(x => x.ViewModel).BindTo(this, x => x.DataContext);

        this.WhenAnyValue(x => x.ViewModel.IsEnabled).BindTo(this, x => x.MyButton.IsEnabled);

        ViewModel = new MyViewModel();
        ViewModel.IsEnabled = true;
    }
}

MainWindow.xaml

<Window x:Class="rx_bindto_test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="MyButton">My Button</Button>
    </Grid>
</Window>

MyViewModel.cs

public class MyViewModel : ReactiveObject, IEnableLogger {
    private bool isEnabled;

    public bool IsEnabled {
        get { return isEnabled; }
        set { this.RaiseAndSetIfChanged(ref isEnabled, value); }
    }
}
like image 507
reijerh Avatar asked May 20 '15 14:05

reijerh


1 Answers

I think the confusion comes from the fact that you get a warning on "MyButton" resolving, and not on the ViewModel.

MyButton is a "constant" object, w/o any lifecycle (neither INPC nor DependencyObject), hence you can safely ignore this warning.

Alternatively you can register the below extra property resolver, which will behaves like the POCO one (minus the warning) for every internal field of a FrameworkElement, which is pretty close to every control from the XAML (I believe):

Locator.CurrentMutable.Register(() => new CustomPropertyResolver(), typeof(ICreatesObservableForProperty));

public class CustomPropertyResolver : ICreatesObservableForProperty
{
    public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false)
    {
        if (!typeof(FrameworkElement).IsAssignableFrom(type))
            return 0;
        var fi = type.GetTypeInfo().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
          .FirstOrDefault(x => x.Name == propertyName);

        return fi != null ? 2 /* POCO affinity+1 */ : 0;
    }

    public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, bool beforeChanged = false)
    {
        var foo = (FrameworkElement)sender;
        return Observable.Return(new ObservedChange<object, object>(sender, expression), new DispatcherScheduler(foo.Dispatcher))
            .Concat(Observable.Never<IObservedChange<object, object>>());
    }
}
like image 51
Gluck Avatar answered Oct 27 '22 22:10

Gluck