How to inform a ViewModel that its collectionSource was changed(add, update or delete) from another viewModel in WPF MVVM? - wpf

If i have a viewmodel for adding new item and another viewmodel for displaying all or filtered items (the second viewmodel's view must always reflect any changes in the collection source), should the change be communicated from viewmodel to viewmodel directly, or viewmodel to repository to second viewmodel?
I tried the following
public class DataAccess //my repository
{
public DataAccess()
{
var ctx = new MyDbContext();
}
public void AddNewItem(Item item)
{
ctx.items.Add(item);
ctx.SaveChanges();
}
public ObservableCollection<Item> GetAllItems()
{
return new ObservableCollection<Item>(ctx.items.ToList());
}
Here's my first ViewModel
public class ItemsViewModel : ObservableObject
{
public ItemsViewModel()
{
DataAccess dt = new DataAccess();
AllItems = new ObservableCollection<Item>(dt.GetAllItems());
}
//AllItems is bound to datagrid
private ObservableCollection<Item> _allItems;
public ObservableCollection<Item> AllItems
{
get {return _allItems;}
set {_allItems = value; RaisePropertyChanged();}
}
//command to load the form for adding new item then the method below
//I passed AllItems into the constructor
//I think I can also pass AllItems using Messenger, but I haven't tried it yet
void LoadNewItemForm()
{
NewItemView view = new NewItemView(){DataContext = new NewItemViewModel(AllItems)};
view.ShowDialog();
}
Here's the second viewmodel
public class AddNewItemViewModel : ObservableObject
{
public AddNewItemViewModel(ObservableCollection<Item> allItems)
{
DataAccess dt = new DataAccess();
_allItems = allItems;
}
private ObservableCollection<Item> _allItems;
public ObservableCollection<Item> AllItems
{
get {return _allItems;}
set {_allItems = value; RaisePropertyChanged();}
}
//Here's the AddNewItem method
public void SaveNewItem()
{
Item newitem = new Item(){ ..... };
dt.AddNewItem(item);
//Now is this change in AllItems here supposed to reflect in the allItems passed via constructor (will this reflect in the first ViewModel)
AllItems.Add(newitem);
}
I've tried creating the AllItems property in the repository (DataAccess class) so that this AllItems property will be the one to be returned in the call for GetAllItems() and also it is in the repository where I'll call AllItems.Add(something) or AllItems.Remove(something). Still, change in this does not reflect in the first viewModel.

Here's an example of using events to inform the view models when the data changes.
The data layer:
public class DataAccess
{
private readonly MyDbContext ctx = new MyDbContext();
public event EventHandler<DataChangedEventArgs> ItemsChanged;
public void AddNewItem(Item item)
{
ctx.Items.Add(item);
ctx.SaveChanges();
RaiseItemsChanged(new DataChangedEventArgs(DataAction.Added, item));
}
public List<Item> GetAllItems()
{
return ctx.Items.ToList();
}
public void RaiseItemsChanged(DataChangedEventArgs eventArgs)
{
ItemsChanged?.Invoke(this, eventArgs);
}
}
The event args:
public enum DataAction
{
Added,
Deleted
}
public class DataChangedEventArgs
{
public DataAction DataAction { get; set; }
public Item Item { get; set; }
public DataChangedEventArgs(DataAction dataAction, Item item)
{
DataAction = dataAction;
Item = item;
}
}
The first view model:
public class ItemsViewModel
{
private readonly DataAccess dataAccess = new DataAccess();
public ObservableCollection<Item> AllItems { get; set; }
public ItemsViewModel()
{
AllItems = new ObservableCollection<Item>(dataAccess.GetAllItems());
dataAccess.ItemsChanged += (sender, eventArgs) =>
{
if (eventArgs.DataAction == DataAction.Added)
AllItems.Add(eventArgs.Item);
else
AllItems.Remove(eventArgs.Item);
};
}
}
This helps separate concerns because the AddNewItemViewModel can now add an item without worrying about updating ItemsViewModel. You could also take a lazier approach and handle any event by clearing and repopulating the ObservableCollection<Item>.

Related

How to update a string property in the Model from an aggregated ViewModel

I'm working on a GUI application in WPF/MVVM. Let's say i have a Model class which is populated deserializing a (third-party) XML file
class Person
{
public string Name { get; set; }
public string Address { get; set; }
public string ReservationId { get; set; }
}
and a ViewModel which exposes to its View a lot of properties and commands to manipulate the Model, in this case strings (such as ReservationId):
class StringManipulatorViewModel
{
string modelString; //here's the problem
public StringManipulatorViewModel(string modelString)
{
this.modelString = modelString;
}
//Stuff to let the view manipulate the string
}
StringManipulatorViewModel is highly reusable and it is used by a lot of ViewModels, e.g.
class PersonViewModel
{
Person model;
public StringManipulatorViewModel ReservationManipulatorVM; //aggregated ViewModel
public StringManipulatorViewModel AddressManipulatorVM; //aggregated ViewModel
public PersonViewModel(Person model)
{
this.model = model;
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId); //here's the problem
AddressManipulatorVM = new StringManipulatorViewModel(model.Address); //here's the problem
}
}
Obviously passing the string as "model" to the ViewModel isn't effective, and C# doesn't seem to allow string references as fields.
What is the best/right way to let member ViewModels manipulate the Model when dealing with string types?
Thank you
Your problem is that you are trying to reference a property, not a string field.
But you can pass a delegate to the setter of the property.
If you also change the modelString field to a property, you can call this delegate automatically when the string is changed.
class StringManipulatorViewModel
{
private string modelString
{
get { return _modelString; }
set { _modelString = value; if (SetModelString != null) SetModelString(value); }
}
private string _modelString;
Action<string> SetModelString;
public StringManipulatorViewModel(string initialValue, Action<string> setModelString)
{
this.modelString = initialValue;
SetModelString = setModelString;
}
//Stuff to let the view manipulate the string
}
You initiate the StringManipulatorViewModel in PersonViewModel like this:
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId, value => model.ReservationId = value); //here's the problem
Here are some other ideas when you want to pass a property.
Passing properties by reference in C#
I can't think of a "right" way to manipulate the string as a reference inside the StringManipulatorViewModel, once you pass the string as a value it has nothing to do with the model.
But a way to legitimately change the model string value whenever the StringManipulatorViewModel manipulates it, is by raising an Event in the view model when it manipulates the string and then add an event handler to update the model with the new value:
class StringManipulatorViewModel
{
string modelString;
public event EventHandler<string> StringManipulated;
public StringManipulatorViewModel(string modelString)
{
this.modelString = modelString;
}
public ManipulateString()
{
// Manipulate the string
StringManipulated?.Invoke(this, modelString);
}
}
And in the PersonViewModel constructor:
class PersonViewModel
{
Person model;
public StringManipulatorViewModel ReservationManipulatorVM;
public StringManipulatorViewModel AddressManipulatorVM;
public PersonViewModel(Person model)
{
this.model = model;
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId);
AddressManipulatorVM = new StringManipulatorViewModel(model.Address);
ReservationManipulatorVM.StringManipulated += (sender, e) => model.ReservationId = e;
AddressManipulatorVM.StringManipulated += (sender, e) => model.Address = e;
}
}

How to use ReactiveList so UI is updated when items are added/removed/modified

I'm creating a WinForms application with a DataGridView. The DataSource is a ReactiveList. Adding new items to the list however does not update the UI.
ViewModel
public class HomeViewModel: ReactiveObject
{
public ReactiveCommand<object> AddCmd { get; private set; }
ReactiveList<Model> _models;
public ReactiveList<Model> Models
{
get { return _models; }
set { this.RaiseAndSetIfChanged(ref _models, value); }
}
public HomeViewModel()
{
Models = new ReactiveList<Model>() { new Model { Name = "John" } };
AddCmd = ReactiveCommand.Create();
AddCmd.ObserveOn(RxApp.MainThreadScheduler);
AddCmd.Subscribe( _ =>
{
Models.Add(new Model { Name = "Martha" });
});
}
}
public class Model
{
public string Name { get; set; }
}
View
public partial class HomeView : Form, IViewFor<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
VM = new HomeViewModel();
this.OneWayBind(VM, x => x.Models, x => x.gvData.DataSource);
this.BindCommand(VM, x => x.AddCmd, x => x.cmdAdd);
}
public HomeViewModel VM { get; set; }
object IViewFor.ViewModel
{
get { return VM; }
set { VM = (HomeViewModel)value; }
}
HomeViewModel IViewFor<HomeViewModel>.ViewModel
{
get { return VM; }
set { VM = value; }
}
}
The view always show "John".
Debugging Subscribe show added items.
Tried it with ObservableCollection same result.How to use ReactiveList so UI is updated when new items are added
Tried it with IReactiveDerivedList same result. Does ReactiveUI RaiseAndSetIfChanged fire for List<T> Add, Delete, Modify?
I think what you want is a ReactiveBindingList rather than a ReactiveList. This is a WinForms specific version of the ReactiveList for binding purposes.
You should use BindingList.
reference :
"If you are bound to a data source that does not implement the IBindingList interface, such as an ArrayList, the bound control's data will not be updated when the data source is updated. For example, if you have a combo box bound to an ArrayList and data is added to the ArrayList, these new items will not appear in the combo box. However, you can force the combo box to be updated by calling the SuspendBinding and ResumeBinding methods on the instance of the BindingContext class to which the control is bound."
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-bind-a-windows-forms-combobox-or-listbox-control-to-data?view=netframeworkdesktop-4.8
Or
ReactiveBindingList
It work fine for me. !!!

