How to pass reference to class instance when creating viewmodel - c#

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?

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.

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

Related

Is it possible to automatically register ViewModels and Views together on App.xaml.cs

Is it possible to automatically register a view model with a view without a class library like Prism on the OnStartUp method of App.xaml.cs
I have something like this on my previous Prism Xamarin project on the application startup.
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//Registered Navigation Containers
containerRegistry.RegisterForNavigation<LoginPage>("Login");
containerRegistry.RegisterForNavigation<RegisterPage>("Register");
containerRegistry.RegisterForNavigation<ProfilePage>("Profile");
containerRegistry.RegisterForNavigation<CreateAppointmentPage>("CreateAppointment");
containerRegistry.RegisterForNavigation<NotificationPage>("Notification");
//Dependency Services
containerRegistry.RegisterSingleton<IConnectivityService, ConnectivityService>();
containerRegistry.RegisterSingleton<IAuthenticationService, AuthenticationService>();
containerRegistry.RegisterSingleton<IAppAPIService, AppAPIService>();
containerRegistry.RegisterSingleton<IPushNotificationManager, PushNotificationManager>();
}
My current company wants to avoid using libraries. My goal is to have a clean code-behind for all views. I would like to avoid something like in my code-behinds:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
I hope my question is clear. Thank you.
No, there is no built-in feature for that in WPF. That is what libraries like Prism are for, so you do not have to reinvent the wheel. In the background, Prism uses multiple mechanisms to achieve that.
A dependency injection container to register and resolve types
Sets to track which view belongs to which view model
Discovery of view models via reflection and naming conventions
An attached property to assign a view model automatically to a view.
...
Depending on your requirements you would have to write parts of that yourself or use alternative concepts that are more lightweight, like a view registry type and a factory to create them, for exapmle:
public interface IViewRegistry
{
void Register<TView, TViewModel>()
where TView : FrameworkElement
where TViewModel : INotifyPropertyChanged;
Type GetViewModelType<TView>()
where TView : FrameworkElement;
}
public interface IViewFactory
{
TView Create<TView>()
where TView : FrameworkElement;
}
The actual view registry implemenatation would have a dictionary to keep track of the mapping between views and view models that can is exposed by GetViewModelType. The type constraint FrameworkElement ensures that you can set a DataContext.
public class ViewRegistry : IViewRegistry
{
private Dictionary<Type, Type> _mappings;
public ViewRegistry()
{
_mappings = new Dictionary<Type, Type>();
}
public void Register<TView, TViewModel>()
where TView : FrameworkElement
where TViewModel : INotifyPropertyChanged
{
_mappings.Add(typeof(TView), typeof(TViewModel));
}
public Type GetViewModelType<TView>()
where TView : FrameworkElement
{
return _mappings.ContainsKey(typeof(TView)) ? _mappings[typeof(TView)] : null;
}
}
The factory would either create new instances of a view and its view model using reflection or via a dependency injection container and assign the DataContext. I really recommend you to use a dependency container, because your view models might have lots of dependnecies and you would have to resolve or create them yourself. See #BionicCode's comment on that.
public class ViewFactory : IViewFactory
{
private readonly IViewRegistry _viewRegistry;
public ViewFactory(IViewRegistry viewRegistry)
{
_viewRegistry = viewRegistry;
}
public TView Create<TView>() where TView : FrameworkElement
{
var viewModelType = _viewRegistry.GetViewModelType<TView>();
var view = // ...resolve the view via dependency injection or create it via reflection using its type
var viewModel = // ...resolve the view model via dependency injection or create it via reflection using its type
view.DataContext = viewModel;
return view;
}
}
In your application, the process of registering an resolving views now looks similar to Prism.
var viewRegistry = new ViewRegistry();
var viewFactory = new ViewFactory(viewRegistry);
viewRegistry.Register<MainWindow, MainWindowViewModel>();
var mainWindow = viewFactory.Create<MainWindow>();
There's nothing automatic within the net framework.
You could mitigate the issue using viewmodel first and datatemplates. This is how I would usually work in a single window app.
I have a resource dictionary merges in app.xaml and that associates a usercontrol with a viewmodel by type.
You can see a very basic version used here:
https://social.technet.microsoft.com/wiki/contents/articles/52485.wpf-tips-and-tricks-using-contentcontrol-instead-of-frame-and-page-for-navigation.aspx
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginView/>
</DataTemplate>
Navigation is done by setting a CurrentView property in mainwindowviewmodel to an instance of a viewmodel. That is bound to the content of a contentcontrol in MainWindow. The datatemplate will then template out the appropriate usercontrol.
No navigationservice and viewmodels that need to be singletons can be cached in an object mainwindowviewmodel instantiates.
You then "only" have mainwindowviewmodel. There are some plusses to manually instantiating mainwindowviewmodel. That way you have control of when expensive instantiation occurs. It's not all automatically done as that viewmodel demands many services via it's ctor.
You can use any DI you like within that to resolve dependencies whilst instantiating the various viewmodels. Or just a mediator pattern with a lazy singleton provides instances cached inside itself.
You could write something of course.
The thing is you're probably going to be re-writing code which is very similar to something someone else already wrote. But since you likely don't have 6 months set aside for framework replacement work your version is likely to be rather simpler.
Caliburn micro uses naming conventions to associate viewmodels and views.
You could write something works like that and uses reflection.
Or maybe you could go back to your boss and discuss what his issue is. Personally, I'm not a fan of Prism due to it's complexity. Maybe this is the sort of issue he has. Maybe a simpler framework like mvvmlight would be more acceptable.

