I've data architecture issues. My goal should be to have bidirectional data communication
between ViewModels and the Model classes. I've one window with different usercontrols. Each usercontrol
has it's own data, but some properties are shared between these.
For each ViewModel I implemented two functions for synchronize the model and the viewmodel.
The model should kept updated, so I implemented in the PropertyChanged event the method call SyncModel.
This is so far not so nice, because when I call the constructor the method call chain is:
constructor -> SyncViewModel -> Property setter -> PropertyChanged -> SyncModel
Here is some sample code to understand my problem better:
public class SampleModel
{
public string Material { get; set; }
public double Weight { get; set; }
public double Length { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public object SharedProperty { get; set; }
}
public class SampleViewModelA : AbstractViewModel
{
public string Material
{
get
{
return _Material;
}
set
{
if (value != _Material)
{
_Material = value;
OnPropertyChanged(nameof(Material));
}
}
}
public double Weight
{
get
{
return _Weight;
}
set
{
if (value != _Weight)
{
_Weight = value;
OnPropertyChanged(nameof(Weight));
}
}
}
public object SharedProperty
{
get
{
return _SharedProperty;
}
set
{
if (value != _SharedProperty)
{
_SharedProperty = value;
OnPropertyChanged(nameof(SharedProperty));
}
}
}
public SampleViewModelA(SampleModel Instance) : base(Instance) { }
public override void SyncModel()
{
//If I wouldn't check here, it would loop:
//constructor -> SyncViewModel -> Property setter -> PropertyChanged -> SyncModel
if (Instance.Material == Material &&
Instance.Weight == Weight &&
Instance.SharedProperty == SharedProperty)
return;
Instance.Material = Material;
Instance.Weight = Weight;
Instance.SharedProperty = SharedProperty;
}
public override void SyncViewModel()
{
Material = Instance.Material;
Weight = Instance.Weight;
SharedProperty = Instance.SharedProperty;
}
private string _Material;
private double _Weight;
private object _SharedProperty;
}
public class SampleViewModelB : AbstractViewModel
{
//Same like SampleViewModelA with Properties Length, Width, Height AND SharedProperty
}
public abstract class AbstractViewModel : INotifyPropertyChanged
{
//All ViewModels hold the same Instance of the Model
public SampleModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public AbstractViewModel(SampleModel Instance)
{
this.Instance = Instance;
SyncViewModel();
}
protected virtual void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
SyncModel();
}
public abstract void SyncModel();
public abstract void SyncViewModel();
}
The real problem is, that the SharedProperty need to be updated between the SampleViewModelA and the SampleViewModelB. First I thought the Observer Pattern could help me, but the SharedProperties are to various to make it work with generic interfaces. Then I thought a datacontroller with change events could help me like this
public class SampleDataController
{
public SampleModel Instance { get; set; }
public delegate void SynchronizeDelegate();
public event SynchronizeDelegate SynchronizeEvent;
public void SetSharedProperty(object NewValue)
{
if (Instance.SharedProperty != NewValue)
{
Instance.SharedProperty = NewValue;
SynchronizeEvent?.Invoke();
}
}
}
If it would do it like this my AbstractViewModel would only communicate with the controller instead of the instance. The SyncModel function would call methods like SetSharedProperty instead of directly access.
The MainViewModel code could look like this.
public class SampleMainViewModel
{
public SampleViewModelA ViewModelA { get; set; }
public SampleViewModelB ViewModelB { get; set; }
public SampleDataController Controller { get; set; }
public SampleMainViewModel()
{
ViewModelA = new SampleViewModelA(Controller);
ViewModelB = new SampleViewModelB(Controller);
Controller.SynchronizeEvent += ViewModelA.SyncViewModel;
Controller.SynchronizeEvent += ViewModelB.SyncViewModel;
}
}
This would cause the problem, that the source for the SynchronizeEvent call is also
subscribed to the event itself. This wouldn't cause a infinity loop because I check if
values are equal to the new state, but it seems very ugly to me. There must be a better
way than this.
In my project I have 8 ViewModels and multiple model classes where I need to sychronize the data with
different shared properties.
I'm thankful for any help and hope the problems are so far understandable.
You already use a SampleMainViewModel which is composed of the other view model classes SampleViewModelA and SampleViewModelB.
Now all you have to do is to move all the properties that are shared between the view models/views (like the SharedProperty, but also Material and Weight) to the composed SampleMainViewModel or to a shared class in general. This way all your controls can bind to the same data source.
Also the communication between Model --> View Model should only take place via events: the Model can notify the View Model by exposing e.g., a DataChanged event. There is no real bi-directional communication/dependency between Model and View Model. That's the main characteristic of MVVM: the uni-directional dependency of the participating components - realized by implementing events, commands and especially by utilizing data binding.
The follwoing example shows how you bind your controls to shared properties and to unshared properties (those that are attributes of the specialized view model classes).
MainWindow.xaml
<Window>
<Window.DataContext>
<SampleMainViewModel />
</Window.DataContext>
<StackPanel>
<UserControlA Material="{Binding Material}"
SharedProperty="{Binding SharedProperty}"
UnsharedPropertyA="{Binding ViewModelA.UnsharedPropertyA}" />
<UserControlB Material="{Binding Material}"
SharedProperty="{Binding SharedProperty}"
UnsharedPropertyB="{Binding ViewModelB.UnsharedPropertyB}" />
</StackPanel>
</Window>
SampleMainViewModel.cs
public class SampleMainViewModel : INotifyPropertyChanged
{
public SampleViewModelA ViewModelA { get; }
public SampleViewModelB ViewModelB { get; }
/* Properties must raise INotifyPropertyChanged.PropertyChanged */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
// Example initialization
public SampleMainViewModel(SomeModelClass someModelClass)
{
this.ViewModelA = new SampleViewModelA();
this.ViewModelB = new SampleViewModelB();
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
someModelClass.DataChanged += UpdateData_OnDataChanged;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
SampleViewModelA.cs
public class SampleViewModelA : INotifyPropertyChanged
{
public object UnsharedPropertyA { get; set; }
}
SampleViewModelB.cs
public class SampleViewModelB : INotifyPropertyChanged
{
public object UnsharedPropertyB { get; set; }
}
"Your suggestion cause the disadvantage, that my separated ViewModels
are no longer encapsulated. And if I would move all my code with
shared properties to the MainViewModel, the class would end up very
large"
To address your comment: if you insist in having a view model per each control including duplicating properties etc., then you must take a different approach.
Also moving the shared/duplicate code out of your view models does not break encapsualtion - assuming that those classes do not contain duplicated code only.
But note that a view model per each control is not recommended. You have a view/page - an aggregation of multiple controls - which has a defined data context. All controls in this view share the same data context - usually, as views are structured context related.
That's why the FrameworkElement.DataContext is inherited by default.
Having a view model for each control makes things overly complicated and leads to a lot of duplicated code - and not only duplicate properties like in your example. You will find yourself duplicating logic too. And talking about testability, if you duplicate logic you will duplicate unit tests too.
This is because you are dealing with the same data and the same model classes.
You usually extract duplicate code to a separate class that is the referenced by the types that depend on this duplicate code. Refactoring your view model classes with this "no duplicate code" policy in mind would end up moving the "shared" properties to a separate class. Since we are talking about the data context of the same view, this separate class would be the view model class that is assigned to the DataContext of the page. I'm trying to say that your approach is doing the exact opposite: you duplicate code (and call it encapsulation). If the class ends up being very large because it contains a lot of properties then you may review your UI design - maybe you should split your big page into more pages with more concise content. This may will improve UX too.
Generally, there is nothing wrong with having a view model with some more properties. If your view model class contains lots of logic too, you can extract this logic to separate classes.
You can still use the pattern of the previous example, which is to listen to data changed events of the Model.
Either you implement a very general event like the above DataChanged event or several more specialized events like a MaterialChanged event. Also make sure to inject the same model instances into each view model.
The following example shows how you can have multiple different view model classes that expose the same data, where all these view model classes update themselves by observing their model classes:
MainWindow.xaml
<Window>
<Window.DataContext>
<SampleMainViewModel />
</Window.DataContext>
<StackPanel>
<UserControlA DataContext="{Binding ViewModelA}"
Material="{Binding Material}"
Weight="{Binding Weight}"
SharedProperty="{Binding SharedPropertyA}" />
<UserControlB DataContext="{Binding ViewModelB}"
Material="{Binding Material}"
Weight="{Binding Weight}"
SharedProperty="{Binding SharedPropertyB}" />
</StackPanel>
</Window>
SampleMainViewModel.cs
public class SampleMainViewModel : INotifyPropertyChanged
{
public SampleViewModelA ViewModelA { get; }
public SampleViewModelB ViewModelB { get; }
// Example initialization
public SampleMainViewModel()
{
var sharedModelClass = new SomeModelClass();
this.ViewModelA = new SampleViewModelA(sharedModelClass);
this.ViewModelB = new SampleViewModelB(sharedModelClass);
}
}
SampleViewModelA.cs
public class SampleViewModelA : INotifyPropertyChanged
{
/* Shared properties */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public SampleViewModelA(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
this.SharedProperty = this.SomeModelClass.SharedProperty;
// Listen to model changes
this.SomeModelClass.DataChanged += UpdateData_OnDataChanged;
this.SomeModelClass.MaterialChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void OnModelMaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
SampleViewModelB.cs
public class SampleViewModelB : INotifyPropertyChanged
{
/* Shared properties */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public SampleViewModelB(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
this.SharedProperty = this.SomeModelClass.SharedProperty;
// Listen to model changes
this.SomeModelClass.DataChanged += UpdateData_OnDataChanged;
this.SomeModelClass.MaterialChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void OnModelMaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
A variation of the first solution (which aimes to eliminate duplicate code) is to refactor your binding source, that exposes the shared properties, by extracting those properties to new classes according to their responsibilities.
For example, you can have your MainViewModel expose a MaterialViewModel class that encapsulates material related properties and logic. This way MaterialViewModel can be mnade available globally.
Given that you follow the one-data-context-class per view principle, you can limit the scope of the shared properties to specific pages by having only their specific view model classes expose the same MaterialViewModel instance:
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<StackPanel>
<MaterialControl DataContext="{Binding MaterialViewModel}"
Material="{Binding Material}"
Weight="{Binding Weight}" />
<UserControlB ... />
</StackPanel>
</Window>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
// Since defined in 'MainViewModel' the properties of 'MaterialViewModel'
// are globally shared accross pages
public MaterialViewModel MaterialViewModel { get; }
/* View model classes per page */
public ViewModelPageA PageViewModelA { get; }
// If 'ViewModelPageB' would expose a 'MaterialViewModel',
// you can limit the visibility of 'MaterialViewModel' to the 'ViewModelPageB' DataContext exclusively
public ViewModelPageB PageViewModelB { get; }
// Example initialization
public SampleMainViewModel()
{
var sharedModelClass = new SomeModelClass();
this.MaterialViewModel = new MaterialViewModel(sharedModelClass);
this.ViewModelPageA = new ViewModelPageA(sharedModelClass);
// Introduce the MaterialViewModel to a page specific class
// to make the properties of 'MaterialViewModel' to be shared inside the page only
this.ViewModelPageB = new ViewModelPageB(sharedModelClass);
}
}
MaterialViewModel.cs
public class MaterialViewModel : INotifyPropertyChanged
{
public string Material { get; set; }
public double Weight { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public MaterialViewModel(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
// Listen to model changes
this.SomeModelClass.MaterialDataChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will also trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
// It can make more sense to define such a command in the owning class,
// like SampleMainViewModel in this case.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void UpdateData_MaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
}
}
Related
I created a datagrid using a MVVM pattern (almost exactly this example). The end goal is to have a graph that plots all the data from this datagrid AND the datagrid should update when a point of the graph has been manually moved (drag). The plotted data is a bunch of inputs for a robot.
The way I intended to build this was with 1 model (list of input parameters), 2 viewmodels (one for the datagrid and one for the graph) and 2 views (same window).
The problem: both viewmodels should use/update the same ObservableCollection containing the list of inputs.
To tackle this issue I tried several approach:
Mediator pattern - I don't understand it
Dependency Injection - Same as for mediator pattern, all examples I
found were to hard for me to understand
Singleton patter - feels like I understand that one, but can't
properly implement it (I'm using this example that I find clear)
To make things simple, I am currently only focusing on the datagrid. So using a Singleton + MVVM pattern, I'm trying to have it work the same way it used to (commands to add/remove row, drag and drop, updating the ObservableCollection).
So this is the singleton class:
class SingletonDoliInputCollection : ViewModelBase
{
#region Events
void OnDoliCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Update item count
this.ItemCount = this.DoliInputCollection.Count;
// Resequence list
SetCollectionSequence(this.DoliInputCollection);
}
#endregion
#region Fields
private ObservableCollection<DoliInput> _doliInputCollection;
private int _itemCount;
//Singleton
private static SingletonDoliInputCollection _instance;
#endregion
#region Singleton Constructor
private SingletonDoliInputCollection() { }
#endregion
#region Properties
public ObservableCollection<DoliInput> DoliInputCollection
{
get => _doliInputCollection;
set
{
_doliInputCollection = value;
OnPropertyChanged("DoliInputCollection");
}
}
public static SingletonDoliInputCollection GetInstance(ObservableCollection<DoliInput> DoliInputCollection)
{
if (_instance == null)
{
_instance = new SingletonDoliInputCollection();
_instance.Initialise(DoliInputCollection);
}
return _instance;
}
public int ItemCount
{
get => _itemCount;
set
{
_itemCount = value;
OnPropertyChanged("ItemCount");
}
}
/// <summary>
/// Return selected item in the grid
/// </summary>
public DoliInput SelectedItem { get; set; }
#endregion
#region Manage Sequencing
/// <summary>
/// Resets the sequential order of a collection.
/// </summary>
/// <param name="targetCollection">The collection to be re-indexed.</param>
public static ObservableCollection<T> SetCollectionSequence<T>(ObservableCollection<T> targetCollection) where T : ISequencedObject
{
// Initialize
var sequenceNumber = 1;
// Resequence
foreach (ISequencedObject sequencedObject in targetCollection)
{
sequencedObject.SequenceNumber = sequenceNumber;
sequenceNumber++;
}
// Set return value
return targetCollection;
}
#endregion
#region Private Methods
#region Initialise
private void Initialise(ObservableCollection<DoliInput> DoliInputCollection)
{
//Create inputList
_instance.DoliInputCollection = new ObservableCollection<DoliInput>();
//Add items
_instance.AddInput("Load", 3, 2, 1);
_instance.AddInput("Position", 3, 11, 1);
_instance.AddInput("Position", 3, 2, 4);
_instance.AddInput("Load", 3, 2, 1);
//Subscribe to the event that gets trigger when change occurs
_instance.DoliInputCollection.CollectionChanged += OnDoliCollectionChanged;
//Start indexing items
this.DoliInputCollection = SetCollectionSequence(this.DoliInputCollection);
//Update if changes
this.OnPropertyChanged("DoliInputCollection");
this.OnPropertyChanged("GridParam");
}
#endregion
#endregion
#region Public Methods
public void AddInput(string CTRL, double Destination, double Speed, double Duration)
{
this.DoliInputCollection.Add(new DoliInput(CTRL, Destination, Speed, Duration));
}
#endregion
}
The ViewModel class looks like that:
public class DataGridVM : ViewModelBase
{
#region Constructor
public ObservableCollection<DoliInput> DoliInputCollection { get; set; }
public DoliInput SelectedItem { get; set; }
public DataGridVM()
{
//ObservableCollection<DoliInput> DoliInputCollection = new ObservableCollection<DoliInput>();
SingletonDoliInputCollection doliInputs = GetDoliInputCollectionInstance(DoliInputCollection);
DoliInputCollection = doliInputs.DoliInputCollection;
SelectedItem = doliInputs.SelectedItem;
Console.WriteLine(doliInputs.DoliInputCollection.ToString());
}
#endregion
#region Properties
public ICommand DeleteItem { get; set; }
public ICommand AddRow { get; set; }
#endregion
#region Private Methods
#region Initialise
private static SingletonDoliInputCollection GetDoliInputCollectionInstance(ObservableCollection<DoliInput> DoliInputs)
{
SingletonDoliInputCollection singleton = SingletonDoliInputCollection.GetInstance(DoliInputs);
return singleton;
}
#endregion
#endregion
}
And for the view, here is just on example of column formatting:
<!--[...]-->
xmlns:dataGrid="clr-namespace:InteractiveGraph.Grid"
<!--[...]-->
<DataGridTextColumn Binding="{Binding Path=(dataGrid:DoliInput.Speed), Mode=TwoWay}" Header="Speed" />
<!--[...]-->
Last a simplified version of the model (there are 3 other properties: CTRL, destination and duration, not displayed here)
public class DoliInput : ObservableObject, ISequencedObject
{
#region Fields
private double _speed;
private int _seqNb;
#endregion
#region Properties
public double Speed
{
get => _speed;
set
{
_speed = value;
OnPropertyChanged("Speed");
}
}
public int SequenceNumber
{
get => _seqNb;
set
{
_seqNb = value;
OnPropertyChanged("SequenceNumber");
}
}
#endregion
#region Constructor
public DoliInput(){ }
public DoliInput(string CTRL, double destination, double speed, double duration)
{
this._speed = speed;
}
#endregion
}
I tried to keep this as short as possible. If more info is necessary, I can add it.
Disclamer: I am obsviously not a professional coder. I'm doing this on the side and I try to learn new things. If you think my whole approach for this code is wrong, I'm entirely open to a new one.
Dependency injection means you pass the dependencies (in this case the data points) to the object (in this case view-model) instead of having objects create their own dependencies and having problems like the one you have right now when dependencies that need to be shared.
You can have the data centralized in some object that is passed to the constructors of both view-models. This however will require you to raise events/ implement INotifyPropertyChanged so that both view-models are aware of the change and raising PropertyChanged event.
You can also have one view-model being bound by two different views but make sure that you're not breaking the single responsibility principle. I think this approach should be preferred. Notice that you're not limited to any 1-1 relationship between view-models and views. You can have many views bound to one viewmodel, a view binding to several view-models etc...
In any case, avoid singletons, at least for this case.
Following on from a previous question I posted last week, I have a Chart/Graph application in which I aim to read data from three sensors and plot it.
I will be able to do this without too much difficulty but previously I had not followed MVVM principles as I was new to WPF and had absolutely everything in the code behind. Now I am in the process of rewriting my Chart/Graph app using MVVM.
The difficulty I am having is trying to split up the MainWindowViewModel to handle stuff elsewhere in other classes to avoid the class being long and complex.
I have the following:
MODEL
public class MainWindowModel
{
public ObservableCollection<SensorDataModel1> ocSensor1{ get; set; }
public ObservableCollection<SensorDataModel2> ocSensor2{ get; set; }
public ObservableCollection<SensorDataModel3> ocSensor3{ get; set; }
}
VIEWMODEL
public class MainWindowViewModel : ObservableObject
{
public ObservableCollection<Sensor1DataModel> ocSensor1Data
{
get { return _model.ocSensor1; }
set
{
_model.ocSensor1 = value;
OnPropertyChanged("ocSensor1Data");
}
}
private void SerialDataReceivedHandler(object sender,
SerialDataReceivedEventArgs e)
{
// parse all sensor data from Arduino to a list
List<string> arduinoData = e.ToString().Split(',').ToList();
// parses data here into 3 variables
// sends to each sensors class (pseudocode to simplify example)
sensor1Class.AddData(data1);
sensor2Class.AddData(data2);
sensor3Class.AddData(data3);
}
}
Sensor Class Example
// Does some conversions to the data and then updates the ObservableCollection
// in the MainWindowViewModel
public class Sensor1Class
{
MainWindowViewModel main;
public Sensor1Class()
{
main = new MainWindowViewModel();
}
public void AddData(List<string> data)
{
SensorDataModel1 model = new SensorDataModel1();
// does some stuff to the data here
// add parsed data to observable collection in viewmodel to be used by UI
main.ocSensor1Data.Add(model);
}
}
So here I have a basic MVVM architecture, I have a Sensor1/2/3Class to reduce some work in the MainWindowViewModel where it needs to do some conversions for the data that's read in.
Ideally I would like to have my own Axis, Label, LineSeries, ChartZoom etc. classes to take a lot of code out of the MainWindowViewModel.
Example: Have an Axis.cs class to hold axis specific data
public class Axis
{
// TODO - Axis class
private double AxisMin;
private double AxisMax;
#region setters
public void SetAxisLimits(double min, double max)
{
AxisMin = min;
AxisMax = max;
}
public void SetAxisMin(double value)
{
AxisMin = value;
}
public void SetAxisMax(double value)
{
AxisMax = value;
}
#endregion
#region getters
public double GetAxisMin()
{
return AxisMin;
}
public double GetAxisMax()
{
return AxisMax;
}
#endregion
}
So instead of all this being in the MainWindowViewModel I can hold it here, however would I need to create a separate Model & ViewModel for Axis, LineSeries, Label and so on and set the DataContext to each individually to have something like this?
I am basically trying to combine MVVM with more OO principles, but then I may be violating MVVM altogether by doing this. I want to know what the best way is to structure my application with a lot of classes and still update the same view.
I have a situation and I'm not sure if I'm doing it correct.
I have a ApplicationViewModel that is my "shell" for my whole application.
And within that viewmodel I have other child-ViewModels.
public ApplicationModelView()
{
ModelViewPages.Add(new GameViewModel());
ModelViewPages.Add(new EditGameViewModel());
//Set Current HomePage
CurrentPage = ModelViewPages[0];
}
#endregion
#region Properties
public BaseModelView CurrentPage
{
get { return _currentPage; }
set { _currentPage = value; OnPropertyChanged(); }
}
public List<BaseModelView> ModelViewPages
{
get
{
if (_modelViewPages == null){_modelViewPages = new List<BaseModelView>();}
return _modelViewPages;
}
}
#endregion
In my GameViewModel I display a list of objects from my model GamesModel that contains title,description etc.
When I click on an item in this list it becomes selected and then I want to change my View to EditGameViewModel with a button but I'm not sure if how to do it.
How can I get my child-ViewModel to change content in my parent-ViewModel?
Or should the child even do that?
EDIT
How I want it to function
I want when I select an item and click on button that I change from the view GameViewModel to EditGameViewModel with the data that I have selected from the list.
public void EditGame(object param)
{
//MessageBox.Show("From EditGame Function"); HERE I WANT TO CHANGE THE VIEWMODEL ON MY APPLICATIONVIEWMODEL
}
public bool CanEditGame(object param)
{
return SelectedGame != null;
}
I can offer something that works, but could be questionable. It all really depends on how you plan for your application to function.
First, similar to your MainViewModel, you want something like this:
public class MainViewModel : ObservableObject //ObservableObject being a property change notification parent
{
//Current view will always be here
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
//By default we will say this is out startup view
Navigate<RedViewModel>(new RedViewModel(this));
}
public void Navigate<T>(BaseViewModel viewModel) where T : BaseViewModel
{
ViewModel = viewModel as T;
Console.WriteLine(ViewModel.GetType());
OnPropertyChanged("ViewModel");
}
}
Now, since we are navigating this way, every child view needs to derive from BaseViewModel.
public class BaseViewModel : ObservableObject
{
private MainViewModel _mainVM;
public BaseViewModel(MainViewModel mainVM)
{
_mainVM = mainVM;
}
protected void Navigate<T>() where T : BaseViewModel
{
//Create new instance of generic type(i.e. Type of view model passed)
T newVM = (T)Activator.CreateInstance(typeof(T), _mainVM);
//Change MainViewModels ViewModel to the new instance
_mainVM.Navigate<T>(newVM);
}
}
Now we just really need to see how we are going to have child views delegate this call of change. So we will have a BlueView.
public class BlueViewModel : BaseViewModel
{
//Relay command to call 'ToRed' function
public ICommand ChangeToRed
{
get { return new RelayCommand(ToRed); }
}
//Requires MainViewModel for BaseViewModel
public BlueViewModel(MainViewModel main) : base(main)
{
}
//Calling BaseViewModel function. Passed BaseViewModel Type
public void ToRed(object param)
{
Navigate<RedViewModel>();
}
}
And a RedView:
public class RedViewModel : BaseViewModel
{
//Relay command to call 'ToBlue' function
public ICommand ChangeToBlue
{
get { return new RelayCommand(ToBlue); }
}
//Requires MainViewModel for BaseViewModel
public RedViewModel(MainViewModel main) : base(main)
{
}
//Calling BaseViewModel function. Passed BaseViewModel Type
public void ToBlue(object param)
{
Navigate<BlueViewModel>();
}
}
Our MainWindow.xaml could look like this:
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding ViewModel}" />
</Grid>
Doing this all children will be able to call to their parent that they would like a change. The BaseViewModel holds this parent for all children who derive, so they can pass it back and forth during navigation like a baton.
Again, Navigation all really depends on how you are using, building and planning for your application. It's not always a one size fits all way.
In my current project I've faced the following situation:
VM1 is used to be shown on a screen.
VM1 has a public property of VM2.
VM1 has a public property of VM3.
VM3 has a propertry that depends on VM2.
VM1 has no disposing mechanism.
At the beginning I thought about hooking to VM2.PropertyChanged event to check for the property I want and change the VM3 affected property accordingly, as:
public class VM1 : INotifyPropertyChanged
{
public property VM2 VM2 { get; private set; }
public property VM3 VM3 { get; private set; }
public VM1()
{
this.VM2 = new VM2();
this.VM3 = new VM3();
this.VM2.PropertyChanged += this.VM2_PropertyChanged;
}
private void VM2_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// if e.PropertyName equals bla then VM3.SomeProperty = lol.
}
}
This means that, since I can not unhook the event in this class, I have a memory leak.
So I end up passing an Action to VM2 so that it will be called when its important property changes the value, as:
public class VM2 : INotifyPropertyChanged
{
public Action WhenP1Changes { get; set; }
private bool _p1;
public bool P1
{
get
{
return _p1;
}
set
{
_p1 = value;
this.WhenP1Changes();
this.PropertyChanged(this, new PropertChangedEventArgs("P1");
}
}
}
public class VM1 : INotifyPropertyChanged
{
public VM2 VM2 { get; private set; }
public VM3 VM3 { get; private set; }
public VM1()
{
this.VM2 = new VM2();
this.VM3 = new VM3();
this.VM2.WhenP1Changes = () => VM3.SomeProperty = "lol";
}
}
Do I have a memory leak here?
PD: It would be great if you can also answer to:
- Is this even a good practice?
Thanks
Do I have a memory leak here?
The lambda assigned to VM2.WhenP1Changes captures this VM1 instance (needed to access the VM3 property), so as long as the view model VM2 is alive, it will keep VM1 alive. Whether this ends up being a leak depends on the lifecycle of those view models, but the implications are effectively the same as your first example using events.
In short, I would prefer to use a delegate to notify between view models. If you have a parent view model which has access to the various child view models (it seems that you do), then you can use one or more delegates like events to notify each other when updates are required.
Rather than typing out the whole scenario again, I'd prefer to point you towards my answer to the Passing parameters between viewmodels question, which provides a full description and code examples of this technique.
There is also a further addition that may interest you that can be found in my answer to the If necessary, how to call functions in MainViewViewModel from other ViewModels question. Please let me know if you have any questions.
enter code hereMaybe the title is not so specific.
The situation which I'm having is. I've got an ItemsControl where I insert many ViewModels, and this ItemsControl should have to show the View through DataTemplates.
So, I write these in a ResourceDictionary:
And then, I add this ResourceDictionary to the ApplicationResources.
This is so redundant and tiredsome.
I'm using MVVM also, so I was thinking if could be a way to use MEF to discover the corresponding the View that should draw. I was investigating that creating a custom attribute tag could be a good idea to simplify these redundant code, maybe adding this tag in the view telling it that this ViewModel should draw for this View, but I get lost with MEF.
The plan is to remove the ResourceDictionary.
Can you lend me a little hand?
Thanks in advance.
In my host WPF application, I added this Import:
[ImportMany("ApplicationResources", typeof(ResourceDictionary))]
public IEnumerable<ResourceDictionary> Views { get; set; }
code behind for the ResourceDictionary:
[Export("ApplicationResources", typeof(ResourceDictionary))]
public partial class ItemView : ResourceDictionary
{
public ItemView()
{
InitializeComponent();
}
}
For reference, the Xaml for the example ResourceDictionary looks like this:
<DataTemplate DataType="{x:Type local:ItemViewModel}">
...
</DataTemplate>
in WPF application, before the main window:
// Add the imported resource dictionaries
// to the application resources
foreach (ResourceDictionary r in Views)
{
this.Resources.MergedDictionaries.Add(r);
}
[System.ComponentModel.Composition.InheritedExport(typeof(ProblemView))]
public abstract class ProblemView : UserControl // or whatever your Views inherit
{
public abstract Type ViewModelType { get; }
}
[System.ComponentModel.Composition.InheritedExport(typeof(ProblemViewModel))]
public abstract class ProblemViewModel : BaseViewModel // or whatever your ViewModels inherit
{
}
// in your App class
{
[ImportMany(typeof(ProblemView))]
public ProblemView[] Views { get; set; }
[ImportMany(typeof(ProblemViewModel))]
public ProblemViewModel[] ViewModels { get; set; }
void MarryViewViewModels()
{// called during MEF composition
foreach (ProblemView view in Views)
{
foreach(ProblemViewModel vm in ViewModels)
{
if(Equals(view.ViewModelType, vm.GetType())
{// match -> inject the ViewModel
view.DataContext = vm;
break;
}
}
}
}
}
// example of usage
public partial class SomeView : ProblemView
{
public override Type ViewModelType { get { return typeof(SomeViewModel); } }
}
Let me explain you how to setup something like this. You can look for further information in official documentation
Best implementation would be using interface and duck typing.
public interface IModule {
DataTemplate Template { get; set; }
string Name{get;set;}
...
}
Then for each plugin, inherit this interface
[Export(typeof(IModule ))]
public class SampleModule : IModule {
private DataTemplate template;
public DataTemplate IModule.Template {
get { return this.teplate; }
set { this.template = value; }
}
private string name = "SamplePlugin";
public string IModule.Name{
get { return this.name ; }
set { this.name = value; }
}
...
}
Class SampleModule is in separate assembly while IModule is in common with both Application and every module assembly.
Now you need to load every module available to application. This code snippet is from window of application
...
[ImportMany]
public IEnumerable<IModule> ModulesAvailable {get;set;}
...
public void LoadModules(string path) {
DirectoryCatalog catalog = new DirectoryCatalog(path);
catalog.ComposeParts(this);
}
Now you can just use foreach loop and add them to Application Resource
foreach(IModule module in ModulesAvailable) {
Application.Current.Resources.Add(module.Template, module.Name);
}
This is just concept and code is not tested.
I ssed MEF in my high-school final project I did few months ago, so you could take a look at my code. It is spreadsheet application with formula support where all operations and operands are loaded as plugins so it is very flexible.