Panel lifespan and disposal

Hi all,
I’m trying to understand the behaviour and lifespan of panels that get registered in Rhino.

In the documentation for Panels.RegisterPanel it states that:

In Windows there is only one panel created which gets recycled for each new document. On the Mac a panel will be created for each open document and destroyed when the document closes. In certain situations in Mac Rhino a a panel may get created and destroyed multiple times when opening/closing a panel while editing a document.

However the behaviour I observe (on Rhino 6 Windows) is that the panel gets disposed an re-initialized when a new document is opened.

Minimal repro source code, with relevant commits as follows:

Commit: initial commit

Command: TabControlTest
TabControlPanel Initialized
Command: New
TabControlPanel Disposing
TabControlPanel Initialized

So the first issue:

  1. The control is disposed and recreated. This is fine, though contrary to the documentation.

If we then go and add some child content:

Commit: add tabpages

Command: TabControlTest
TabControlPanel Initialized
MyTabPage Initialized
MyTabPage Initialized
MyTabPage Initialized
Command: New
TabControlPanel Disposing
TabControlPanel Initialized
MyTabPage Initialized
MyTabPage Initialized
MyTabPage Initialized

The second issue:

  1. Dispose does not appear to be called recursively, meaning that we don’t get the opportunity to dispose of child controls (in this case, the the MyTabPage instances).

Due to the above, if we add a simple ViewModel that is intended to subscribe/unsubscribe from an event, we get:

Commit: add datacontext

 Command: TabControlTest
 TabControlPanel Initialized
 MyTabPage Initialized
 MyTabPage Initialized
 MyTabPage Initialized
 Command: Box
 This should only print once per tab page.
 This should only print once per tab page.
 This should only print once per tab page.

(expected behaviour, but then after a new document):

 Command: New
 TabControlPanel Disposing
 TabControlPanel Initialized
 MyTabPage Initialized
 MyTabPage Initialized
 MyTabPage Initialized
 Command: Box
 This should only print once per tab page.
 This should only print once per tab page.
 This should only print once per tab page.
 This should only print once per tab page.
 This should only print once per tab page.
 This should only print once per tab page.

In this case it’s treating RhinoDoc as a placeholder singleton/static model, and AddRhinoObject as an event fired by the model.

The reason this matters:
In this scenario:

  • View creates a ViewModel (datacontext)
  • ViewModel subscribes to events in Model (static/singleton instance, persistent across Rhino documents etc.)
  • When the view is disposed, the viewmodel needs to unsubscribe from events in the model.

So, because the tab pages weren’t disposed of, the datacontext is not disposed and the event is not given the opportunity to unsubscribe.

This is fine for the main view, but since the inner views (MyTabPage) are not having the Dispose method called they don’t get an opportunity to unsubscribe.

And for convenience: If the view is not getting disposed, then we’re able to use anonymous/lambda functions to subscribe the view to the model and hence result in much cleaner code. However because it is, we require a lot of cleanup code.

So questions would be:

  1. I use a tab page for each ‘separate’ part of the view. Each of these constructs their own view model/datacontext. What’s the appropriate way to ensure that each of these child pages get properly disposed of? Is there a design pattern I am missing?
  2. Should I be implementing the MVVM model differently somehow? This implementation appears to be in line with the limited eto examples.

My current workaround is to manually store a reference to each of the tabpages and call dispose of them, but this leads to messy code and it also needs to be done recursively for all embedded controls and their DataContext. And this doesn’t quite work, because while you can dispose of their datacontext you can’t call the Dispose method on the control (throws exceptions and hard crashes) because the owner control is already disposed.

Another alternative would be to have only one ViewModel that’s persistent across all views/tabpages, but this seems counter to MVVM as the ViewModels should be transient with the controls.

Notes:

  • In Rhino 5, if you embed the panel via the ToNative() Eto methods, behaviour is as per the documentation, and the panel does not get disposed when opening a new document.
  • There’s no OnClose method in Eto.Forms.Panel, but OnUnLoad also does not get called on the inner panels.

Any advice welcomed…

Cheers
Cam

@JohnM is in the best position to explain how the panel system works, as he has created it.

Hi @camnewnham,

Please review the following and let us know if you have any questions.

– Dale

@dale, amazing. Thanks for providing that, it explains a lot.
Definitely answers the first question, whereby I can use a System panel instead of a PerDoc panel to avoid panel reloading.

Regarding the second part, which is around disposing of inner controls, do you have any advice on how to structure this?

Since Dispose and presumably the IPanel methods are only called on the primary panel, is there a recommended code structure to ensure that the inner controls get properly disposed and/or have an opportunity to unsubscribe from events?

At the moment I manually keep track of the inner control’s datacontext and dispose of them when the primary is controlled, but it ends up a little messy.

Cheers
Cam

Hi @camnewnham,

I’m sure there are a number of ways of doing this - here is one…

TestCamNewnham.zip (44.9 KB)

– Dale