I'm getting a warning in my log about POCO objects only returning a single value with WhenAny

361 views
Skip to first unread message

Brad Phelan

unread,
May 15, 2014, 10:48:01 AM5/15/14
to reacti...@googlegroups.com
I see the log error

POCOObservableForProperty: WeinCad.Controls.View.Moineau3DMeshingView is a POCO type and won't send change notifications, WhenAny will only return a single value!

However Moineau3DMeshingView is not a POCO type it is a dependency object, an instance of an XAML user control. 

Is this a spurious warning?

Paul Betts

unread,
May 15, 2014, 11:34:24 AM5/15/14
to reacti...@googlegroups.com
Hey Brad,

Is it possible that you registered a custom service locator and it's not
working / doesn't have the default RxUI types registered?

--
Paul Betts <pa...@paulbetts.org>
"What's money? A man is a success if he gets up in the morning and goes to bed
at night and in between does what he wants to do." - Bob Dylan

On Thu, May 15, 2014 at 07:48:01AM -0700, Brad Phelan wrote:
> I see the log error
>
> POCOObservableForProperty: WeinCad.Controls.View.*Moineau3DMeshingView *is
> a POCO type and won't send change notifications, WhenAny will only return a
> single value!
>
> However *Moineau3DMeshingView *is not a POCO type it is a dependency

Brad Phelan

unread,
May 15, 2014, 11:43:17 AM5/15/14
to reacti...@googlegroups.com
Yeah I'm using Ninject. I setup Ninject like


using Ninject;
using ReactiveUI;
using System;
using System.Collections.Generic;

namespace WeinCad.Controls
{
    public class NinjectDependencyResolver : IMutableDependencyResolver
    {
        IKernel _kernel;

        public NinjectDependencyResolver(IKernel kernel)
        {
            _kernel = kernel;
        }

        public void Register(Func<object> factory, Type serviceType, string contract = null)
        {
            var r = _kernel.Bind(serviceType).ToMethod(context => factory());
            if (contract!=null)
            {
                r.Named(contract);
            }
        }

        public object GetService(Type serviceType, string contract = null)
        {
            if (contract!=null)
            {
                return _kernel.Get(serviceType, contract);
            }
            else
            {
                return _kernel.Get(serviceType);
            }
        }

        public IEnumerable<object> GetServices(Type serviceType, string contract = null)
        {
            if (contract!=null)
            {
                return _kernel.GetAll(serviceType, contract);
            }
            else
            {
                return _kernel.GetAll(serviceType);
            }
        }

        public void Dispose()
        {
            _kernel.Dispose();
        }
    }

    public class NinjectToRx
    {
        public static IKernel SetupNinject(IKernel testKernel = null)
        {
            var kernel = testKernel ?? CreateStandardKernel();

            // Set up NInject to do DI
            RxApp.MutableResolver = new NinjectDependencyResolver(kernel);
            //RxApp.Initialize();
            RxApp.MutableResolver.InitializeResolver();

            return kernel;
        }

        public static IKernel CreateStandardKernel()
        {
            return new StandardKernel();
        }

    }
}

and then call this function in my AppBootstrapper.



using System;
using Ninject;
using ReactiveUI;
using ReactiveUI.Ext;
using WeinCad.Controls.View;
using WeinCad.Controls.ViewModel;
using WeinCad.Data;
using Weingartner.Controls;
using Weingartner.Lens;
using Weingartner.Numerics.Pump;

namespace WeinCad.Controls
{
    /// <summary>
    /// Singleton resources for the application
    /// </summary>
    public class ApplicationResources
    {
        /// <summary>
        /// The main undo stack for the application
        /// </summary>
        public MoineauSaveLoadViewModel MoineauSaveLoadViewModel { get; private set; }

        /// <summary>
        /// The main undo stack for the settings
        /// </summary>
        public UndoStack<SettingsType> RootSettings { get; private set; }


        public ApplicationResources(IIOService io)
        {
            // Set up our data models
            MoineauSaveLoadViewModel = new MoineauSaveLoadViewModel
                ( io
                , WeinCadFile.Default
                , WeinCadFile.Load
                , (stream, o) => o.ToJson(stream)
                );

            RootSettings = SettingsStorage.Create();

        }
    }


    public class AppBootstrapper : ReactiveObject
    {

