WPF - Use a single Model with multiple ViewModels - c#

How would you allow multiple viewmodels to share the same model?
I'm creating the viewmodels in an ApplicationViewModel that is used for switching between views using a DataTemplate with the selected VM.
public ApplicationViewModel()
{
//Add pages
BasePageViewModels.Add("Home Page", new HomeViewModel());
BasePageViewModels.Add("Summary Page", new SummaryViewModel());
BasePageViewModels.Add("AddTestRun Page", new AddTestRunViewModel());
//some code here
CurrentBasePageViewModel = BasePageViewModels["Home Page"];
}
I want to be able to access the same Data class from within each of the created VM's.
Ideally I'd pass in the Data class to each ViewModel with a parameter but that then causes setting DataContex within XAML to throw an error because the DataContext has no accessible constructors.
Update
I'm setting the DataContext in the other Views like so:
<UserControl.DataContext>
<viewModels:SummaryViewModel/>
</UserControl.DataContext>
but doing that creates a new instance of the ViewModel, rather than using the one bound to CurrentBasePageViewModel.

Definitelly, the solution is to pass model to viemodel's constructor.
Now, how to solve your problem with xaml?
First of all, from your question and posted code it is not clear, what's the problem. (the xaml code is missing).
I just guess, the problem is causing design time datacontext, since it requires parameterless constructor. There are two solutions:
Add parameterless constructor:
public class MyViewModel {
public MyViewModel(){
//design time ctor. Create design time data here
}
public MyViewModel(MyModel model){...}
}
Create new class for design time datacontext:
public class MyViewModelDesignTime : MyViewModel {
public MyViewModelDesignTime() : base(new MyModel()){
//design time ctor. Create design time data here
}
}
and use this class in xaml:
d:DataContext="{d:DesignInstance l:MyViewModelDesignTime, IsDesignTimeCreatable=True}"

public ApplicationViewModel()
{
//shared model
var model = new MyModel();
//Add pages
BasePageViewModels.Add("Home Page", new HomeViewModel(model));
BasePageViewModels.Add("Summary Page", new SummaryViewModel(model));
BasePageViewModels.Add("AddTestRun Page", new AddTestRunViewModel(model));
//some code here
CurrentBasePageViewModel = BasePageViewModels["Home Page"];
}
<UserControl x:Class="ApplicationView"
xmlns:vm="clr-namespace:MyApp.ViewModels"
xmlns:vw="clr-namespace:MyApp.Views">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<vw:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SummaryViewModel}">
<vw:SummaryView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AddTestRunViewModel}">
<vw:AddTestRunView />
</DataTemplate>
</UserControl.Resources>
<ContentControl Content={Binding CurrentBasePageViewModel} />
</UserControl>

try something like this:
HomeViewModel _homeViewModel;
SummaryViewModel _summaryViewModel;
public ApplicationViewModel()
{
//Add pages
_homeViewModel = new HomeViewModel();
_summaryViewModel = new SummaryViewModel();
//some code here
_homeViewModel.SomeProperty = 5;
CurrentBasePageViewModel = _homeViewModel;
}
Then some int property of your view homeViewModel will have value 5.
Also you can create property in homeViewModel that will hold reference to current ApplicationViewModel or create interface.
Example of the interface:
public interface IName
{
/// <summary>
/// Property name.
/// </summary>
string PropertyName { get; }
}
Then make ApplicationViewModel implement this interface and pass it to viewmodel. In viewModel create property. Something like:
public class HomeViewModel
{
IName _iName;
public HomeViewModel(IName name)
{
_name = name;
}
}
And your ApplicationViewModel:
public class ApplicationViewModel : IName
{
HomeViewModel _homeViewModel;
SummaryViewModel _summaryViewModel;
public ApplicationViewModel()
{
//Add pages
_homeViewModel = new HomeViewModel(this);
_summaryViewModel = new SummaryViewModel();
//some code here
_homeViewModel.SomeProperty = 5;
CurrentBasePageViewModel = _homeViewModel;
}
}