MVVM & business logic Layer

I have a problem with MVVM pattern and binding collection. My ViewModel provides a collection to the View but to get this collection I use this:
public BindingList<Car> BindingListCars { get; set; }
public CarsVm()
{
BindingListVoiture = carServices.ListCars;
}
When I bind my View on this List it's as if I bind directly my View on the Model because they use the same reference. So when I edit one property of a Car, the model is directly edited without using carServices validation method.
What is the best solution to correct this problem ?
Do I have to expose a copy of my Model to my View to not edit directly my Model from the View?
Do I have to use BindingList in my Model and subsribe to ListChanged in my carServices to validate each change?
You should either perform the validation directly in the Car class itself or expose wrapper objects instead of exposing the "real" Car objects to the view.
The following sample code should give you the idea about what I mean:
//the "pure" model class:
public class Car
{
public string Model { get; set; }
}
public class CarService
{
public List<CarWrapper> ListCar()
{
List<Car> cars = new List<Car>(); //get your Car objects...
return cars.Select(c => new CarWrapper(c, this)).ToList();
}
public bool Validate()
{
//
return true;
}
}
public class CarWrapper
{
private readonly Car _model;
CarService _service;
public CarWrapper(Car model, CarService service)
{
_model = model;
_service = service;
}
//create a wrapper property for each property of the Car model:
public string Model
{
get { return _model.Model; }
set
{
if(_service.Validate())
_model.Model = value;
}
}
}
Obviously if you expose an IEnumerable<Car> from your view model for the view to bind, you are effectively bypassing any validation that is dedined outside of the Car class if the view is able to set any properties of the Car class.
Thanks for your answer mm8,
With this solution I have to create one wrapper per class which need outside validation. It add work and during refactoring we have to edit the class and the Wrapper.
What do you think about this solution :
I put my list of vehicle in a binding list
My service subscribe to ListChanged event of this list
My service implement INotifyDataErrorInfo
For each modification in this list validation is executed
If there is an error ErrorsChanged event is raised
The view model subsribe to this event and retrieve error Data.
The view model subsribe to this event and retrieve error Data.
For example :
My services implementation :
public class VehicleServices : INotifyDataErrorInfo
{
private BindingList<Vehicle> _bindingListCar
public BindingList<Vehicle> BindingListCar
{
get return _bindingListCar;
}
private readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
//INotifyDataErrorInfo implementation
public IEnumerable GetErrors(string propertyName)
public bool HasErrors
private void RaiseErrorsChanged(string propertyName)
public VehicleServices()
{
_bindingListCar = GetVehicles();
_bindingListCar.ListChanged += BindingListVehicleChanged;
}
private void BindingListVehicleChanged(object sender, ListChangedEventArgs e)
{
//Only modification is managed
if (e.ListChangedType != ListChangedType.ItemChanged) return;
switch(e.PropertyDescriptor.Name)
//Validate each property
//if there is ErrorsChanged is raised
}
}
And my ViewModel
public class CarVm : BindableBase
{
private ICollection<string> _errors;
public ICollection<string> Error
{
get
{
return _errors;
}
set
{
SetProperty(ref _errors, value);
}
}
private VehicleServices _carServices;
public BindingList<Vehicle> BindingListCar { get; set; }
public CarVm(VehicleServices carServices)
{
_carServices = carServices;
BindingListCar = new BindingList<Vehicle>(_carServices.BindingListCar);
_carServices.ErrorsChanged += _carServices_ErrorsChanged;
}
private void _carServices_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
Error = _carServices.ValidationErrors[e.PropertyName];
}
}
Do you think this is a good practice ?