        public AppBootstrapper(IKernel testKernel = null, IRoutingState testRouter = null)
        {

            var kernel = NinjectToRx.SetupNinject();
            kernel.Bind<IViewFor<MoineauPumpFormViewModel>>().To<MoineauPumpFormView>();
            kernel.Bind<IViewFor<Moineau3DMeshingViewModel>>().To<Moineau3DMeshingView>();
            kernel.Bind<IViewFor<MoineauManufacturingFormViewModel>>().To<MoineauManufacturingFormView>();
            kernel.Bind<IViewFor<MoineauMillingViewModel>>().To<MoineauMillingView>();
            kernel.Bind<IViewFor<DummyViewModel>>().To<DummyView>();
            kernel.Bind<IViewFor<MoineauPumpCorrectionsViewModel>>().To<MoineauPumpCorrectionsWindow>();
            kernel.Bind<IViewFor<MoineauSaveLoadViewModel>>().To<MoineauSaveLoadView>();
            kernel.Bind<IViewFor<MeasuringViewModel>>().To<MeasuringView>();
            kernel.Bind<IViewFor<MoineauDesignViewModel>>().To<MoineauDesignView>();
            kernel.Bind<IIOService>().To<IOService>();
            kernel.Bind<ApplicationResources>().ToSelf().InSingletonScope();
            kernel.Rebind<ILogger>().To<ConsoleLogger>().InSingletonScope();

            // Bind the units settings into the container
            var unitsSettings = RxApp.DependencyResolver.GetService<ApplicationResources>()
                .RootSettings
                .Focus(x => x.MeasurementSettings);

            kernel.Bind<ILens<MeasurementSettings>>().ToMethod(context => unitsSettings);

            // TODO: This is a good place to set up any other app 
            // startup tasks, like setting the logging level
            LogHost.Default.Level = LogLevel.Info;

        }
    }

    public class ConsoleLogger : ILogger
    {
        private LogLevel _Level;

        public void Write(string message, LogLevel logLevel)
        {
            if(logLevel>=Level)
                Console.WriteLine(message);
        }


        public LogLevel Level
        {
            get
            {
                return _Level;
            }
            set
            {
                _Level = value;
            }
        }
    }
}


It is likely that I've been too clever. Can you spot what I might have done wrong?
 
 

Paul Betts

unread,
May 15, 2014, 11:52:25 AM5/15/14
to reacti...@googlegroups.com
Hey Brad,

My psychic debugger is saying that NInject doesn't allow multiple
registrations to the same type without some sort of distinguishing name, so
when RxUI registers all of its ICreatesObservableForPropertys, it only really
ends up registering one.

What happens when you call
RxApp.MutableLocator.GetServices<ICreatesObservableForProperty>() - does
DepObjObservableForProperty show up in the list?

--
Paul Betts <pa...@paulbetts.org>
"This is where I am great. I am great at being very, very stubborn and
ignoring all sorts of reasons why you should change your goal, reasons that
many other people will be susceptible to. Many people want to be on the
winning side. I didnt give a damn about that. I wanted to be on the side that
was right, and even if I didnt win, at least I was going to give it a good
try." - RMS

Brad Phelan

unread,
May 15, 2014, 12:02:37 PM5/15/14
to reacti...@googlegroups.com
I thought that might be the case too so I just ran my debugger on that.and got

RxApp.MutableResolver.GetServices(typeof(ReactiveUI.ICreatesObservableForProperty)).ToList()
Count = 4
    [0]: {ReactiveUI.INPCObservableForProperty}
    [1]: {ReactiveUI.IRNPCObservableForProperty}
    [2]: {ReactiveUI.POCOObservableForProperty}
    [3]: {ReactiveUI.Xaml.DependencyObjectObservableForProperty}

However!!  the DP resolver is in last place so if the fallback mechanism relies on the ordering of Ninject call returning rather than the ordering of when you put them in there would be a problem.

B


Brad Phelan

