2

Closed

Caliburn.Micro subscribes multiple times to PropertyChanged

description

I have the problem that CM seems to subscribe twice to PropertyChanged. Or, depending on the POV / expected behavior: It doesn't unsubscribe when the item is deactivated.

The scenario is the following:

I have a view model (OuterViewModel in the sample) that has a property (CurrentInnerViewModel) of another view model (InnerViewModel).

Outer has a button. A click on the button cycles through all inner view models it knows, one at a time. When there are no more, it activates the next screen (NextViewModel) (see OuterViewModel.ShowNext).

Next has a button that simply activates the previous screen.

Now, clicking in fast succession on the "Show next" and "Show previous" buttons with an attached debugger shows that multiple views get created for a single raised PropertyChanged event (see Output pane).

file attachments

Closed Sep 16, 2013 at 2:04 AM by EisenbergEffect

comments

tibel wrote Jul 17, 2013 at 2:31 PM

In Caliburn.Micro only ActionMessage subscribes to PropertyChanged event
and only when there is a Can<ActionName> property.
As there is no Can-property in your sample, there is also no registration from Caliburn.Micro side.

So the behavior you described must come from some other place.

tibel wrote Jul 17, 2013 at 9:16 PM

Changing UITranslator method to the following removes the issue:
private static void OnTranslateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    GC.Collect();
    return;
}
So this is not Caliburn.Micro related.

tibel wrote Jul 17, 2013 at 9:16 PM

** Closed by tibel 07/17/2013 1:16PM

dhilgarth wrote Jul 18, 2013 at 8:43 AM

Forcing a garbage collection certainly isn't a solution.

The problem might not be related to PropertyChanged, but it still is a CM problem.
In the example, the InnerView gets created multiple times. Examining the call stack in the constructor of InnerView shows clearly that all instances are created by CM:
CMBug.exe!CMBug.Views.InnerView.InnerView() Line 26 C#
mscorlib.dll!System.Activator.CreateInstance(System.Type type, bool nonPublic) + 0x46 bytes 
mscorlib.dll!System.Activator.CreateInstance(System.Type type) + 0x7 bytes  
Caliburn.Micro.dll!Caliburn.Micro.ViewLocator..cctor.AnonymousMethod__2(System.Type viewType = {Name = "InnerView" FullName = "CMBug.Views.InnerView"}) Line 272 + 0x8 bytes    C#
Caliburn.Micro.dll!Caliburn.Micro.ViewLocator..cctor.AnonymousMethod__d(System.Type modelType = {Name = "InnerViewModel" FullName = "CMBug.ViewModels.InnerViewModel"}, System.Windows.DependencyObject displayLocation = {System.Windows.Controls.ContentControl}, object context = null) Line 370 + 0x24 bytes    C#
Caliburn.Micro.dll!Caliburn.Micro.ViewLocator..cctor.AnonymousMethod__e(object model = {CMBug.ViewModels.InnerViewModel}, System.Windows.DependencyObject displayLocation = {System.Windows.Controls.ContentControl}, object context = null) Line 400 + 0x29 bytes  C#
Caliburn.Micro.dll!Caliburn.Micro.View.OnModelChanged(System.Windows.DependencyObject targetLocation = {System.Windows.Controls.ContentControl}, System.Windows.DependencyPropertyChangedEventArgs args = {System.Windows.DependencyPropertyChangedEventArgs}) Line 292 + 0x27 bytes    C#
WindowsBase.dll!System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e) + 0x4c bytes 
PresentationFramework.dll!System.Windows.FrameworkElement.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e) + 0x50 bytes   
WindowsBase.dll!System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs args) + 0x3c bytes   
WindowsBase.dll!System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex entryIndex = {System.Windows.EntryIndex}, System.Windows.DependencyProperty dp = {System.Windows.DependencyProperty}, System.Windows.PropertyMetadata metadata, System.Windows.EffectiveValueEntry oldEntry, ref System.Windows.EffectiveValueEntry newEntry = {System.Windows.EffectiveValueEntry}, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType) + 0x71b bytes    
WindowsBase.dll!System.Windows.DependencyObject.InvalidateProperty(System.Windows.DependencyProperty dp) + 0xad bytes   
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.Invalidate(bool isASubPropertyChange) + 0x62 bytes  
PresentationFramework.dll!System.Windows.Data.BindingExpression.TransferValue(object newValue, bool isASubPropertyChange) + 0x1fd bytes 
PresentationFramework.dll!System.Windows.Data.BindingExpression.ScheduleTransfer(bool isASubPropertyChange) + 0x3b bytes    
PresentationFramework.dll!MS.Internal.Data.ClrBindingWorker.NewValueAvailable(bool dependencySourcesChanged, bool initialValue, bool isASubPropertyChange) + 0x5c bytes 
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(int k, System.ComponentModel.ICollectionView collectionView, object newValue, bool isASubPropertyChange) + 0x1d4 bytes 
PresentationFramework.dll!MS.Internal.Data.ClrBindingWorker.OnSourcePropertyChanged(object o, string propName) + 0x84 bytes 
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.System.Windows.IWeakEventListener.ReceiveWeakEvent(System.Type managerType, object sender, System.EventArgs e) + 0xa2 bytes   
WindowsBase.dll!System.Windows.WeakEventManager.DeliverEventToList(object sender = {CMBug.ViewModels.OuterViewModel}, System.EventArgs args = {System.ComponentModel.PropertyChangedEventArgs}, System.Windows.WeakEventManager.ListenerList list = {System.Windows.WeakEventManager.ListenerList}) + 0x5e bytes    
WindowsBase.dll!System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(object sender = {CMBug.ViewModels.OuterViewModel}, System.ComponentModel.PropertyChangedEventArgs args) + 0x43a bytes   
Caliburn.Micro.dll!Caliburn.Micro.PropertyChangedBase.OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e = {System.ComponentModel.PropertyChangedEventArgs}) Line 280   C#
Caliburn.Micro.dll!Caliburn.Micro.PropertyChangedBase.NotifyOfPropertyChange.AnonymousMethod__2() Line 257 + 0x30 bytes C#
Caliburn.Micro.dll!Caliburn.Micro.Execute.OnUIThread(System.Action action = {Method = {System.Reflection.RuntimeMethodInfo}}) Line 156 + 0xb bytes  C#
Caliburn.Micro.dll!Caliburn.Micro.PropertyChangedBase.NotifyOfPropertyChange(string propertyName = "CurrentInnerViewModel") Line 259    C#
Caliburn.Micro.dll!Caliburn.Micro.PropertyChangedBase.NotifyOfPropertyChange<CMBug.ViewModels.InnerViewModel>(System.Linq.Expressions.Expression<System.Func<CMBug.ViewModels.InnerViewModel>> property = {System.Linq.Expressions.Expression<System.Func<CMBug.ViewModels.InnerViewModel>>}) Line 268  C#
CMBug.exe!CMBug.ViewModels.OuterViewModel.CurrentIndex.set(int value = 0) Line 48 + 0xe6 bytes  C#
As a result, please re-open this issue.