Related

How to handle dependency injection with WPF with multiple pages and navigation?

I've been trying to do the following thing in WPF:
A window with a login-page and a home-page.
Upon successfully authenticating a user, the window should display the home-page.
It should work by using the native WPF dependency injection framework.
But also...
There might be a page 3, 4, 5 and each of these pages should be able to call one another.
And maybe each of these pages could have pages inside them that can also call each other.
So the solution should be able to work with nested pages and navigations if possible.
What I have:
So, after looking for solutions in the stack forum I ended up with this composition approach.
Starting by the App.xaml, all services and viewmodels are initialized and the main window receives its viewmodel by injection:
private void ConfigureServices(ServiceCollection services)
{
services.AddSingleton<MainWindow>();
//ViewModels
services.AddSingleton<MainViewModel>();
services.AddSingleton<AuthViewModel>();
services.AddSingleton<HomeViewModel>();
}
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.DataContext = serviceProvider.GetService<MainViewModel>();
mainWindow.Show();
}
Then, the mainViewModel receives by injection every other viewmodel and stores them in a property.
public class MainViewModel
{
public IPageViewModel SelectedPage {get; set; } //PropertyChanged() removed for brevity.
public ObservableCollection<IPageViewModel> Pages {get; set;}
public MainViewModel(AuthViewModel authViewModel, HomeViewModel homeViewModel)
{
this.Pages = new ObservableCollection<IPageViewModel>() { authViewModel, homeViewModel};
this.SelectedPage = this.Pages.First();
}
}
All page viewmodels inherit from this interface so they can be retrieved from the collection by name and then added as the SelectedPage when needed.
public interface IPageViewModel : INotifyPropertyChanged
{
public string PageTitle { get; set; }
}
The window has a content control with a property content bound to the SelectedPage so it's updated.
<Window>
<ContentControl Content="{Binding SelectedPage}" />
</Window>
And it knows which view to use for each viewmodel by these data templates.
<Application.Resources>
<DataTemplate DataType="{x:Type vm:AuthViewModel}">
<views:AuthView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<views:HomeView />
</DataTemplate>
</Application.Resources>
But then... I noticed that this won't work, I can only call changes on the SelectedPage from within the mainViewModel.
public class AuthViewModel : BaseViewModel
{
public AuthViewModel() { }
public void AttemptLogin() {
// how
SelectedPage = Pages[1];
}
}
Issues
I could perhaps inject the mainviewmodel in all child models, but that would not look good and in fact from the start a lot of things are kind of a mess.
For example, I have to:
Add a service viewmodel for every viewmodel I create to the app.xaml.
Add each one of them as a parameter of the mainwindow viewmodel which looks ugly.
I'm probably doing this very wrong, I need help.
There are many possible solutions. A simple one is to introduce an event.
I also recommend to move and restrict the responsibility to select view models to the MainViewModel. Other page models should not be aware of the flow like who selects who. Otherwise this would add a too tight coupling, which is avoidable at this point.
public class MainViewModel
{
public IPageViewModel SelectedPage { get; set; }
private Dictionary<string, IPageViewModel> Pages { get; }
public MainViewModel(AuthViewModel authViewModel, HomeViewModel homeViewModel)
{
authViewModel.AuthenticationPassed += OnAuthenticationSuccessfull;
this.Pages = new Dictionary<string, IPageViewModel>()
{
{ nameof(AuthViewModel), authViewModel },
{ nameof(HomeViewModel), homeViewModel }
};
this.SelectedPage = this.Pages[nameof(AuthViewModel)];
}
public OnAuthenticationSuccessfull(object sender, EventArgs e)
{
(sender as AuthViewModel).AuthenticationPassed -= OnAuthenticationSuccessfull;
this.SelectedPage = this.Pages[nameof(HomeViewModel)];
}
}
class AuthViewModel
{
public event EventHandler AuthenticationPassed { get; }
...
}