How to access to all viewmodels via the viewmodel base?

I've created a ViewModel class that have inside the implementation of INotifyPropertyChanged, now I also have other ViewModels that inherit from the ViewModel (base).
All working good actually but I have a doubt.
So let's say that I've in the CustomerViewModel an ObservableCollection called Price, like this:
private ObservableCollection<Models.Price> _price = new ObservableCollection<Models.Price>();
public ObservableCollection<Models.Price> Price
{
get { return _price; }
}
this ObservableCollection should be populated by other classes, 'cause I need to access to the same resource.
I really don't understand how can I do this in mvvm. I though to a Singleton ViewModel, so something like this defined in the base VM:
public static ViewModel Instance { get; set; }
So import all the childs VM into the base and access them via ViewModel.Instance.Price;
but doesn't seems a good practice for me. Any idea?
With this implementation, you can share the same Datasource to all ViewModels
public class PriceGenerator {
private PriceGenerator() {
this.Prices = new ObservableCollection<Price>();
this.Generate();
}
void Generate() {
//Generate Objects here
this.Prices.Add(generatedPrice);
}
public ObservableCollection<Price> Prices {
get;
}
private static PriceGenerator _instance;
public static PriceGenerator Instance => _instance ?? (_instance = new PriceGenerator());
}
There are generally two approaches to this.
Even if you don't have a real database/repository, implement a singleton class that simulates this. This singleton class should also implement INotifyPropertyChanged (and/or INotifyCollectionChanged as appropriate). All ViewModels will be able to access this simulated repository, and interested ViewModels can choose to subscribe to this repository's PropertyChanged callback. For your question, it is generally tidier to have a repository that handles just prices, rather than having a simulated repository that stores 101 different information.
Have a main ViewModel. Some people would visualize that the MainWindow being the main view, with a corresponding main ViewModel. This ViewModel is intentionally made a singleton, which other ViewModels can access through static call. This main ViewModel is basically acting just like #1 - it is like a repository that stores 101 different information. That main ViewModel is likely to look untidy, but it is simply to trace where things are stored - if you need any data, it's probably in there.
Personally, I prefer to use the first approach. lokusking's answer is an example of this approach. Although his "repository" does more than storing data.