unread,
May 15, 2014, 12:06:32 PM5/15/14
to reacti...@googlegroups.com
I also notice that in DependencyObjectObservableForProperty there are fallback cases to PocoObservableForProperty

    public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
    {
      Type type = sender.GetType();
      if (beforeChanged)
      {
        LogHost.Log<DependencyObjectObservableForProperty>(this).Warn<string, string>("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object", type.FullName, propertyName);
        return new POCOObservableForProperty().GetNotificationForProperty(sender, propertyName, beforeChanged);
      }
      else
      {
        Func<DependencyProperty> dpFetcher = this.getDependencyPropertyFetcher(type, propertyName);
        if (dpFetcher != null)
          return Observable.Create<IObservedChange<object, object>>((Func<IObserver<IObservedChange<object, object>>, IDisposable>) (subj =>
          {
            DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(dpFetcher(), type);
            EventHandler ev = (EventHandler) ((o, e) => subj.OnNext((IObservedChange<object, object>) new ObservedChange<object, object>()
            {
              Sender = sender,
              PropertyName = propertyName
            }));
            dpd.AddValueChanged(sender, ev);
            return Disposable.Create((Action) (() => dpd.RemoveValueChanged(sender, ev)));
          }));
        LogHost.Log<DependencyObjectObservableForProperty>(this).Warn<string, string>("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName);
        return new POCOObservableForProperty().GetNotificationForProperty(sender, propertyName, beforeChanged);
      }
    }

but I'm pretty sure i'm not triggering those conditions. My objects are custom user controls and the properties are DP's.  

Brad Phelan

unread,
May 15, 2014, 12:08:43 PM5/15/14
to reacti...@googlegroups.com
btw. I'm on reactiveui 5.4.0 

Brad Phelan

unread,
May 15, 2014, 12:13:56 PM5/15/14
to reacti...@googlegroups.com

Ok I see it is not order based.

   GetAffinityForObject is the trick. 

Brad Phelan

unread,
May 15, 2014, 12:18:40 PM5/15/14
to reacti...@googlegroups.com
Would it be possible for you to publish debuggable nuget packages? 


then I could dig around with the debugger and have a look.


Paul Betts

unread,
May 15, 2014, 1:24:34 PM5/15/14
to reacti...@googlegroups.com
Hey Brad,

I would manually new up a DependencyObjectObservableForProperty and see what
it returns for GetAffinityForObject.

--
Paul Betts <pa...@paulbetts.org>
"When something is too hard it means that you're not cheating enough" - DHH

On Thu, May 15, 2014 at 09:13:56AM -0700, Brad Phelan wrote:
>
> Ok I see it is not order based.
>
> GetAffinityForObject is the trick.
>
> --
> You received this message because you are subscribed to the Google Groups "ReactiveUI mailing list" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to reactivexaml...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Brad Phelan

unread,
May 15, 2014, 1:25:34 PM5/15/14
to reacti...@googlegroups.com

Good idea.  I'll try it tomorrow.

Paul Betts

unread,
May 15, 2014, 1:26:24 PM5/15/14
to reacti...@googlegroups.com
Hey Brad,

> Would it be possible for you to publish debuggable nuget packages?

This is already set up actually, but SymbolSource 500s when you upload the
packages :(

--
Paul Betts <pa...@paulbetts.org>
"A ship in harbor is safe, but that's not what ships are for." - John A. Shedd

Brad Phelan

unread,
May 16, 2014, 1:48:36 AM5/16/14
to reacti...@googlegroups.com
Pity about the source debugging...

Anyway

yy.GetAffinityForObject(typeof(WeinCad.Controls.View.Moineau3DMeshingView), "ViewModel", false);
1
xx.GetAffinityForObject(typeof(WeinCad.Controls.View.Moineau3DMeshingView), "ViewModel", false);
4

where yy is the POCO one and xx is the DP one. Seems to work fine. I don't understand how  the warning in the log could be generated.

Brad Phelan

unread,
May 16, 2014, 2:11:34 AM5/16/14
to reacti...@googlegroups.com
I found the source of the warning. It was not where I expected it. I set a breakpoint in my Logger intercepting messages with POCO in it. I found that the warning was caused by

        // If the ViewModel changes we need to ensure the
            // AdditionalContent get's the correct DataContext
            this.WhenAnyValue(x => x.ViewModel)
                .BindTo(this, x => x.DataContextHost.DataContext);

It was not the WhenAny but the BindTo that was the source of the warning. In this case I have

public class ReactiveUserControl<ViewModelT> : UserControl, IViewFor<ViewModelT>
        where ViewModelT : class
{
 
    public UniformGrid DataContextHost { get; private set; }

public ReactiveUserControl(){

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

Now DataContextHost is not a DP. It doesn't need to be. I know that somewhere deep in BindTo there is a call to 

    this.WhenAny(this, p=>p.DataContext)

which will fail to detect DataContext as a DP and fallback to POCO. Is this something you would want to fix or come up with a way to handle it?

 I think my code is behaving in an expected way but the easy fix for me is to make DataContext a DP and the warning should go away.

Brad

Brad Phelan

unread,
May 16, 2014, 2:13:59 AM5/16/14
to reacti...@googlegroups.com
I mean I should make __DataContextHost__ a DP on ReactiveUserControl but that is a pain cause I want it readonly and I never remember the pattern to do correct readonly DP's

otherwise I can just not use BindTo and avoid it's fancy magic and just subscribe and set directly the property I want.

Reply all
Reply to author
Forward
0 new messages