Multiple ViewModels in same View

I have several different ViewModels that I would like to display in the same view (MainPage.xaml).
I'm new to this and don't know how to do it. I have tried to create a MainViewModel:
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
{
WeatherViewModel weatherView = new WeatherViewModel();
ForecastViewModel forecastViewModel = new ForecastViewModel();
DeparturesViewModel departuresViewModel = new DeparturesViewModel();
CalenderViewModel calenderViewModel = new CalenderViewModel();
}
public void GetAllViews()
{
weatherView.GetCurrentTemp();
forecastViewModel.GetForecastTemp();
departuresViewModel.GetDepartures();
calenderViewModel.GetCalender();
}
And in my MainPage.xaml.cs I have this:
public MainPage()
{
this.InitializeComponent();
this.DataContext = new MainViewModel();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as MainViewModel;
vm.GetAllViews();
}
I manage to display each ViewModel individually like this instead:
this.DataContext = new WeatherViewModel();
but I would like to display everything in the same View.
I think you're on the right track but missed some small but important pieces.
In your example code the MainViewModel class is currently setup with private fields where you really need public properties. Additionally, I would make sure ViewModelBase implements INotifyPropertyChanged if it's not already; that way none of the classes deriving from ViewModelBase need to worry about that part.
public abstract class ViewModelBase : INotifyPropertyChanged
{
/* INotifyPropertyChanged implementation +
whatever other common behavior makes sense
belongs in this class
*/
}
public class MainViewModel : ViewModelBase
{
public WeatherViewModel Weather { get; } = new WeatherViewModel();
public ForecastViewModel Forecast { get; } = new ForecastViewModel();
public DeparturesViewModel Departures { get; } = new DeparturesViewModel();
public CalendarViewModel Calendar { get; } = new CalendarViewModel();
}
In your view code behind file you're setting the data context to 2 different instances of MainViewModel - once in the constructor and once in the Loaded event handler. I'd stick with the constructor version or instead you could set the data context in XAML like this:
<MainPage.DataContext>
<MainViewModel>
</MainPage.DataContext>
Once the data context for the main page is setup and the view models are public properties then you can use bindings to access the state (properties) of the view models perhaps something like this:
<TextBlock Text='{Binding Path=Weather.CurrentTempCelsius, StringFormat='Current Temp: {0}°C'}' />
Multiple ViewModels in same View
You have many ways to approach. Fist way using x:bind. You could initialize each view model in the page resource and give them x:Name, then using x:bind to access specific property like following.
<Page.Resources>
<local:CalenderViewModel x:Name="CalenderViewModel"/>
<local:DeparturesViewModel x:Name="DeparturesViewModel"/>
<local:ForecastViewModel x:Name="ForecastViewModel"/>
<local:WeatherViewModel x:Name="WeatherViewModel"/>
</Page.Resources>
<Grid>
<TextBlock Text="{x:Bind WeatherViewModel.temperature}"/>
</Grid>
Other Way is that integrate all the ViewModels into MainViewModel. And coding.monkey provide the correct solution that you could refer directly.

Parameterless View Constructor In Prism 6

