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:
- 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:
- 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:
- 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?
- 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.
- 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
OnUnLoadalso does not get called on the inner panels.
Any advice welcomed…