Questions regarding appropriate use of ViewModelLocator in MVVM

I am working on a WPF/MVVM app using MVVM Light. Right now my ViewModelLocator is pretty standard; it includes a static constructor that registers the ViewModels via SimpleIoc and has properties returning the current instances of the ViewModels.
I don't know how appropriate this is, but I have been exploring using instances of ViewModelLocator in ViewModels to access properties of other ViewModels and to change ContentControl in one of my views. If there are any major problems with doing that, please let me know so that I can find a way around it. For example, I may have something in a ViewModel like:
private ViewModelLocator _viewModelLocator = new ViewModelLocator();
private void SomeMethod()
{
_viewModelLocator.OtherViewModel.SomeProperty = something;
}
In a different ViewModel I have the following:
private ViewModelLocator _viewModelLocator = new ViewModelLocator();
public ViewModelBase CurrentViewModel { get; set; }
private void SomeMethod()
{
CurrentViewModel = _viewModelLocator.SomeViewModel;
}
In this case, CurrentViewModel is bound to a ContentControl in my view.
At the moment being able to do this is very convenient but I'd like to get some input from more experienced programmers to make sure that I'm not shooting myself in the foot. If there is a problem with it, are there more acceptable routes I can take to achieve the same results?
Now, if there is nothing wrong with the aforementioned approach, I would like to know if it would be appropriate and/or acceptable to make ViewModelLocator static. To try things out, I did a quick changeover to a static ViewModelLocator. In my MainWindow.xaml, I set the DataContext to:
DataContext="{Binding Source={x:Static vm:ViewModelLocator.Main}}"
...and going back to the first example, I can use:
private void SomeMethod()
{
ViewModelLocator.OtherViewModel.SomeProperty = something;
}
and:
public ViewModelBase CurrentViewModel { get; set; }
private void SomeMethod()
{
CurrentViewModel = ViewModelLocator.SomeViewModel;
}
Right now the program works fine using a static ViewModelLocator, but it is in its infancy, so I'd like to know if this is something that could be a viable option in the future or if I should stay away from a static ViewModelLocator altogether.
Any advice or input on these issues would be greatly appreciated. I am still fairly new to programming and I would like to learn techniques that will serve me well in the long run.
If there are no glaring issues with what I'm doing here, please let me know as well.
Thanks.
It is considered incorrect to reference other view models from view models. This breaks the decoupling that is supposed to make you project more testable and maintainable. If I need to have properties accessable from a number of view models I create a service like iUniversalAppDataService and then use the dependency injection built into MVVM-Light to resolve when the vewimodel is created.
ie this is the constructor of your viewmodel:
public New(IUniversalAppDataService AppDataService)
{
_MyAppDataService = AppDataService;
}
That way any changes/properties within that service are available to any viewmodel that implements the service.
the service needs to be declared in the viewmodellocator as well:
SimpleIoc.Default.Register<IUniversalAppDataService , UniversalAppDataService >
I have used this method to create navigation services that respond to user navigation and of course data services from databases or web data services. I strongly suggest using this service approach because it is WAY easier to maintain in the long run should there be changes to your underlying data model or app architecture.

Two views - one ViewModel