Here's what I'm trying to do (and there may be a better way):
My application is meant to keep track of players in a game. As players join the game events will be sent (using the event aggregator) to create a user control and add it to a List which is connected to an ItemControl. When they leave the game, events will be sent to remove the user control from the list.
The actual ItemContol looks like this - the Players binding is to the List so that it can show the individual player user controls:
<UserControl>
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Players}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PlayerListView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
This is inserted into a grid row/column of the main window view just by using the below command:
<views:PlayerListView />
The issue I am running into is this last line <views:PlayerListView /> as it is saying that "There are no accessible PlayerListView constructors". Here is the code behind with the constructor and I believe the reason I am getting this error is because the constructor isn't parameterless -- here is the code-behind.
public partial class PlayerListView : UserControl
{
public PlayerListView(IEventAggregator eventAggregator)
{
InitializeComponent();
DataContext = new PlayerListViewModel(eventAggregator);
}
}
I need the PlayerListViewModel to have the IEventAggregator in its constructor but am not sure the appropriate way to do it. I've viewed this Stack Overflow post: Prism MVVM - How to pass an IEventAggregator to my ViewModel but am not sure if that is the best way to achieve what I am trying to do or not and am slightly confused on how I would incorporate it.
Adding PlayerListViewModel
namespace PlayerTools.ViewModels
{
class PlayerListViewModel
{
#region Private Fields
IEventAggregator _eventAggregator;
#endregion
#region Public Commands
ICommand AddPlayerToListCommand { get; set; }
ICommand RemovePlayerFromListCommand { get; set; }
#endregion
public List<PlayerStackPanelViewModel> Players = new List<PlayerStackPanelViewModel>();
#region Constructor
public PlayerListViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
Players.Add(new PlayerStackPanelViewModel()
{
PlayerName = "Steven",
PlayerClass = "Tennis",
CurrentLevel = 10,
NumberOfDeaths = 0
});
Players.Add( new PlayerStackPanelViewModel()
{
PlayerName = "Steven",
PlayerClass = "Soccer",
CurrentLevel = 17,
NumberOfDeaths = 1
});
//Commands
AddPlayerToListCommand = new DelegateCommand<PlayerStackPanelViewModel>(AddPlayerToList);
RemovePlayerFromListCommand = new DelegateCommand<string>(RemovePlayerFromList);
}
#endregion
public void AddPlayerToList(PlayerStackPanelViewModel player)
{
Players.Add(player);
}
public void RemovePlayerFromList(string playerName)
{
Players.Remove(Players.Single(s => s.PlayerName == playerName));
}
}
}
You could use the ViewModelLocator to wire the DataContext of a view to an instance of your view model:
PlayerListView.xaml.cs:
public partial class PlayerListView : UserControl
{
public PlayerListView()
{
InitializeComponent();
}
}
PlayerListView.xaml:
<UserControl ... prism:ViewModelLocator.AutoWireViewModel="True" />
Your use of the event aggregator should be implemented in the view model and not in the view.

WPF MVVM one view multiple object types

Currently I'm learning WPF with MVVM and have maybe a crazy idea...
I have several simple classes:
public class Car : IProduct
{
public int Id {get;set;}
public string Brand {get;set;}
// some custom properies
}
public class Seat : IProduct
{
public int Id {get;set;}
public string Brand {get;set;}
// some custom properties
}
Idea was that I have one editor view for diferent models.
public class ProductViewModel<T> : ViewModelBase, IProductViewModel<T> where T : IProduct
{
private T m_editorModel;
public T EditorModel
{
get { return m_editorModel; }
set
{
m_editorModel = value;
RaisePropertyChanged(() => EditorModel);
}
}
public Type ModelType
{
get { return typeof(T); }
}
}
Which can be afterwords set to view DataContext
viewModel = ViewModelFactory.CreateViewModel<IProductViewModel<Car>>();
view = ViewFactory.CreateView<ProductView>();
view.DataContext = viewModel;
// etc...
The problem is that I don't know is it possible or how to create in run time
ObservableCollection of same object EditorModel.
Is it maybe easier path to create for each class it's own view and viewmodel or something totally different?
In MVVM in general [I'm not speaking for everyone here], you don't want to be instantiating views from code. Instead we work with and manipulate data. To change views, we change view models and often set the connections between the two in simple DataTemplates:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UsersViewModel}">
<Views:UsersView />
</DataTemplate>
This way, we don't need to explicitly set any DataContexts. We can simply have a BaseViewModel property that each view model extends:
public BaseViewModel ViewModel
{
get { return viewModel; }
set { if (viewModel != value) { viewModel = value; NotifyPropertyChanged("ViewModel"); } }
}
We can change view models and therefore views like this:
ViewModel = new UsersView();
Then we can display the relating view in a ContentControl like this:
<ContentControl Content="{Binding ViewModel}" />
Finally, in my opinion, you really should create a view model for each view... the view model's sole job is to provide the data and functionality for each view. So unless you have multiple identical views, you'd need different view models. It is however possible to have one view model that all of the views bind to, but I'd advise against that for large applications.

