Basically this is a very very similar question to this one, with the big difference that I cannot easily "just use an observable collection inside the model"; a good example is the keycollection of a dictionary.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class Data
{
private Dictionary<String, String> _randomData;
public Data()
{
_randomData = new Dictionary<String, String>();
}
public ICollection<string> RandomDataKeys {
get {
return _randomData.Keys;
}
}
public void AddElement(string k, string v) {
_randomData[k] = v;
}
}
public class DataViewModel
{
private Data _data;
public DataViewModel(Data data)
{
_data = data;
RandomData = new ObservableCollection<String>(_data.RandomDataKeys);
//obviously above wouldn't work, since it just copies the keys.
}
public ObservableCollection<String> RandomData {get; set;}
}
Now what is the common approach here? A backup is to change the "add" function to tell the viewmodel that a new item is added (but that would require the model to have information about the viewmodel, explicitly calling a function on the viewmodel to tell the viewmodel to keep a duplicate of the model's data, this feels wasteful and slow).
The problem is, that when you're doing this RandomData = new ObservableCollection<String>(_data.RandomDataKeys); , you basically create a new instance of collection. So when you update your _data.RandomDataKeys, this changed are not reflected in your observable collection.
Read this, please
The best way would be to use ObservableCollection in your Data class, but if you don't want to do so, you can change your
public ObservableCollection<String> RandomData {get; set;}
To
public ObservableCollection<String> RandomData
{
get { return new ObservableCollection(_data.RandomDataKeys); }
}
But that is definately not a good idea, to create each time new collection.
So you can try to implement INotifyPropertyChanged interface in your Data class and bind directly to your _data.RandomDataKeys
Related
I am in a situation where I have to use a existing model which I am not allowed to change the existing Properties.
public class Car : ModelBase
{
private string _model;
public string Model
{
get { return _model; }
set { this.Update(x => x.Model, () => _model= value, _model, value); }
}
private IEnumerable<Person> _allowedDrivers;
public IEnumerable<Person> AllowedDrivers
{
get { return _allowedDrivers; }
set { this.Update(x => x.AllowedDrivers, () => _allowedDrivers=value, _allowedDrivers, value); }
}
}
Now we recently started implementing a WPF UI and I need to use these existing models. Is there any way I can use the IEnumerable and let it work like a ObservableCollection without changing it really? What are my options.
The thing is I remove a AllowedDriver and then add a AllowedDriver and the UI is not updating at all. This is logical, I then made(for testing purposes) the IEnumerable an ObservableCollection and then the UI works. Do I have any other options in keep using the IEnumerable but gets updated?
Is there any way I can use the IEnumerable and let it work like a
ObservableCollection without changing it really?
You can create another class which inherits your Car class. Since your ObservableCar inherits Car class you have access to AllowedDrivers property.
So, you can declare your desired observable collection and initialize with an observable collection converted from AllowedDrivers. This initialization should be inside get.
public class ObservableCar: Car {
public ObservableCar(){
_observableAllowedDriver = new ObservableCollection<Person>(AllowedDrivers);
}
private ObservableCollection<Person> _observableAllowedDriver;
public ObservableCollection<Person> ObservableAllowedDriver
{
get { return _observableAllowedDriver; }
}
}
You could simply assign an ObservableCollection to the AllowedDrivers property and operate on that collection:
var drivers = new ObservableCollection<Person>();
car.AllowedDrivers = drivers;
Now adding a new Person to the drivers collection will actually update the UI, because a bound UI element does a runtime check whether a collection implements INotifyCollectionChanged:
drivers.Add(new Person(...));
The above of course assumes that your ModelBase.Update method isn't doing anything strange, and eventually assigns the value argument of the property setter to its backing field _allowedDrivers so that the property getter returns the collection instance that was passed to the setter.
EDIT: If possible at all, it would make sense to change the Car class to use ICollection<T> instead of IEnumerable<T>:
public class Car : ModelBase
{
...
private ICollection<Person> _allowedDrivers;
public ICollection<Person> AllowedDrivers
{
get { return _allowedDrivers; }
set { this.Update(x => x.AllowedDrivers, () => _allowedDrivers=value, _allowedDrivers, value); }
}
}
You could then still assign an ObservableCollection, but get rid of the drivers variable:
car.AllowedDrivers = new ObservableCollection<Person>();
car.AllowedDrivers.Add(new Person(...));
Thanks for everyones input. I solved it via making/casting the IEnumerable to an ObservableCollection.
Like so:
public class Car : ModelBase
{
public Car()
{
AllowedDrivers = new ObservableCollection<Person>();
}
private string _model;
public string Model
{
get { return _model; }
set { this.Update(x => x.Model, () => _model= value, _model, value); }
}
private IEnumerable<Person> _allowedDrivers;
public IEnumerable<Person> AllowedDrivers
{
get { return _allowedDrivers; }
set { this.Update(x => x.AllowedDrivers, () => _allowedDrivers=value, _allowedDrivers, value); }
}
}
And then using it in my viewmodel or so I use it like this:
var allowedDrivers = (ObservableCollection<Person>)Car.AllowedDrivers;
allowedDrivers.Add(person)
So I have seen some responses to similar questions as this, but I was wondering if a certain paradigm that I am thinking of is even possible in C#. First, I'll lay out the issue:
I have a MVVM application that I am developing in C#. The model has properties that change, and when a single property changes in the model, it often times affects multiple properties in the view-model. So the view-model listens for changes on the model. And the view listens for changes on the view-model.
In my view-model, I end up getting some code that looks like this:
private void OnModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
string prop_name = e.PropertyName;
if (prop_name.Equals("some_property_on_the_model"))
{
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
}
else if (...)
{
... etc ...
}
}
This gets annoying because it just seems messy. And if I forget to edit this function after adding a new property to the view-model then it can easily lead to bugs.
So here is what I would like to do, but I don't know if this is possible. So I would like one of you to help me understand if it is possible or not.
It would be really cool if I could use C#'s "attributes" feature to take care of the property changed propagation.
So maybe something like this:
[ListenToModelProperty("some_property_on_the_model")]
[OnPropertyChanged("MyButtonVisibility")]
public Visibility MyButtonVisibility
{
get
{
if (model.some_property_on_the_model == true)
{
return Visibility.Visible;
}
else
{
return Visibility.Hidden;
}
}
}
private void OnModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
prop_name = e.PropertyName;
foreach (var property in view_model)
{
var attributes = property.GetCustomAttributes(typeof(ListenToModelPropertyAttribute));
var descriptions = attributes.Select(x => x.Description);
if (descriptions.Contains(prop_name))
{
notification_to_make = property.GetCustomAttributes(typeof(OnPropertyChangedAttribute));
string notification_string = notification_to_make[0].Description;
NotifyPropertyChanged(notification_string);
}
}
}
Please note that the above code is not meant to be real code. It will definitely not compile and will not work. But I would like to see if something like the above is possible in C#. Is it possible to do something like this using attributes? Or is there a library out there that makes something like this possible?
I have figured out how to do it! It is fairly simple. I will post the relevant code here, and those who are interested can find all the code at this github repository that I just made: https://github.com/davepruitt/model-subscribe
First, I created a custom attribute class. It is a simple class that takes an array of strings as a parameter to its constructor. This allows you to listen to multiple properties on the model for changes. It looks like this:
using System;
using System.Collections.Generic;
namespace TestPropagationOfPropertyChanges
{
[AttributeUsage(AttributeTargets.All)]
public class ListenForModelPropertyChangedAttribute : System.Attribute
{
public List<string> ModelPropertyNames = new List<string>();
public ListenForModelPropertyChangedAttribute (string [] propertyNames)
{
ModelPropertyNames.AddRange (propertyNames);
}
}
}
I then created my model. For simplicity's sake, it only contains two properties. They are strings that store a "first name" and a "last name":
using System;
using System.ComponentModel;
namespace TestPropagationOfPropertyChanges
{
public class Model : NotifyPropertyChangedObject
{
#region Constructors
public Model ()
{
}
#endregion
#region Private data members
private string _first = string.Empty;
private string _last = string.Empty;
#endregion
#region Public properties
public string FirstName
{
get
{
return _first;
}
set
{
_first = value;
NotifyPropertyChanged ("FirstName");
}
}
public string LastName
{
get
{
return _last;
}
set
{
_last = value;
NotifyPropertyChanged ("LastName");
}
}
#endregion
}
}
The view-model, in this case, has a "full name" property. So it wants to listen to any changes that happen to the first or last name on the model, and then react to changes on either of those. I realize this isn't the best "real world" scenario in which this kind of system would be used, but it does help illustrate the concept. The first part of my view-model is below:
using System;
namespace TestPropagationOfPropertyChanges
{
public class ViewModel : NotifyPropertyChangedObject
{
#region Private data members
//This is public for testing purposes
public Model _model = new Model();
#endregion
#region Constructors
public ViewModel ()
{
_model.PropertyChanged += ReactToModelPropertyChanged;
}
#endregion
#region Properties
[ListenForModelPropertyChangedAttribute(new string [] {"FirstName", "LastName"})]
public string FullName
{
get
{
return _model.FirstName + _model.LastName;
}
}
Finally, the view-model finishes with the method that reacts to changes on the model. Normally, in a large and complex application, this method could contain a large if-else statement with lots of calls to NotifyPropertyChanged. Instead, we now just iterate through the properties of the view-model and see which ones subscribe to the model's property that was changed. See below:
void ReactToModelPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//Get the name of the property that was changed on the model
string model_property_changed = e.PropertyName;
//Get a System.Type object representing the current view-model object
System.Type t = typeof(ViewModel);
//Retrieve all property info for the view-model
var property_info = t.GetProperties();
//Iterate through each property
foreach (var property in property_info)
{
//Get the custom attributes defined for this property
var attributes = property.GetCustomAttributes (false);
foreach (var attribute in attributes)
{
//If the property is listening for changes on the model
var a = attribute as ListenForModelPropertyChangedAttribute;
if (a != null)
{
//If the property that was changed on the model matches the name
//that this view-model property is listening for...
if (a.ModelPropertyNames.Contains(model_property_changed))
{
//Notify the UI that the view-model property has been changed
NotifyPropertyChanged (property.Name);
}
}
}
}
}
Overall, it works excellently, and is exactly what I needed. This code can easily be expanded upon to be even more functional for those interested.
I have three ViewModels: MainViewModel, PreferencesViewModel and ColourControllerViewModel - the latter 2 are properties of the MainViewModel.
ColourControllerViewModel is used for the 'ColourSelector' view, where various colours can be created and deleted. It contains an ObservableCollection of ColourViewModel, which has a property detailing the colour, and a bool property determining if it should be shown on the preferences tab (DisplayOnPreferences).
PreferencesViewModel is used for the 'Preferences' view, which contains a combo box of colours - this is represent by an ObservableCollection of ColourViewModel, and only those ColourViewModels where DisplayOnPreferences == true should be displayed.
My question is, what's the easiest way to do this? Currently, I am using an Action delegate called UpdateList() which passes the updated list from ColourControllerViewModel to MainViewModel, which in turn updates the PreferencesViewModel. I don't really like this though, it feels like there's a better way.
Should there be a single ObservableCollection of ColourViewModel on MainViewModel that is updated/accessed by either instance?
Here are the classes:
public class MainViewModel : ViewModel
{
private ColourMappingControllerViewModel _colourMappingControllerViewModel;
private PreferencesControllerViewModel _preferencesTabViewModel;
public MainViewModel()
{
// Initialise the database Handler
dbHandler = DatabaseHandler.DbHandlerInstance;
_colourMappingControllerViewModel = new ColourMappingControllerViewModel(dbHandler.GetColourMappingsList(), UpdateColourList);
_preferencesTabViewModel = new PreferencesControllerViewModel(dbHandler.GetPreferences, ColourMappingList)
}
public ObservableCollection<ColourMappingViewModel> ColourMappingList
{
get { return ColourMappingControllerViewModel.ColourMappingList; }
}
public void UpdateColourList(ObservableCollection<ColourMappingViewModel> colourList)
{
PreferencesTabViewModel.UpdateColourList(colourList);
}
}
public class ColourMappingControllerViewModel : ViewModel
{
public ColourMappingControllerViewModel(IEnumerable<ColourMapping> colourMappingsList, Action<ObservableCollection<ColourMappingViewModel>> updateColourListAction)
{
InitialiseCommands();
ColourMappingList = new ObservableCollection<IColourMappingViewModel>(InitialiseColourMappingsList(colourMappingsList));
}
public ICommand AddColourMappingCommand { get; set; }
private void InitialiseCommands()
{
AddColourMappingCommand = new DelegatingCommand(AddColourMapping);
}
private void AddColourMapping() // Attached to Command on View
{
var newColourMapping = new ColourMappingViewModel(
new ColourMapping());
ColourMappingList.Add(newColourMapping);
ColourMappingsCollectionView.MoveCurrentToLast();
UpdateColourMappingList();
}
private void UpdateColourMappingList()
{
UpdateColourListAction.Invoke(ColourMappingList);
}
}
public PreferencesControllerViewModel : ViewModel
{
public PreferencesControllerViewModel(object preferenceInfo, ObservableCollection<ColourMappingViewModel> colourMappingsList)
{
var pciTrendBlocks = pciBlocks;
ColourMappingsList = colourMappingsList;
}
public void UpdateColourList(ObservableCollection<ColourMappingViewModel> colourList)
{
ColourMappingsList = colourList;
}
}
I know the ObservableCollection class is being misused - it's probably not necessary on the Preferences as it will only be updated in ColourMappingController.
I would agree that you need a single ObservableCollection that is shared between views. This effectively becomes your "Model" in MVVM.
You may also want to enforce different access semantics by having a ReadOnlyObservableCollection that can be passed to your preferences VM etc. This ensures that only ColourControllerViewModel (Which gets the underlying ObservableCollection) can actually alter the collection.
In my apps I tend to have a separate data layer, but yes, for now it would be simplest to just add them to MainViewModel.
The alternative would be to have ColourControllerViewModel be the thing that owns the collection (and exposes it as a ReadOnlyObservableCollection), and have you MainViewModel just pass the collection into any other VM's that need it.
A property in ViewModel, that is bound to an element in View, usually takes value from
some model class instance and when the value is updated in the View, the property propagates the change of the value into the underlying model class instance.
However in case when you have a property in ViewModel returning e.g. an ObservableCollection that is constructed from values
from the model, the change of the values in the View only changes the values in the ObservableCollection. It does not propagate the change of values into the underlying model class instance.
So for example, you have a modal dialog (= the View), you display this dialog together with its datacontext (=ViewModel) and the ViewModel contains an instance of a model class.
Now
situation A) you have a ViewModel that has only properties that return non-collection types (int, string ...), when you close the dialog
you know that the model class instance has the updated values.
situation B) you have a ViewModel that has a property exposing an ObservableCollection<>, when you close the dialog, the model class
instance does not contain the updated values.
What is the common pattern/practice of dealing with this?
I thought you could have a method that writes the data that are present in ViewModel into the Model instance and call this method when the dialog closes. But this seems unnatural to me with the philosophy of MVVM and WPF so far.
Please keep in mind that the before mentioned example is just an example. This applies to any View-ViewModel-Model interaction in general.
In case someone didn't understand what I was trying to describe:
Let's have a model class
public enum A
{
A1,
A2,
A3
}
public class MyModel
{
private float[][] array;
public MyModel()
{
array = new float[Enum.GetNames(typeof(A)).Length][];
foreach (A a in EnumUtil.GetValues<A>())
{
array[(int) a] = new float[3]; // any number, really, but it will be referenced later in text
}
}
public A EnumerationA { get; set; }
public float this[A a, int b]
{
get
{
return array[(int) a][b];
}
set
{
array[(int)a][b] = value;
}
}
public float[] ArraySlice
{
get
{
return array[(int) EnumerationA];
}
}
}
and a ViewModel class
public class MyViewModel : ViewModelBase
{
private MyModel _myModel;
private ObservableCollection<float>[] _tmp = new ObservableCollection<float>[Enum.GetNames(typeof(A)).Length];
public MyViewModel()
{
// in constructor we will add 3 values to each collection
}
public A EnumerationA
{
get
{
return _myModel.EnumerationA;
}
set
{
if (Enum.Equals(_myModel.EnumerationA, value) == false)
{
_myModel.EnumerationA = value;
RaisePropertyChanged("EnumerationA");
RaisePropertyChanged("ArraySlice");
}
}
}
public ObservableCollection<float> ArraySlice
{
get
{
return _tmp[(int) EnumerationA];
}
}
}
The ArraySlice can be bound to anything DataGrid etc....
So in this example, when user is done, the values stored in the array of ObservableCollections are not saved in the model instance.
However if instead of the property returning ObservableCollection, we had three properties, each returning float, the values would easily be propagated into the underlying model class instance right in the property setter.
Preferably I would welcome an idea / solution that wouldn't require me to do anything once the interaction between View / ViewModel / Model ends.
I have a class like
internal class CalculationsDataRelations
{
public List<CalculationsDataRelation> Relations;
}
And trying to bind it to a datagridview using following code
relations = new CalculationsDataRelations();
bs = new BindingSource(relations, "Relations");
DgvRelations.DataSource = bs;
But I get exception "DataMember property 'Relations' cannot be found on the DataSource."
How to bind datagridview properly?
Binding has to happen with Properties, but your internal class is only providing a Field. Also, you haven't instantiated the List<CalculationsDataRelation> variable with "new".
Try changing it to something like this:
internal class CalculationsDataRelations {
private List<CalculationsDataRelation> relations = new List<CalculationsDataRelation>();
public List<CalculationsDataRelation> Relations {
get { return relations; }
}
}