tibel wrote Jul 18, 2013 at 8:51 AM

The view is created because you forced it by creating new ViewModel instance in OuterViewModel:
_conductor.ActivateItem(new NextViewModel(this, _conductor));
I'm using Caliburn.Micro for more than a year in a bigger project without any problems.

dhilgarth wrote Jul 18, 2013 at 10:31 AM

The view is created because you forced it by creating
This is incorrect. The problem is that the InnerView is created multiple times. This has nothing to do with NextViewModel. Furthermore, even when I only ever create one instance of NextViewModel in the constructor of OuterViewModel and reuse that, the problem still happens.

Thanks for re-opening the issue.

tibel wrote Jul 18, 2013 at 5:45 PM

Hmm, your sample is quite weird...

You are right:
InnerView is created because it is not cached and when you change the ActiveItem in the Conductor to OuterViewModel they are recreated.

Simple solution would be to inherit InnerViewModel from ViewAware.

So again: No issue on Caliburn.Micro side.
Also note that Unloaded is not always called for your xaml object, so UITranslator might make problems.

dhilgarth wrote Jul 18, 2013 at 6:17 PM

Again, I disagree.

The problem is not that a new InnerView is created every time the property on OuterViewModel changes. The problem is that multiple instances of InnerView get created when the property on OuterViewModel is changed once

tibel wrote Jul 18, 2013 at 7:15 PM

Ok, I have no idea what is happening...
Please tell me when you find out :-)

tibel wrote Sep 13, 2013 at 5:29 PM

Any news on this?

dhilgarth wrote Sep 13, 2013 at 5:54 PM

Not from my side. It is up to a CM developer to further check this. I tried to figure it out but got lost inside the CM code.

tibel wrote Sep 15, 2013 at 10:12 AM

I created a new simpler version of your sample, including a fix/workaround for the issue (see OuterViewModel).

Multiple instances of the innermost view are created:
  • when nesting a Conductor<T>.Collection.OneActive inside another one
  • don't use (disabled) view caching
  • change the ActiveItem in the OnActivate() method of the inner conductor
I found three possible workarounds:
  • use view caching (this is on by default)
  • disable property change notifications in OnActivate() method while changing ActiveItem
  • in the outer conductor call GC.Collect() before changing ActiveItem
The last workaround make me believe that the issue might be in WPF...not sure about that.
Hope this helps on finding the root cause.

dhilgarth wrote Sep 16, 2013 at 8:59 AM

Thanks for the analysis and the workaround.