Ste Mc
Ste Mc

Reputation: 3

How to pass reference to class instance when creating viewmodel

I am developing a program that contains a main view and 5 user controls. I have created the XAML and created a view-model to sit behind each of these views in which the view is bound too.

I have a main Program class and I want to have some other classes such as product, testTool, etc.

When the application starts I load mainWindow, that will then create the mainWindowViewModel and in turn create the Program class.

When a user presses a button I want the mainWindowViewModel to display userControl1 but I want userControl1ViewModel to be able to see the Program class and access its properties and methods.

I keep reading things like "pass the instance of the class in by reference" which is fine but if userControl1View creates userControl1ViewModel how can I pass a reference to the 'program' class created at the start of the program?

Upvotes: 0

Views: 698

Answers (2)

Mark Feldman
Mark Feldman

Reputation: 16119

This is what dependency injection is designed to solve.

First of all, if you're doing MVVM then you should be able to run your entire application without creating any views at all i.e. only view models. If you have a MainWindow with a ChildView (say) then in general you match those with corresponding view models:

public MainViewModel : ViewModeBase
{
    public ChildViewModel MyChild {get; } // gets assigned later

Then in your XAML:

<Window ...

   <local:ChildView DataContext="{Binding MyChild}" />

Sometimes you'll need MyChild to display different views, each of which will have its own corresponding view model, and you may need to change it at run-time. In those cases MyChild will need to be of type object (or some common base class) and will also need to support property change notification:

public class MainViewModel : ViewModelBase
{
    private object _MyChild;
    public object MyChild
    {
        get { return this._MyChild; }
        set
        {
            if (this._MyChild != value)
            {
                this._MyChild = value;
                RaisePropertyChanged(() => this.MyChild);
            }
        }
    }
}

Then in your XAML you create a ContentControl instead:

<Window ...

   <ContentControl ="{Binding MyChild}" />

With this is place you then use DataTemplate in your window or application Resources section to specify which views are matched to which view models:

    <DataTemplate DataType="{x:Type vm:FooViewModel}">
        <view:FooView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:BarViewModel}">
        <view:BarView />
    </DataTemplate>

So now if you do something like this in your MainViewModel...

this.MyChild = new FooViewModel();

...the ContentControl is automatically populated with a control of type FooView. Furthermore, it's DataContext will automatically be set to the instance of FooViewModel that you created. And you then re-assign it like so:

this.MyChild = new BarViewModel();

...then the FooView will be replaced with a BarView.

So with DataTemplating in place all you have to worry about is passing references of your ViewModels into each other, and that's where dependency injection comes in. It's a big topic, I suggest you go and read up on it, but the idea is you create all of your view models via the DI framework (instead of the new operator) and let it glue all the bits together. Your Products, for example, might be part of a repository class that manages all of them, so you start by declaring an interface:

public interface IProductRepository
{
    Products[] AllProducts();
    Product GetProductByName(string name);
    ... etc ...

You then create an actual class that implements this interface and during setup you give your dependency framework the rules for what it should do whenever anything requests an IProductRepository (use a single instance, create a new one etc). Then, whenever anything in your entire application needs to access the product repository, all it has to do is declare a property with an [Inject] tag (this is if you use Ninject, each library has it's own way of doing this):

public class MyClass
{
    [Inject]
    public IProductRepository ProductRepo {get; set;} // <-- gets injected

Now, when you create an instance of type MyClass the dependency injection framework will create it for you and automatically initialize ProductRepo using the rules you provided it.

That's a very simple overview of how DataTemplating and Dependency Injection work in MVVM, once you start using them you'll wonder how you ever managed without. The main issue in your question, as far as I can tell, is that you're trying to get your view models to talk to each other. In general that's not how MVVM is implemented. View models communicate via services that get injected into them As a general rule of thumb their job is to serve as the conduit between those services and the front-end GUI elements, not each other.

Upvotes: 1

David
David

Reputation: 10708

What you're talking about is not actually simple process, what you're talking about is architecture to get the references you expect where you expect them. This can be solved a rather huge number of ways, so I'm going to throw out a fairly unsound but extremely quick example below. Architectural problems are noting inline with // HACK:s

Typically, you'll want the Models coming from a central location, such as database backing, which controls handing over the proper instance.

public abstract class Model
{ 
    // HACK: Don't bother wiring up OnPropertyChanged here, since we don't expect ID to get updated that often, but need a public setter for the Provider
    Guid ID { get; set; }
}

// HACK: While using a generic here makes for readable code, it may become problematic if you want to inherit your models
public class ModelProvider<TModelType> where TModelType : Model, new()
{
    // HACK: Use better dependency injection than this
    private static ModelProvider<TModelType> _instance = new ModelProvider<TModelType>();
    public static ModelProvider<TModelType> Instance => _instance;
    private ModelProvider() { }

    // TODO: Make this into a dictionary of WeakReferences so that you're not holding stale data in memory
    ConcurrentDictionary<Guid, TModelType> LoadedModels = new Dictionary<Guid, TModelType>();

    private TModelType GenerateModel(Guid id) => new TModelType { ID = id };
    private TModelType LoadKnownModel(Guid id)
    {
        throw new NotImplementedException("Implement a data store to get known models");
    }

    public TModelType GetNew() => LoadedModels.AddOrUpdate(Guid.NewGuid(). GenerateModel);
    public TModelType GetById(Guid id) => LoadedModels.GetOrAdd(id, LoadKnownModel);
}

And then your ViewModels have access to

ModelProvider<Product>.Instance.GetById(WellKnownGuid);

For testing, WellKnownGuid might as well be a static id in Program

Upvotes: 0

Related Questions