1
votes

Prism can create an unneeded ViewModel if you have a one-arg constructor on your view. I am trying to understand how this can be avoided, or, if I can design something to work differently. Here is what happens.

The XAML view declares ViewModelLocator.AutoWireViewModel:

mvvm:ViewModelLocator.AutoWireViewModel="True"

And the class declares two constructors:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public MainWindow(MainWindowViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
    }
}

There is a reason that I declare the one-arg constructor: it is because the ViewModel is serializable; and when deserialized, the view is constructed by explicitly invoking that constructor with the restored ViewModel. But the issue can happen in two ways.

First, when you invoke the one-arg constructor:

MainWindowViewModel viewModel = new MainWindowViewModel();
MainWindow window = new MainWindow(viewModel);

Then Prism constructs the view, and that invokes the ViewModelLocator from the XAML, which creates and sets a ViewModel ... And then your explicit argument is set; replacing the auto-created instance (or if you reverse the lines in the constructor, than your explicit argument is actually wiped off).

AND, perhaps unexpectedly, or perhaps by some folly in my understanding, or some other unknown design aspect, it ALSO will happen if you resolve the view from the Container --- you might do this expecting to invoke the default constructor for the view; but that in fact does not happen; and, once again you will create two ViewModels:

MainWindow window = Container.Resolve<MainWindow>();

This line of code actually begins by discovering the one-arg constructor on the View, and then RESOLVING a ViewModel and invoking that constructor ... which again triggers the XAML auto-ViewModel; and then your one-arg constructor continues to wipe off the auto-ViewModel ...

It's consuming resources; and in fact, I tripped an exception where the view was binding based on some other state that was inconsistent with the ViewModel that I was expecting to explicitly be set.

I can't see a way to defeat the auto-created instance, and so I am not seeing how to invoke a one-arg constructor around the AutoWireViewModel behavior; or, how to resolve the view from the Container and avoid two ViewModels being created.

Perhaps resolving the View from the Container is abuse if it hasn't been registered, but the one-arg constructor seems to be reasonable, and it creates two instances ...

Is there some way? [Perhaps you can customize that behavior to check for an existing DataContext and then not set it if present ... or something along those lines?]

I created a simple example on GitHub:

https://github.com/steevcoco/PrismAutoCreatesViewModelTwice

2
Why do you want to deserialize a view model? A view model does not contain anything worth serializing, it just provides data (that it gets from one or more servcies) in a format easily consumable by the view and commands for the view to bind to that change the data (through the same or different services) - Haukinger
It's possible that I'll refactor that; but it's because I have a complex tree for a DataGrid: 4 primary classes and almost 2 dozen descriptors that can update live in an editing window; and make up a dynamic data stream with view-customizing features (cell layouts, data sources, fonts, colors, behaviors). The tree simply models what the user sees. Everything is DataContext and can serialize state ... I could consider refactoring but there would be another level of abstraction and more code to handle saving view-customizing state in classes outside of the ViewModels ... Perhaps it will happen! - Steven Coco
I am struggling to find value in any of this. As Brian says below, pick one approach and use it. You are creating issues, not solving them. - R. Richards

2 Answers

2
votes

To be clear here, Prism isn't creating the ViewModel twice. You are. You do it once in the code behind with the ctor, and you do it again with the ViewModelLocator. Pick one approach and use it. There is no need to have two different ways of setting the VM on the same View.

You should read up on containers and how they work. That will help your understanding of what is going on here. Every thing is working exactly as it should.

0
votes

Well, for what it's worth, I have actually implemented a custom ViewModelLocator that works around this behavior. It is somewhat crude: it simply first checks if the current DataContext is non-null; and if so, the AutoWire will not create or set a Viewmodel.

I have updated the code in the repository for anyone interested.