Update viewmodel based on MainWindow event

I have a UdpClient, firing off a DataRecevied event on my MainWindow:
public partial class MainWindow : Window
{
public static YakUdpClient ClientConnection = new YakUdpClient();
public ClientData;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ClientData = new ClientData();
ClientConnection.OnDataReceived += ClientConnectionOnDataReceived;
}
private void ClientConnectionOnDataReceived(object sender, MessageEventArgs messageEventArgs)
{
ClientData.Users = messageEvenArgs.ConnectedUsers;
}
}
My ClientData and User classes look as follow:
public class ClientData
{
public List<User> Users {get;set;)
}
public class User
{
public string Name {get;set;}
}
On my MainWindow, I have a UserControl called UserListView which has a ViewModel called UserListViewModel
The ViewModel looks as follow:
public class UserListViewModel: BindableBase
{
public UserListViewModel()
{
//I am sure there are better ways of doing this :(
Users = new ObservableCollection<User>((MainWindow)Application.Current.MainWindow).ClientData.Users
});
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get{ return _users;}
set { this.SetProperty(ref this._users, value); }
}
}
The difficulty I have here, is when the ClientConnectionOnDataReceived event on the MainWindow gets fired, I would like to update my ClientData class, My Viewmodel should then somehow be notified that the list changed, and subsequently update my UI.
Can anyone give me a solid example of how to achieve this using MVVM (Prism) in WPF?
I am new to MVVM, so i am still trying to figure this out.
First of all, there's no obvious reason why the main window should do the subscription.
I'd go for something like this:
create a service that encapsulates the subscription (and subscribes in its constructor)
register that as a singleton
have it implement INotifyPropertyChanged (to notify consumers of a change to Users)
inject the service into UserListViewModel and observe the Users property (see PropertyObserver)
when Users in the service changes, update Users in the user list view model
and best of all, no need for ObservableCollection here :-)
EDIT: example:
interface IUserService : INotifyPropertyChanged
{
IReadOnlyCollection<User> Users
{
get;
}
}
class YakUdpService : BindableBase, IUserService
{
private readonly YakUdpClient _yakUdpClient;
private IReadOnlyCollection<User> _users;
public YakUdpService()
{
_yakUdpClient = new YakUdpClient();
_yakUdpClient.OnDataReceived += ( s, e ) => Users = e.ConnectedUsers;
}
public IReadOnlyCollection<User> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
class UserListViewModel : BindableBase
{
private IReadOnlyCollection<UserViewModel> _users;
private readonly IUserService _userService;
private readonly PropertyObserver<IUserService> _userServiceObserver;
public UserListViewModel( IUserService userService )
{
_userService = userService;
_userServiceObserver = new PropertyObserver<IUserService>( userService );
_userServiceObserver.RegisterHandler( x => x.Users, () => Users = _userService.Users.Select( x => new UserViewModel( x ) ).ToList() );
// ^^^ should use factory in real code
}
public IReadOnlyCollection<UserViewModel> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
and then register the service
Container.RegisterType<IUserService, YakUdpService>( new ContainerControlledLifetimeManager() );
in your bootstrapper or your module's initialization.

Implementing INotifyCollectionChanged interface

I need to implement a collection with special capabilities. In addition, I want to bind this collection to a ListView, Therefore I ended up with the next code (I omitted some methods to make it shorter here in the forum):
public class myCollection<T> : INotifyCollectionChanged
{
private Collection<T> collection = new Collection<T>();
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void Add(T item)
{
collection.Insert(collection.Count, item);
OnCollectionChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
protected virtual void OnCollectionChange(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
CollectionChanged(this, e);
}
}
I wanted to test it with a simple data class:
public class Person
{
public string GivenName { get; set; }
public string SurName { get; set; }
}
So I created an instance of myCollection class as follows:
myCollection<Person> _PersonCollection = new myCollection<Person>();
public myCollection<Person> PersonCollection
{ get { return _PersonCollection; } }
The problem is that the ListView does not update when the collection updates although I implemented the INotifyCollectionChanged interface.
I know that my binding is fine (in XAML) because when I use the ObservableCollecion class instead of myCollecion class like this:
ObservableCollection<Person> _PersonCollection = new ObservableCollection<Person>();
public ObservableCollection<Person> PersonCollection
{ get { return _PersonCollection; } }
the ListView updates
What is the problem?
In order for your collection to be consumed, you should implement IEnumerable and IEnumerator too. Although, you're probably better off subclassing ObservableCollection<T>

Resources