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 ?
Related
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;
}
}
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.
WPF-MVVM beginner here.
My problem: in a WPF-MVVM UI I am editing an entity. Some properties when changed, require automatic updates on other properties. These are done in Entity class, set methods, but not reflected in my View
More details:
1) I have the Model (a simple class with properties) in a separate assembly (not WPF related since is the general business model). Note that "SomeOption" when set to false, requires some other options to automatically be changed.
Example:
public class Employee : BaseEntity
{
public string EmployeeNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
....
private bool someOption
public bool SomeOption {
get
{ return someOption}
set {
someOption= value;
if (!value)
{
OtherOption = false;
OtherProperty= "";
AndAnotherOption= false;
}
}
}
}
2) The WPF UI has a base ViewModel implementing INotifyPropertyChanged. The current edited record (Employee) is a public property of the ViewModel:
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (_selectedEmployee != value)
{
_selectedEmployee = value;
OnPropertyChanged(nameof(SelectedEmployee));
}
}
}
3) When un-checking the checkbox bound to "SomeOption", the other properties which are changed in entity code, are not reflected on the View, and stay on the screen as edited by user.
Please let me know what I am missing. Thanks!
You should implement INotifyPropertyChanged in your model to update entities at your UI. For example:
public class Employee : BaseEntity, INotifyPropertyChanged
{
private string employeeNumber;
public string EmployeeNumber {
get{return employeeNumber};
set
{
employeeNumber=value;
OnPropertyChanged("EmployeeNumber");
}
//...Other properties...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Employee needs to implement INotifyPropertyChanged just as your viewmodel does, and fire PropertyChanged on changes to its own properties (the ones you're calling OtherOption, OtherProperty, etc.)
What you've got now will update the UI when the view model selects a different Employee, but subsequent changes to that Employee don't send any notifications.
I'm working on adding a Windsor IoC container to an existing WinForms application that uses an MVP UI design pattern. I'm trying to determine a good approach to resgistering a datacontext that depends on a connection string supplied at runtime. The problem is that I cannot create a datacontext until the user selects a database, i.e. a 'connection string' after the application has loaded. Granted only one datacontext is generally used, but sometimes a user need to switch to a different database, i.e. creating a differnet datacontext. This leads to additional runtime dependencies as well.
public interface IProductsView
{
event EventHandler<ProductSelectedEventArgs> ProductSelectedEvent;
event EventHandler<StringEventArgs> ProductStatusEvent;
void ClearProductList();
void DisplayProductList(IList<Product> products);
Control Control { get; }
IProductsPresenter Presenter { get; set; }
}
public class ProductsPresenter : IProductsPresenter
{
public IProductsView View { get; set; }
private IProductRepository Repository { get; set; }
public ProductsPresenter(IProductsView view, IProductRepository repository)
{
View = view;
View.Presenter = this;
Repository = repository;
}
public void ProductSelected(IList<Product> products)
{
throw new NotImplementedException();
}
public void ShowProductList(string name)
{
IList<Product> productList;
if (string.IsNullOrEmpty(name))
productList = Repository.GetProducts();
else
productList = Repository.GetProductsByName(name);
View.DisplayProductList(productList);
}
}
public class ProductDao : IDisposable, IProductRepository
{
private MeasurementDataContext dataContext;
public ProductDao(MeasurementDataContext context)
{
dataContext = context;
}
public List<Product> GetProducts()
{
return dataContext.Products.Select(p => Mapper.Map(p)).ToList().OrderBy(x => x.Name).ToList();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
if (dataContext != null)
{
dataContext.Dispose();
dataContext = null;
}
}
~ProductDao()
{
this.Dispose(false);
}
}
So this means that the Presenter in my View is null until the IProductRepository is created, which in turn depends on creating a MeasurementDataContext. I have these component regisitered in a IWindsorInstaller like so:
container.Register(Component.For<IProductsView>()
.ImplementedBy<ViewProductsControl>());
container.Register(Component.For<IProductsPresenter>()
.ImplementedBy<ProductsPresenter>());
Do I need to use Named and DependsOn which supply a unique name and connectionString argument for each datacontext?
What I currently do to register the data context at runtime after the user has selected a database
kernel.Register(Component.For<MeasurementDataContext>()
.UsingFactoryMethod(() => new MeasurementDataContext(conn)));
and then `Resolve' my Views and set their Presenters. I know this is not good design, but it's a brute force way of resolving my dependcies.
Thanks
UPDATE:
I changed the way I registered my datacontext's in the installer to the following:
container.Register(Component.For<DataContext>().ImplementedBy<MeasurementDataContext>().Named("Localhost").DependsOn(new { connectionString = conn }));
and then modified my model's constructor to:
public ProductDao(DataContext context)
{
dataContext = context as MeasurementDataContext;
}
All components will resolve with the right key:
kernel.Resolve<DataContext>(cbo.SelectedItem.ToString());
What about injecting a wrapper class to hold the connection string and have the datacontext objects use that? Something along these lines:
public class ConnectionStringProvider : IConnectionStringProvider
{
private string _value;
public event EventHandler ConnectionStringChanged;
public string ConnectionString
{
get { return _value; }
set
{
_value = value;
var del = ValueChanged;
if (del != null)
del(this, EventArgs.Empty);
}
}
}
Register this with and singleton lifestyle. This way your application can set or update the connection string on a single object and everyone who depends on it will be notified of the change.
I am trying to make a call to a wcf service with my silverlight application and I am having some trouble understanding how the model returns the result back to the view model. Within my view model I have the following command:
public DelegateCommand GetSearchResultCommand
{
get
{
if (this._getSearchResultCommand == null)
this._getSearchResultCommand = new DelegateCommand(GetSearchResultCommandExecute, CanGetSearchResultsCommandExecute);
return this._getSearchResultCommand;
}
}
private void GetSearchResultCommandExecute(object parameter)
{
this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
}
/// <summary>
/// Bindable property for SearchResults
/// </summary>
public ObservableCollection<QueryResponse> SearchResults
{
get
{
return this._SearchResults;
}
private set
{
if (this._SearchResults == value)
return;
// Set the new value and notify
this._SearchResults = value;
this.NotifyPropertyChanged("SearchResults");
}
}
then within my model I have the following code
public ObservableCollection<QueryResponse> GetSearchResults(string searchQuery)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += new EventHandler<QueryCompletedEventArgs>(sc_QueryCompleted);
sc.QueryAsync(new Query { QueryText = searchQuery });
return LastSearchResults;
}
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
this.LastSearchResults = results;
}
When I insert breakpoints within the model I see where the query is being executed and a result is returned within the model (this.LastSearchResults = results) however I cannot seem to get this collection to update/ notify the view model of the result. I've generated and run a similar test using just a method and dummy class and it seems to work so I suspect the issue is due to the async call /threading. I have INotifyPropertyChanged within the ViewModel to sync the View and ViewModel. Do I need to also implement INotifyPropChng within the model as well? I'm new to mvvm so any help / example of how I should approach this would be appreciated.
Thank you,
UPDATE
In further testing I added INotifyPropertyChanged to the model and changed the Completed event as follows:
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
//this.LastSearchResults = results;
SearchResults = results;
}
Placing a watch on Search Results I now see it is updated with results from teh WCF. My question is still around is this teh correct approach? It seems to work right now however I am curious if I am missing something else or if I should not be placing INotify within the Model.
Thank you,
I've found that it's best to encapsulate my WCF services in an additional layer of Service classes. This allows me to more easily Unit Test my ViewModels. There are several patterns when doing this, though this is the simplest I've used. The pattern is to create a method that matches the definition of the service call, though also contains an Action that can be invoked after the service call completes.
public class Service : IService
{
public void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += (s,e) =>
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
reply(results);
};
sc.QueryAsync(new Query { QueryText = searchQuery });
}
}
You can also provide an interface that your ViewModel can use. This makes Unit Testing even easier, though is optional.
public interface IService
{
void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply);
}
Your ViewModel would then look something like this:
public class MyViewModel : INotifyPropertyChanged
{
private IService _service;
public MyViewModel()
: this(new Service())
{ }
public MyViewModel(IService service)
{
_service = service;
SearchResults = new ObservableCollection<QueryResponse>();
}
private ObservableCollection<QueryResponse> _searchResults
public ObservableCollection<QueryResponse> SearchResults
{
get { return _searchResults; }
set
{
_searchResults = value;
NotifyPropertyChanged("SearchResults");
}
}
public void Search()
{
_service.GetSearchResults("abcd", results =>
{
SearchResults.AddRange(results);
});
}
protected void NotifyPropertyChanged(string property)
{
var handler = this.PropertyChanged;
if(handler != null)
handler(new PropertyChangedEventArgs(property));
}
}
An additional reason for encapsulating your service calls into another class like this is that it can provide a single place for such things as logging and error handling. That way your ViewModel itself doesn't need to take care of those things specifically related to the Service.
I would likely use something along the lines of:
public class ViewModel : INotifyPropertyChanged
{
private readonly IModel model;
private readonly DelegateCommand getSearchResultsCommand;
public DelegateCommand GetSearchResultsCommand
{
get { return getSearchResultsCommand; }
}
public ObservableCollection<QueryResponse> SearchResults
{
get { return model.SearchResults; }
}
public ViewModel(IModel model)
{
this.model = model;
this.model.SearchResultsRetrieved += new EventHandler(model_SearchResultsRetrieved);
this.getSearchResultsCommand = new DelegateCommand(model.GetSearchResultCommandExecute, model.CanGetSearchResultsCommandExecute);
}
private void model_SearchResultsRetrieved(object sender, EventArgs e)
{
this.NotifyPropertyChanged("SearchResults");
}
}
public interface IModel
{
event EventHandler SearchResultsRetrieved;
void GetSearchResultCommandExecute(object parameter);
bool CanGetSearchResultsCommandExecute(object parameter);
ObservableCollection<QueryResponse> SearchResults { get; }
}
With the SearchResultsRetrieved event being fired by the Model when its SearchResults collection has been filled with the appropriate data. I prefer to have custom events rather than implement INotifyPropertyChanged on my models, particularly if there are only one, or a few, events that need to be communicated to the viewmodel.