How to create a singleton ViewModel - aka ViewModelLocator (MVVM Light?)

3516
3
07-26-2016 07:05 AM
HoriaTudosie
Occasional Contributor II

I need a VM instantiated by whatever Tool/Button was activated firstly, but shared by the other tools as well.

Tags (1)
0 Kudos
3 Replies
CharlesMacleod
Esri Regular Contributor

I am not sure I understand the reference to MVVM Light and ViewModelLocator??......however, the closest thing to that construct would be the DockpaneManager class and FrameworkApplication.DockPaneManager.Find("the id of the dockpane"). The returned DockPane is your ViewModel instance and it is a singleton. This pattern is also repeated in other aspects of Pro. For example, an Embedded Control if you are using such in a Map Tool, can be accessed via a MapTool's OverlayEmbeddableControl property: which also returns your ViewModel - again, a singleton. These are view models created by Framework from the definitions added to your Config.daml. You cannot create them.

If you are referring to a ~custom~ view model - one that you are making rather than one defined in your Config.daml - then you are responsible for instantiating and passing that guy around.

To make a property, method, instance, etc globally available within your Add-in you must add such a method or property to your Module class (to which all objects in your Add-in have access)

0 Kudos
HoriaTudosie
Occasional Contributor II

Maybe I'm doing wrong in ArcGIS Pro, because I've started the first application/Extension with the old good habits from pure WPF applications.

But usually, you connect the View model in XAML like this:

<Grid x:Name="main" >
  <Grid.DataContext>
        <local:GfxViewModel/>
  </Grid.DataContext>

(Where Grid may be any control in the page, not necessary the root, and the ViewModel may not be the main one handeled by the DockPaneManager.)

This approach, with proper techniques, let the page function in DesignMode as well using the Inversion Of Control pattern (somehow, injecting a corresponding model for RealTime, another for DesignMode and some more for unit tests...))

The problem is that if you associate the same ViewModel with another page in this way, each page creates its own instance, so the data you want to share is different in each instance.

What the above code references in line 3 is actually the parameter-less constructor of the ViewModel. You cannot add a parameter, refer a Singleton or a a static reference (although a static resource does the job...)

This is where the VMLocator comes. It is actually a static reference that manages VMs. It needs however a place where to instantiate its Singleton.

DockPanelManager is not a very good place, because you may need the VM instance in a Tool as well for example!

GalaSoft's MVVM Light may instantiate singletons managed by the static resource VMLocator in a top page named APP.XAML.

VMLocator is a very complex piece of code - I dare not reproduce it, mostly because of the testing involved.

It is a manager that can manage multiple VMs, singletons or not.

I already have a solution in the code behind, but I ask for a professional solution if somebody already have put it in ArcGIS Pro.

In my extension I have a DockingPanel that handle the main work with the above VM. I open it with a Ribbon Button.

I have another DockPanel that I open with another button (in a split of another ribbon button) that displays a Pie Chart (the beginning of a Dashboard,) with the data from the first Panel's VM.

Users may activate these buttons in any order, but obviously one of them is wrong.

My code behind in the Chart Panel check the instance of the Main Panel and give up if was not instantiated: the panel comes blank.

Could come better if it could make the instance first: could display a pie with four equal sectors (Eventually a message) which are the defaults of the DataSource. Will cange as soon as the VM will get notifications (Property Changed) from the first page.

But this solution would require instantiating the (same) VM of the Working Panel also in code. This makes the Design Time complicated... (And I don't have a general-enough place where to put the singleton.)

(Blocking the button of the Pie Panel until the Working panel opens also works, but then the ToolTip of the button - not mentioning the panel itself - won't come and I'll have to write a manual or help, which makes the User Experience gross...)

Note that instantiating VMs in the code behind, which is technically the View, breaks the MVVM pattern, making the design difficult and the testing incomplete!

Note also that a DockpaneViewModel is not exactly a ViewModel: it contains an internal static class for the button to open it, with override methods, which you have to trust - not to test...

0 Kudos
CharlesMacleod
Esri Regular Contributor

Right, no I get what you are trying to do, the distinction I was trying to make in my original comment was that View Models created as Pro Framework elements -eg a View Model that is subclassed from ArcGIS.Desktop.Framework.Contracts.DockPane - (another example I gave was an embedded control but there are others) are created and managed by Pro - their lifecycle, datacontext, the whole smash. There are no substitutes (eg GalaSoft) for what Pro does via its Framework and no dependency injection for those classes.

Now, if you had custom content, say a User Control and an associated proprietary View Model, that you wanted to re-use on "X" Dockpanes (eg - in the UserControl hosted on a Pro Dockpane you had a content provider to which you were binding your custom control) then this would be a relationship that you would manage as you wish within the limitations of Add-ins, etc.. I guess that was what I was trying to say.

Of course, as you state, you can not create the "parent" "Pro" Dockpane and its user control (to which your custom User Control was bound) in a Unit Test scenario, or otherwise.

0 Kudos