Lack of knowledge to initializing viewmodel

Okay. So what I need to do is to initialize a ViewModel using a constructor. The problem is I can't create the constructor due lack of knowledge. I'm new to MVVM (or c# in general for that matter) and had to get some help to implement this code:
public class ViewModel
{
private static ViewModel instance = new ViewModel();
public static ViewModel Instance
{
get
{
return instance;
}
}
}
However, I fail to create a constructor to place this code.
DataContext = ViewModel.Instance
It is meant to go into two different pages to pass a value between TextBoxes.
I'm also confused as to whether I should put the ViewModel in both the main window and the page or in just one of the two.
So, anyone can help?
Follow this pattern:
This part is how your model classes should look like,
Even if you use entity framework to create your model they inherit INPC.. so all good.
public class Model_A : INotifyPropertyChanged
{
// list of properties...
public string FirstName {get; set;}
public string LastName {get; set;}
// etc...
}
each view model is a subset of information to be viewed, so you can have many view models for the same model class, notice that in case your make the call to the parameter-less c-tor you get auto instance of a mock model to be used in the view model.
public class ViewModel_A1 : INotifyPropertyChanged
{
public Model_A instance;
public ViewModel()
{
instance = new instance
{ //your mock value for the properties..
FirstName = "Offer",
LastName = "Somthing"
};
}
public ViewModel(Model_A instance)
{
this.instance = instance;
}
}
And this is for your view, if you view in the ide designer you will have a mock view model to show.
public class View_For_ViewModelA1
{
public View_For_ViewModel_A1()
{
//this is the generated constructor already implemented by the ide, just add to it:
DataContext = new ViewModel_A1();
}
public View_For_ViewModel_A1(ViewModel_A1 vm)
{
DataContext = vm;
}
}
XAML Side:
<Window x:Class="WpfApplication1.View_For_ViewModel_A1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModel="clr-namespace:WpfApplication1"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance ViewModel:ViewModel_A1, IsDesignTimeCreatable=True}"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
</Grid>
</Window>
In a more advanced scenario you would want to have a single view model class to relate to several model classes.. but you always should set a view to bind to a single view model.
if you need to kung-fu with your code - make sure you do that in your view model layer.
(i.e. creating a view-model that have several instances of different model types)
Note: This is not the complete pattern of mvvm.. in the complete pattern you can expose command which relate to methods in your model via your view-model and bind-able to your view as well.
Good luck :)
I basically follow this pattern:
public class ViewModelWrappers
{
static MemberViewModel _memberViewModel;
public static MemberViewModel MemberViewModel
{
get
{
if (_memberViewModel == null)
_memberViewModel = new MemberViewModel(Application.Current.Resources["UserName"].ToString());
return _memberViewModel;
}
}
...
}
To bind this to a page is:
DataContext = ViewModelWrappers.MemberViewModel;
And if I'm using more than 1 ViewModel on the page I just bind to the wrapper.
DataContext = ViewModelWrappers;
If you or anybody else, who's new to the MVVM, gets stuck here, for example at the "INotifyPropertyChanged could not be found". I recommend trying some example-MVVM's or tutorials.
Some I found useful:
http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
https://www.youtube.com/watch?v=EpGvqVtSYjs&index=1&list=PL356CA0B2C8E7548D

Categories

Resources