public void NotificationPocoSuppressErrorOnBind() { RxApp.EnsureInitialized(); using (var testLoggerRegistration = new TestLoggerRegistration()) { var instance = new POCOObservableForProperty(); var testLogger = testLoggerRegistration.Logger; var testClass = new PocoType(); Expression <Func <PocoType, string> > expr = x => x.Property1 !; var exp = Reflection.Rewrite(expr.Body); var propertyName = exp.GetMemberInfo()?.Name; if (propertyName is null) { throw new InvalidOperationException("propertyName should not be null"); } instance.GetNotificationForProperty(testClass, exp, propertyName, false, true).Subscribe(_ => { }); testLogger.LastMessages.Should().NotContain(m => m.Contains(nameof(POCOObservableForProperty))); } }
public void CheckGetAffinityForObjectValues() { var instance = new POCOObservableForProperty(); Assert.Equal(1, instance.GetAffinityForObject(typeof(PocoType), null, false)); Assert.Equal(1, instance.GetAffinityForObject(typeof(INPCClass), null, false)); }
public IObservable <IObservedChange <object, object> > GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false) { Contract.Requires(sender != null && sender is DependencyObject); var type = sender.GetType(); if (beforeChanged == true) { this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged)); } var dpFetcher = getDependencyPropertyFetcher(type, propertyName); if (dpFetcher == null) { this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged)); } #if !WINRT && !SILVERLIGHT return(Observable.Create <IObservedChange <object, object> >(subj => { var dp = dpFetcher(); var dpd = DependencyPropertyDescriptor.FromProperty(dp, type); var ev = new EventHandler((o, e) => subj.OnNext(new ObservedChange <object, object>() { Sender = sender, PropertyName = propertyName, })); dpd.AddValueChanged(sender, ev); return Disposable.Create(() => dpd.RemoveValueChanged(sender, ev)); })); #else var dpAndSubj = createAttachedProperty(type, propertyName); BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1, new Binding() { Source = sender as DependencyObject, Path = new PropertyPath(propertyName) }); return(dpAndSubj.Item2 .Where(x => x == sender) .Select(x => (IObservedChange <object, object>) new ObservedChange <object, object>() { Sender = x, PropertyName = propertyName })); #endif }
/// <inheritdoc/> public IObservable <IObservedChange <object, object> >?GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) { if (sender == null) { throw new ArgumentNullException(nameof(sender)); } var depSender = sender as DependencyObject; if (depSender == null) { throw new ArgumentException("The sender must be a DependencyObject", nameof(sender)); } var type = sender.GetType(); if (beforeChanged) { this.Log().Warn( CultureInfo.InvariantCulture, "Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, expression, propertyName, beforeChanged)); } var dpFetcher = GetDependencyPropertyFetcher(type, propertyName); if (dpFetcher == null) { this.Log().Warn( CultureInfo.InvariantCulture, "Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, expression, propertyName, beforeChanged)); } return(Observable.Create <IObservedChange <object, object> >(subj => { var handler = new DependencyPropertyChangedCallback((o, e) => { subj.OnNext(new ObservedChange <object, object>(sender, expression)); }); var dependencyProperty = dpFetcher(); var token = depSender.RegisterPropertyChangedCallback(dependencyProperty, handler); return Disposable.Create(() => depSender.UnregisterPropertyChangedCallback(dependencyProperty, token)); })); }
public void NotificationPocoErrorOnBind() { RxApp.EnsureInitialized(); // Use same logger, when the test is executed multiple times in the same AndroidRunner/AppDomain/AssemblyLoadContext if (_testLoggerForNotificationPocoErrorOnBind is null) { _testLoggerForNotificationPocoErrorOnBind = new TestLogger(); } // Run test twice and verify that POCO message is logged only once. for (var i = 0; i < 2; i++) { using (var testLoggerRegistration = new TestLoggerRegistration(_testLoggerForNotificationPocoErrorOnBind)) { var instance = new POCOObservableForProperty(); var testLogger = testLoggerRegistration.Logger; var testClass = new PocoType(); Expression <Func <PocoType, string> > expr = x => x.Property1 !; var exp = Reflection.Rewrite(expr.Body); var propertyName = exp.GetMemberInfo()?.Name; if (propertyName is null) { throw new InvalidOperationException("propertyName should not be null"); } instance.GetNotificationForProperty(testClass, exp, propertyName, false).Subscribe(_ => { }); Assert.True(testLogger.LastMessages.Count > 0); var expectedMessage = $"{nameof(POCOObservableForProperty)}: The class {typeof(PocoType).FullName} property {nameof(PocoType.Property1)} is a POCO type and won't send change notifications, WhenAny will only return a single value!"; Assert.Equal(expectedMessage, testLogger.LastMessages[0]); // Verify that the message is logged only once foreach (var logMessage in testLogger.LastMessages.Skip(1)) { Assert.NotEqual(expectedMessage, logMessage); } } } }
public void NotificationPocoSuppressErrorOnBind() { var instance = new POCOObservableForProperty(); var testLogger = new TestLogger(); Locator.CurrentMutable.RegisterConstant <ILogger>(testLogger); var testClass = new PocoType(); Expression <Func <PocoType, string> > expr = x => x.Property1; var exp = Reflection.Rewrite(expr.Body); instance.GetNotificationForProperty(testClass, exp, exp.GetMemberInfo().Name, false, true).Subscribe(_ => { }); testLogger.LastMessages.ShouldNotContain(m => m.Contains(nameof(POCOObservableForProperty))); }
public IObservable <IObservedChange <object, object> > GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false) { Contract.Requires(sender != null && sender is DependencyObject); var type = sender.GetType(); if (beforeChanged == true) { this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged)); } var dpFetcher = getDependencyPropertyFetcher(type, propertyName); if (dpFetcher == null) { this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged)); } var dpAndSubj = createAttachedProperty(type, propertyName); return(Observable.Create <IObservedChange <object, object> >(obs => { BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1, new Binding() { Source = sender as DependencyObject, Path = new PropertyPath(propertyName) }); var disp = dpAndSubj.Item2 .Where(x => x == sender) .Select(x => new ObservedChange <object, object>() { Sender = x, PropertyName = propertyName }) .Subscribe(obs); // ClearBinding calls ClearValue http://stackoverflow.com/questions/1639219/clear-binding-in-silverlight-remove-data-binding-from-setbinding return new CompositeDisposable(Disposable.Create(() => (sender as DependencyObject).ClearValue(dpAndSubj.Item1)), disp); })); }
public void NotificationPocoErrorOnBind() { var instance = new POCOObservableForProperty(); var testLogger = new TestLogger(); Locator.CurrentMutable.RegisterConstant <ILogger>(testLogger); var testClass = new PocoType(); Expression <Func <PocoType, string> > expr = x => x.Property1; var exp = Reflection.Rewrite(expr.Body); instance.GetNotificationForProperty(testClass, exp, exp.GetMemberInfo().Name, false).Subscribe(_ => { }); Assert.True(testLogger.LastMessages.Count > 0); Assert.Equal(testLogger.LastMessages[0], $"{nameof(POCOObservableForProperty)}: The class {typeof(PocoType).FullName} property {nameof(PocoType.Property1)} is a POCO type and won't send change notifications, WhenAny will only return a single value!"); }
public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false) { Contract.Requires(sender != null && sender is DependencyObject); var type = sender.GetType(); if (beforeChanged == true) { this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return ret.GetNotificationForProperty(sender, propertyName, beforeChanged); } var dpFetcher = getDependencyPropertyFetcher(type, propertyName); if (dpFetcher == null) { this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return ret.GetNotificationForProperty(sender, propertyName, beforeChanged); } #if !WINRT && !SILVERLIGHT return Observable.Create<IObservedChange<object, object>>(subj => { var dp = dpFetcher(); var dpd = DependencyPropertyDescriptor.FromProperty(dp, type); var ev = new EventHandler((o, e) => subj.OnNext(new ObservedChange<object, object>() {Sender = sender, PropertyName = propertyName,})); dpd.AddValueChanged(sender, ev); return Disposable.Create(() => dpd.RemoveValueChanged(sender, ev)); }); #else var dpAndSubj = createAttachedProperty(type, propertyName); BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1, new Binding() { Source = sender as DependencyObject, Path = new PropertyPath(propertyName) }); return dpAndSubj.Item2 .Where(x => x == sender) .Select(x => (IObservedChange<object, object>) new ObservedChange<object, object>() { Sender = x, PropertyName = propertyName }); #endif }
public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false) { Contract.Requires(sender != null && sender is DependencyObject); var type = sender.GetType(); if (beforeChanged == true) { this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return ret.GetNotificationForProperty(sender, propertyName, beforeChanged); } var dpFetcher = getDependencyPropertyFetcher(type, propertyName); if (dpFetcher == null) { this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return ret.GetNotificationForProperty(sender, propertyName, beforeChanged); } var dpAndSubj = createAttachedProperty(type, propertyName); return Observable.Create<IObservedChange<object, object>>(obs => { BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1, new Binding() { Source = sender as DependencyObject, Path = new PropertyPath(propertyName) }); var disp = dpAndSubj.Item2 .Where(x => x == sender) .Select(x => new ObservedChange<object, object>() { Sender = x, PropertyName = propertyName }) .Subscribe(obs); // ClearBinding calls ClearValue http://stackoverflow.com/questions/1639219/clear-binding-in-silverlight-remove-data-binding-from-setbinding return new CompositeDisposable(Disposable.Create(() => (sender as DependencyObject).ClearValue(dpAndSubj.Item1)), disp); }); }
public IObservable <IObservedChange <object, object> > GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false) { Contract.Requires(sender != null && sender is DependencyObject); if (beforeChanged == true) { return(null); } var dobj = sender as DependencyObject; var type = dobj.GetType(); // Look for the DependencyProperty attached to this property name #if WINRT var pi = type.GetProperty(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (pi != null) { goto itWorks; } #endif var fi = type.GetField(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (fi == null) { this.Log().Debug("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged)); } #if !WINRT && !SILVERLIGHT return(Observable.Create <IObservedChange <object, object> >(subj => { var dp = (DependencyProperty)fi.GetValue(null); var dpd = DependencyPropertyDescriptor.FromProperty(dp, type); var ev = new EventHandler((o, e) => subj.OnNext(new ObservedChange <object, object>() { Sender = sender, PropertyName = propertyName, })); dpd.AddValueChanged(sender, ev); return Disposable.Create(() => dpd.RemoveValueChanged(sender, ev)); })); #else itWorks: return(Observable.Create <IObservedChange <object, object> >(subj => { DependencyProperty attachedProp; if (!attachedProperties.ContainsKey(type)) { // NB: There is no way to unregister an attached property, // we just have to leak it. Luckily it's per-type, so it's // not *that* bad. attachedProp = DependencyProperty.RegisterAttached( "ListenAttached" + propertyName + this.GetHashCode().ToString("{0:x}"), typeof(object), type, new PropertyMetadata(null, (o, e) => subjects[o].Item1.OnNext(o))); attachedProperties[type] = attachedProp; } else { attachedProp = attachedProperties[type]; } // Here's the idea for this cracked-out code: // // The reason we're doing all of this is that we can only // create a single binding between a DependencyObject and its // attached property, yet we could have multiple people // interested in this property. We should only drop the actual // Binding once nobody is listening anymore. if (!subjects.ContainsKey(sender)) { var disposer = new RefcountDisposeWrapper( Disposable.Create(() => { #if !SILVERLIGHT && !WINRT // XXX: Apparently it's simply impossible to unset a binding in SL :-/ BindingOperations.ClearBinding(dobj, attachedProp); #endif subjects.Remove(dobj); })); subjects[sender] = Tuple.Create(new Subject <object>(), disposer); var b = new Binding() { Source = dobj, Path = new PropertyPath(propertyName) }; BindingOperations.SetBinding(dobj, attachedProp, b); } else { subjects[sender].Item2.AddRef(); } var disp = subjects[sender].Item1 .Select(x => (IObservedChange <object, object>) new ObservedChange <object, object>() { Sender = x, PropertyName = propertyName }) .Subscribe(subj); return Disposable.Create(() => { disp.Dispose(); subjects[sender].Item2.Release(); }); })); #endif }
public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false) { Contract.Requires(sender != null && sender is DependencyObject); var dobj = sender as DependencyObject; var type = dobj.GetType(); // Look for the DependencyProperty attached to this property name #if WINRT var pi = type.GetProperty(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (pi != null) { goto itWorks; } #endif var fi = type.GetField(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (fi == null) { this.Log().Debug("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object", type.FullName, propertyName); var ret = new POCOObservableForProperty(); return ret.GetNotificationForProperty(sender, propertyName, beforeChanged); } itWorks: return Observable.Create<IObservedChange<object, object>>(subj => { DependencyProperty attachedProp; if (!attachedProperties.ContainsKey(type)) { // NB: There is no way to unregister an attached property, // we just have to leak it. Luckily it's per-type, so it's // not *that* bad. attachedProp = DependencyProperty.RegisterAttached( "ListenAttached" + propertyName + this.GetHashCode().ToString("{0:x}"), typeof(object), type, new PropertyMetadata(null, (o,e) => subjects[o].Item1.OnNext(o))); attachedProperties[type] = attachedProp; } else { attachedProp = attachedProperties[type]; } // Here's the idea for this cracked-out code: // // The reason we're doing all of this is that we can only // create a single binding between a DependencyObject and its // attached property, yet we could have multiple people // interested in this property. We should only drop the actual // Binding once nobody is listening anymore. if (!subjects.ContainsKey(sender)) { var disposer = new RefcountDisposeWrapper( Disposable.Create(() => { #if !SILVERLIGHT && !WINRT // XXX: Apparently it's simply impossible to unset a binding in SL :-/ BindingOperations.ClearBinding(dobj, attachedProp); #endif subjects.Remove(dobj); })); subjects[sender] = Tuple.Create(new Subject<object>(), disposer); var b = new Binding() { Source = dobj, Path = new PropertyPath(propertyName) }; BindingOperations.SetBinding(dobj, attachedProp, b); } else { subjects[sender].Item2.AddRef(); } var disp = subjects[sender].Item1 .Select(x => (IObservedChange<object, object>) new ObservedChange<object, object>() { Sender = x, PropertyName = propertyName }) .Subscribe(subj); return Disposable.Create(() => { disp.Dispose(); subjects[sender].Item2.Release(); }); }); }