I must have misunderstood the concept of ViewModels and Views. But at this moment I can't rebuild the application from ground and this time doing it better. My situation is that I have a view where the user can load files and read them, a plotter shows the graphs and some operations are implemented. I want to be able to generate reports (like summary) of the data but I want it in other view. I'm using ModernUI, this other view is in another tab.
What I want is have that two tabs synchronized, when I load a file in the "plotter tab", the file must be loaded in the other view too. For that I think what I need is to bind the view to the same ViewModel, where I have for example LoadedFiles = List<File>, so I will be able to achieve it. The problem is that if I bind it either
MainViewModel vm = new MainViewModel();
DataContext = vm;
or in XAML
<UserControl.Resources>
<UserControl.DataContext=local:MainViewModel/>
</UserControl.Resources>
I'm actually binding to different MainViewModels and the data is not shared anymore. Do I need some classes from MVVM libraries such Locator and so? How this could be done? What can I do in the future in order to have separate ViewModels for each View but the same (or different) data?
You could create a new class that has your LoadedFiles property and then each unique view model can reference this class. You can share the one class with these shared properties between multiple view models. I am using MVVMLight's Locator with an Autofac container to inject this class into each of my view models (basically using Inversion of Control and Dependency Injection).
You can read up on Inversion of Control and Dependency Injection here.
Some sample code-
public MyClass
{
public List<File> LoadedFiles{get; set;}
}
public ViewModelOne
{
public MyClass MyClassInstance {get; set;}
public ViewModelOne(MyClass myclass)
{
MyClassInstance = myclass
}
}
public ViewModelTwo
{
public MyClass MyClassInstance {get; set;}
public ViewModelTwo(MyClass myclass)
{
MyClassInstance = myclass
}
}
You could also use MVVMLight's Locator to set each View's DataContext to the appropriate View.
<UserControl x:Class="View1"
DataContext="{Binding ViewModel1, Source={StaticResource Locator}}"...>
Store the VM in a parent VM's property, then bind the property to two ContentPresenters using different ContentTemplates (containing the respective views).
You should ask yourself if both of your views should share the same viewmodel?
Does they share the sameproperties in the view model or that they have different properties?
If they should share the same viewmodel you should use locator create the viewmodel from the locator and pass the locator to the views.
Otherwise, You should have two viewmodels. in order to keep minimal cuppling between the viewmodels you should use a service which known by both of the viewmodels (better via interfaces). One viewmodel notify the service about action that have been performed, and the second viewmodel has been handle that action (By register to event)
Good Luck,
M. Moshe

MVVM : Share data between ViewModels

How do I share data between multiple ViewModels ?
For example there is a class named Project in application .
public class Project : ModelBase
{
private string _projectName;
public string ProjectName
{
get { return _projectName; }
set
{
_projectName = value;
RaisePropertyChanged(() => ProjectName);
}
}
}
In multiple ViewModels application should access ActiveProject.
What's the best way to share Project between ViewModels ?
Mediator Pattern ? (Messaging)
Static object
Singleton pattern (If yes how?)
I've used Messaging before but it needs much codding . For all ViewModels I've to create ActiveProject property and also have to register a messenger to update that.
I use MVVM Light framework.
Any code example would be appreciated.
I would create a ViewModel that acts as a parent to all the Project ViewModels. (Let's call it Solution)
The Solution ViewModel would have the property ActiveProject and an observable collection of Projects.
I would recommend the Mediator Pattern. I have used an EventAggregator for this type of messaging between VM's before and there is really not much to it.
Don't, don't. Don't use singletons this way in your MVVM application. In fact, the Project class should be a model for your ViewModels. Just pass it in vm's constructor. If you really need to share one instance of Project class in multiple vm's, then use factories and some type of cache when constructing view models. If your vm reguires some more information, just create special Model class which will derive from Project (or implement IProject), so you can easilly use interface segregation principle.
Singleton will definitely help. To implement, if I had a class named User:
private static User mInstance;
private User () //constructor
{
}
public static User Instance
{
get
{
if (mInstance == null)
mInstance = new User();
return mInstance;
}
}
You could have a static collection which your view model populate before you navigate to the new view model. The target view model can then retrieve the data from within it's constructor.
For example ViewModel1 (VM1) will create a Project and populate it. VM1 will then put the Project into a shard, static, collection. VM1 will then navigate to another view model (VM2). In the constructor of VM2 you would go to the collection and retrieve the Project placed in there by VM1.
If you used a dictionary of key-value pairs it would also allow you to share other data between view models.

Categories

Resources