When is view destroyed?

Jan 5, 2011 at 3:58 PM

I searching for 2 hours but I can’t find answer o this questiom.

I create view it is simple WPF window and  also I have view model class for this view. View Model class is export as nonshared with MEF.

When I close View - WPF window, for example click on X ... object of view / view model class is still alive.

It exist way have destroy this objects?

 

Because If I use this method on creation view / view model...

 

[Export(typeof(IChatViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ChatViewModel : Screen, IChatViewModel
    {}

Open window:

 

rivate IWindowManager _windowManager;
var chatScreen = IoC.Get<IChatViewModel>();
_windowManager.Show(chatScreen);

It create new object for every openning window, but if I close window, it doesn’t destroyed view / window object. So this way is increasing memory problem.

Jan 5, 2011 at 4:54 PM

The current Screen implementation caches the view in an internal dictionary to allow IViewAware consumers to retrieve it.

This means that a view is destroyed when it is no more referenced in the UI AND is flushed from the internal registry.

The last action is performed only when a view is replaced (i.e. when IViewAware.AttachView is called with an already existing context), which measn that the last view attached to a Screen in a specific context is kept in memory until it is replaced.

In your scenario, you should always have one lingering view even after multiple windows of the same type are opened (e.g. if you open multiple chat windows from the same person, I expect that there is only one instance in memory even if you open and close the chat a hundred times). If this behaviour does not suit your needs, consider changing the Screen implementation of the views member from  Dictionary<object, object> to Dictionary<object, WeakReference>, so that it stores weak references to views.

For your convenience, this is a modified Screen implementation using weak references to views.

namespace Caliburn.Micro
 {
     using System;
     using System.Linq;
     using System.Collections.Generic;
     using System.Windows;

     /// <summary>
     /// A base implementation of <see cref="IScreen"/>.
     /// </summary>
     public class Screen : PropertyChangedBase, IScreen, IChild<IConductor>, IViewAware
     {
         protected static readonly ILog Log = LogManager.GetLog(typeof(Screen));

         bool isActive;
         bool isInitialized;
         IConductor parent;
         string displayName;
         readonly Dictionary<object, WeakReference> views = new Dictionary<object, WeakReference>();

         /// <summary>
         /// Creates an instance of the screen.
         /// </summary>
         public Screen()
         {
             DisplayName = GetType().FullName;
         }

         /// <summary>
         /// Gets or Sets the Parent <see cref="IConductor"/>
         /// </summary>
         public IConductor Parent
         {
             get { return parent; }
             set
             {
                 parent = value;
                 NotifyOfPropertyChange("Parent");
             }
         }

         /// <summary>
         /// Gets or Sets the Parent
         /// </summary>
         object IChild.Parent
         {
             get { return Parent; }
             set { Parent = (IConductor)value; }
         }

         /// <summary>
         /// Gets or Sets the Display Name
         /// </summary>
         public string DisplayName
         {
             get { return displayName; }
             set
             {
                 displayName = value;
                 NotifyOfPropertyChange("DisplayName");
             }
         }

         /// <summary>
         /// Indicates whether or not this instance is currently active.
         /// </summary>
         public bool IsActive
         {
             get { return isActive; }
             private set
             {
                 isActive = value;
                 NotifyOfPropertyChange("IsActive");
             }
         }

         /// <summary>
         /// Indicates whether or not this instance is currently initialized.
         /// </summary>
         public bool IsInitialized
         {
             get { return isInitialized; }
             private set
             {
                 isInitialized = value;
                 NotifyOfPropertyChange("IsInitialized");
             }
         }

         /// <summary>
         /// Raised after activation occurs.
         /// </summary>
         public event EventHandler<ActivationEventArgs> Activated = delegate { };

         /// <summary>
         /// Raised before deactivation.
         /// </summary>
         public event EventHandler<DeactivationEventArgs> AttemptingDeactivation = delegate { };

         /// <summary>
         /// Raised after deactivation.
         /// </summary>
         public event EventHandler<DeactivationEventArgs> Deactivated = delegate { };

         void IActivate.Activate()
         {
             if (IsActive)
                 return;

             var initialized = false;

             if (!IsInitialized)
             {
                 IsInitialized = initialized = true;
                 OnInitialize();
             }

             IsActive = true;
             Log.Info("Activating {0}.", this);
             OnActivate();

             Activated(this, new ActivationEventArgs
             {
                 WasInitialized = initialized
             });
         }

         /// <summary>
         /// Called when initializing.
         /// </summary>
         protected virtual void OnInitialize() { }

         /// <summary>
         /// Called when activating.
         /// </summary>
         protected virtual void OnActivate() { }

         void IDeactivate.Deactivate(bool close)
         {
             if (!IsActive && !IsInitialized)
                 return;

             AttemptingDeactivation(this, new DeactivationEventArgs
             {
                 WasClosed = close
             });

             IsActive = false;
             Log.Info("Deactivating {0}.", this);
             OnDeactivate(close);

             if (close)
                 Log.Info("Closed {0}.", this);

             Deactivated(this, new DeactivationEventArgs
             {
                 WasClosed = close
             });
         }

         /// <summary>
         /// Called when deactivating.
         /// </summary>
         /// <param name="close">Inidicates whether this instance will be closed.</param>
         protected virtual void OnDeactivate(bool close) { }

         /// <summary>
         /// Called to check whether or not this instance can close.
         /// </summary>
         /// <param name="callback">The implementor calls this action with the result of the close check.</param>
         public virtual void CanClose(Action<bool> callback)
         {
             callback(true);
         }

         /// <summary>
         /// Attaches a view to this instance.
         /// </summary>
         /// <param name="view">The view.</param>
         /// <param name="context">The context in which the view appears.</param>
         public virtual void AttachView(object view, object context)
         {
             var loadWired = views.Values.Any( r => r.Target == view);
             views[context ?? View.DefaultContext] = new WeakReference(view);

             var element = view as FrameworkElement;
             if (!loadWired && element != null)
                 element.Loaded += delegate { OnViewLoaded(view); };

             if (!loadWired)
                 ViewAttached(this, new ViewAttachedEventArgs { View = view, Context = context });
         }

         /// <summary>
         /// Called when an attached view's Loaded event fires.
         /// </summary>
         /// <param name="view"></param>
         protected virtual void OnViewLoaded(object view) { }

         /// <summary>
         /// Gets a view previously attached to this instance.
         /// </summary>
         /// <param name="context">The context denoting which view to retrieve.</param>
         /// <returns>The view.</returns>
         public virtual object GetView(object context)
         {
             WeakReference view;
             views.TryGetValue(context ?? View.DefaultContext, out view);
             return view != null ? view.Target : null;
         }

         /// <summary>
         /// Raised when a view is attached.
         /// </summary>
         public event EventHandler<ViewAttachedEventArgs> ViewAttached = delegate { };

         /// <summary>
         /// Tries to close this instance by asking its Parent to initiate shutdown or by asking its corresponding default view to close.
         /// </summary>
         public void TryClose()
         {
             Execute.OnUIThread(() =>
             {
                 if (Parent != null)
                     Parent.CloseItem(this);
                 else
                 {
                     var view = GetView(null);

                     if (view == null)
                     {
                         var ex = new NotSupportedException("A Parent or default view is required.");
                         Log.Error(ex);
                         throw ex;
                     }

                     var method = view.GetType().GetMethod("Close");
                     if (method != null)
                     {
                         method.Invoke(view, null);
                         return;
                     }

                     var property = view.GetType().GetProperty("IsOpen");
                     if (property != null)
                     {
                         property.SetValue(view, false, new object[] { });
                         return;
                     }

                     var ex2 = new NotSupportedException("The default view does not support Close/IsOpen.");
                     Log.Error(ex2);
                     throw ex2;
                 }
             });
         }

#if !SILVERLIGHT

         /// <summary>
         /// Closes this instance by asking its Parent to initiate shutdown or by asking it's corresponding default view to close.
         /// This overload also provides an opportunity to pass a dialog result to it's corresponding default view.
         /// </summary>
         /// <param name="dialogResult">The dialog result.</param>
         public virtual void TryClose(bool? dialogResult)
         {
             Execute.OnUIThread(() =>
             {
                 var view = GetView(null);

                 if (view != null)
                 {
                     var property = view.GetType().GetProperty("DialogResult");
                     if (property != null)
                         property.SetValue(view, dialogResult, null);
                 }

                 TryClose();
             });
         }

#endif
     }
 }
Jan 5, 2011 at 6:26 PM

re: e.g. if you open multiple chat windows from the same person, I expect that there is only one instance in memory even if you open and close the chat a hundred times

Mr. Wise I must create nonshared (non singleton) instance for view model / view because in view I have bindable richtextbox and I bind property   (type of FlowDocument)  from view model class on richextbox property Document in view.

If instace of view model / view is shared and it is open 1 - N window it doesn’t work.

Solution is create non shared instaces of view models / views, but I need mechanism how control instaces of these object in memory.

Yes if I open 100 times chat window for same person I would like have only and the same object in memory- that is true.

Problem is if you export view model as shared (MEF default export is shared)

[Export(typeof(IChatViewModel))]  //export as shared
public class ChatViewModel : Screen, IChatViewModel {}

Situation:

Chat View 1

Chat View 2       share  one and the same Chat View Model  (in Chat View Model I have one property FlowDocumnet Conversation, and this property is bind on richtextboxes in Chat Views)

Chat View 3

 

Problem: 

Property Conversation is shared between Views ! 

 

Solution:

Export View Model class as non shared:

[Export(typeof(IChatViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ChatViewModel : Screen, IChatViewModel
    {}

Actual problem:

If I open 500 instaces of view chat screen (also view model class), close these views -> it still hanging in memory. Then I open other 500 views, close this view , then I have 1000 instaces of view/view models in memory.

If I strore refrences in Dictionary<object, WeakReference> it solve this problem? After I close view (click on X in WPF Window) is destroyed view object (WPF window) ? Sory for my english I am 16 years old guys from Poland ;)

Jan 5, 2011 at 7:06 PM

Don't worry for your English... I'm Italian so I can manage somehow! [BTW, Mr.Wise is pretty nice but there is no need for that, just drop the Mr. and use the nickname... I'm just 13 years older! :)]

I think I understand your application scenario (i.e. a MSN/Adium/GoogleTalk like application), but it is not clear to me how you implemented it, let's see if I got something right.

You are using the Shared policy to retrieve fresh instances of the view-model every time you start a conversation (which is fine), then I suppose that you cache such view-models during the sessions, so that once a conversation is started, every time you close and re-open the window the old conversation part is present. Am I correct? is this the approach you are using? Can you provide a small repro or explain in detail your implementation?

Jan 5, 2011 at 7:33 PM
Edited Jan 5, 2011 at 7:44 PM

I think I understand your application scenario (i.e. a MSN/Adium/GoogleTalk like application),

Yes, you right..

 

Oki, I describe my scenario.

I have shell.  I shell is active  messenger screen (it is user control).

This messenger consist  contact list (listbox).

Situation:  Any chat screen is not open.  As I wrote. Chat screen is WPF window.

  • I click on listbox item / or new message arrived.
  • It open non shared chat screen.
  • Add reference on this screen to my dictionary. This dictionary consist references on active chat screens (WPF windows).
  • And now is open chat screen so messanger view model can publish messages on chat screen.
  • If I click on some contact ( on listbox item) or new message arrived I use dictionary on control if is good chat screen active.
  • If is not good chat screen active I open  new instace for this chat screen.
  • If is screen closing (click on X in WPF window ) I remove reference on this cha screen from dictionary. but object on this screen is still hanging in memory.

 

I don’t cache chat view model object if is some chat screen close. I cache only history of comunication. If  I open chat screen  first I publish on them messages history.

 

Scenario is simple, I use dictionary on control if some chat screen is active, if chat screen is not active I create new instace for this screen and show . If is screen closing I remove reference on him from dictionary.

Problem is destroyed chat view object if is chat screen closing.  My chat screen can not have shared chat view model, this all problem, I must create non shared instaces.

I hope that you understand me, if no I can draw some diagram in Viso if you can.

Thank for help and your time, I am helpless :(.

It is my project, I school we deal only with C, it is boring for me. My teacher don’t know basic of OOP :D he live in world of structs.

 

I am on good way? How you solve this problem? (but non shared chat view models please)

 

Squadra Azura Rulezzz ;)

Jan 6, 2011 at 3:05 AM
Edited Jan 6, 2011 at 3:06 AM

I think that your designer is pretty good regarding the caching part (re-create the view-model and push messages when needed), but I still have a dubt... are you using a view-first approach? I'm asking since you wrote that you are storing opened chat windows in a dictionary. Honestly, I was thinking in a view-model-first way, since I prefer this approach and feels more natural to me in this scenario, but I suppose this is not the issue here. :)

If you are using a view-model-first approach instead, than do not store references to the views, since that functionality should be supported through the view-models.

That said, have you verified that the Deactivate method is called on window close? I double checked the WindowManager implementation (and expecially the WindowConductor one) and, except for a probably incorrect behaviour in a rare case (i.e. if you deactivate the view-model without requesting a close, and then perform a real close from the view-model, such operation is ignored due to the fact that the event has been detached), I could not spot an issue causing such a heavy memory leak.

Have you tried forcing a

GC.Collect();
GC.WaitForPendingFinalizers();

either using a specific button or invoking the methods on each chat window open/close?

Finally, can I ask you how you identified the problem? Does your application try an OutOfMemoryException? Are you using a memory profiler? Or you just guessed that a problem is occurring since the application memory usage increases?

Jan 6, 2011 at 11:35 AM
Edited Jan 6, 2011 at 11:40 AM

Hi BladeWise, first thank for a help, And now I answers on your questions:

 

I think that your designer is pretty good regarding the caching part (re-create the view-model and push messages when needed), but I still have a dubt... are you using a view-first approach? I'm asking since you wrote that you are storing opened chat windows in a dictionary.


 I store reference on chat view models in dictionary with this reasons:

  • Chat view model class is create as non shared  because for every user I want own chat view model class and chat view.
  • If I create chat view model class non shared I must  ensure  that for one user  exist only one chat view model class.
Example:

You click on listbox item and create new chat view. You click  again on  same listbox item and it create another new chat view. This behavior I don’t want in my application. So if some chat view model is created I use dictionary on control if this chat view is active/deactive. I create chat view models class only in situation if this class doesn’t exist.

 

That said, have you verified that the Deactivate method is called on window close?


Yes, I add debuger breakpoint  in  method OnDeactivate, this method is call if  chat view is closing. 

 

I double checked the WindowManager implementation (and expecially the WindowConductor one) and, except for a probably incorrect behaviour in a rare case (i.e. if you deactivate the view-model without requesting a close, and then perform a real close from the view-model, such operation is ignored due to the fact that the event has been detached), I could not spot an issue causing such a heavy memory leak.


How can I deactive view model with requesting a close? If you wish I can upload my application and you can try it and check if this problem is real. Have this problem some solution? I check Issue Tracker ->WindowManager+WindowConductor possible incorrect detaching from Deactivated . If I override method void Deactivated(object sender, DeactivationEventArgs e) in WindowConductor in solve this problem? I have problem in caliburn micro find WindowConductor class :[

 

Have you tried forcing a

GC.Collect();
GC.WaitForPendingFinalizers();

Yes, it doesn’t help decrease memory usage.

 

 

Finally, can I ask you how you identified the problem? Does your application try an OutOfMemoryException? Are you using a memory profiler? Or you just guessed that a problem is occurring since the application memory usage increases?


 I use ANTS Memory profiler. Quick example. Chat view consist two bindable richteboxes with conversation ( text + smiles images).   

I open chat view, publish some messages and close WPF window. Then I use ANTS Memory profiler and I see  richtextboxes object are still alive.  I suppose after I close view it also destroy UI controls in view

also object of chat view model is still alive.

I use simple logic. My chat view models are non shared, so my chat view are non shared (non singleton instaces). If I open chat view and then I close chat view. I don’t have instaces of this object in memory. 

After I open/close chat screen 10 - 20 times memory is still increasing.


 

 

Jan 6, 2011 at 3:03 PM

Uhmmm, then it would seem that you have spot an issue... using a non-shared view-model is perfectly legit, and from what I can tell, your design is proper.

I found that ANTS profiler is quite good, but I prefer .NET Memory Profiler since the retention graph proposed by the first is less readable to me that the reference view provided by the second.

If it is not possible to share a small repro project (which would really help), can you share an image of the retention graph of your view-model, produced by ANTS profiler? This could shed some light on the issue, since it could be possible to identify which objects are retaining a reference to the view-models.

Jan 6, 2011 at 6:38 PM
Edited Jan 6, 2011 at 6:43 PM
BladeWise wrote:

Uhmmm, then it would seem that you have spot an issue... using a non-shared view-model is perfectly legit, and from what I can tell, your design is proper.

I found that ANTS profiler is quite good, but I prefer .NET Memory Profiler since the retention graph proposed by the first is less readable to me that the reference view provided by the second.

If it is not possible to share a small repro project (which would really help), can you share an image of the retention graph of your view-model, produced by ANTS profiler? This could shed some light on the issue, since it could be possible to identify which objects are retaining a reference to the view-models.

I can upload VS project on some web host such as rapidshare, netload or which you wish.

I use .NET Memory profiler on test, and here are screens:

1. Basic situation is ist open only main screen: http://i53.tinypic.com/2nau15s.jpg

2. I open 2 chat screen : http://i52.tinypic.com/358uhyw.jpg

3. I close 2 chat screen: http://i56.tinypic.com/24uzudk.jpg

If you compare data on screens in 2. and 3. you can see in memory are still alive 2 objects of  chat screens and chat view models class.


 

Jan 7, 2011 at 4:30 PM

If you want to debug it yourself, just go in the Instance details tab and check which objects are retaining a reference to the view-models.

Otherwise, feel free to upload the project on MediaFire or whatever file sharing site you prefer.