There's a text box and some view model with detail
public class Detail { public string Value {get;set;}}
public class SomeVM
{
public Detail {get;set;}
}
Trying to bind to textbox with this code:
textBox.DataBindings.Add("Text", new SomeVm (), "Detail.Value");
But it says "there's no property to bind". Is there a solution for this problem?
Have you tried:
public string Value
{
get
{
return _someVm.Value;
}
set
{
_someVm.Value=value;
}
}
private readonly SomeVM _someVM=new SomeVM();
...
textBox.DataBindings.Add("Text", _someVm, "Value");
Related
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 ?
In AttachmentViewModel I have the following code
public ICommand EditAttachmentCommand { get; private set; }
public AttachmentsViewModel()
{
EditAttachmentCommand = new ActionCommand<AvailAttachment>(EditAttachment);
}
private void EditAttachment(AvailAttachment attachment)
{
var attachmentDetailsViewModel = Router.ResolveViewModel<AttachmentDetailsViewModel>(true, ViewModelTags.ATTACHMENT_DETAILS_VIEWMODEL);
attachmentDetailsViewModel.NavigateToAttachment(attachment.ArticleId);
EventAggregator.Publish(ViewTags.ATTACHMENT_DETAILS_VIEW.AsViewNavigationArgs());
EventAggregator.Publish(new CurrentViewMessage(ContentView.Attachment));
}
In my MainViewModel I have the following code:
public ICommand SessionAttachmentCommand { get; private set; }
public MainMenuViewModel()
{
SessionAttachmentCommand = new ActionCommand<AvailAttachment>(EditAttachment);
}
private void EditAttachment(AvailAttachment attachment)
{
var attachmentDetailsViewModel = Router.ResolveViewModel<AttachmentDetailsViewModel>(true, ViewModelTags.ATTACHMENT_DETAILS_VIEWMODEL);
attachmentDetailsViewModel.NavigateToAttachment(attachment.ArticleId);
EventAggregator.Publish(ViewTags.ATTACHMENT_DETAILS_VIEW.AsViewNavigationArgs());
EventAggregator.Publish(new CurrentViewMessage(ContentView.Attachment));
}
I would like to pass the object state of AvailAttachment class from AttachmentsViewModel to MainMenuViewModel. presently null value comes for AvailAttachment's attachment object in MainMenuViewModel. I am debugging a code written by someone. This is a silverlight project using MVVM model. How do I do that?
Any help is appreciated. Thanks
i have a model user:
public class User
{
public string Name { get; set; }
public int Level { get; set; }
}
in the view:
<TextBox Text="{Binding NewUser.Name}"/>
<TextBox Text="{Binding NewUser.Level}"/>
and the property in the VM:
public User NewUser
{
get { return _newUser; }
set
{
if (_newUser == value)
return;
_newUser = value;
RaisePropertyChanged("NewUser");
}
}
this code does update the property:
NewUser = new User() { Name = "test", Level = 1 };
this code does not:
NewUser.Name = "test";
what am i doing wrong? i'm using mvvm light.
When setting NewUser.Name, the RaisePropertyChanged on the ViewModel is not called and therefore no PropertyChangedEvent is fired.
In general you should have a good reason to expose model classes directly in your ViewModel, as you do here (Expose a User model as a public property in your ViewModel). This basically violates the separation of concerns between Models and ViewModels, for which MVVM is designed. Though it seems academic, my experience is that it is really worth it to stay clean here, as in most real-world cases the ViewModels tend to become more complex over time and contain functionality that you don't want to have in your model (like INPC implementations, btw).
Although it involves a bit more coding, you should implement a nested ViewModel here. Here's a bit of code to get you started:
public class ParentViewModel : NotifyingObject
{
private UserViewModel _user;
// This is the property to bind to
public UserViewModel User
{
get { return _user; }
private set
{
_user = value;
RaisePropertyChanged(() => User);
}
}
public ParentViewModel()
{
// Wrap the new instance in a ViewModel
var newUser = new User {Name = "Test"};
User = new UserViewModel(newUser);
}
}
This is the extra ViewModel in which the User model class is wrapped:
public class UserViewModel : NotifyingObject
{
/// <summary>
/// The model is private here and not exposed to the view
/// </summary>
private readonly User _model;
public string Name
{
get { return _model.Name; }
set
{
_model.Name = value;
RaisePropertyChanged(() => Name);
}
}
public UserViewModel(User model)
{
_model = model;
}
}
This is your model class. There is no need to implement INotifyPropertyChanged.
public class User
{
public string Name { get; set; }
}
You did not implement INotifyPropertyChanged for your User class. So changing the property NewUser by assignment will trigger the UI, setting the property Name by assignment will not.
If you follow your pattern, this:
public string Name { get; set; }
should in the end look like this:
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
RaisePropertyChanged("Name");
}
}
I want to open a window from my ViewModel.
How can I create and show it using Galasoft Messenger?
public partial class View {
public View() {
InitializeComponents();
//Register Open message
}
//This is called when ViewModel sends a message
public void OpenView() {
new View().Show();
}
}
public class ViewModel {
public ViewModel() {
//Send message to open some view
}
}
This situation does not require an object to be passed from ViewModel to View; therefore, just registering of type object, passing null, BUT the token is key.
public partial class View {
public View() {
InitializeComponents();
//Register Open message BEFORE ViewModel calls Messenger.Default.Send
Messenger.Default.Register<object>(this, ViewModel.OpenViewToken, p => { OpenView(); });
}
//This is called when ViewModel sends a message
public void OpenView() {
new View().Show();
}
}
public class ViewModel {
public static readonly Guid OpenViewToken = Guid.NewGuid();
public ViewModel() {
Messenger.Default.Send<object>(null, OpenViewToken);
}
}
I have a WPF application built with MVVM and am trying to display a custom class in a combobox. I am still getting the Namespace.Asset despite overriding the ToString Method to something easier on the eyes. What am I doing wrong?
XAML code
<ComboBox ItemsSource="{Binding Drivers}" SelectedItem="{Binding SelectedDriver}" Grid.Row="20" Grid.Column="1" Grid.ColumnSpan="3"/>
ViewModel Code
public List<Driver> Drivers
{
get
{
return this.drivers;
}
set
{
this.drivers = value;
this.RaisePropertyChanged("Drivers");
}
}
public Driver SelectedDriver
{
get
{
return this.selectedDriver;
}
set
{
this.selectedDriver = value;
this.RaisePropertyChanged("SelectedDriver");
}
}
One of the custom classes code with overriden ToString
public class ExperimentalDriver : Driver
{
public override DriverResponse GetDriverResponse(double time)
{
... random unrelated code....
}
public override string ToString()
{
return "Experimental Driver";
}
}
You might need to set the ToString() on the base class
Something like:
public class Driver
{
protected string displayName;
public override string ToString()
{
return displayName;
}
}
Then your class constructors for your sub classes would simply set the displayName
public class ExperimentalDriver : Driver
{
public ExperimentalDriver()
{
displayName = "Experimental Driver";
}
}
Ok I figured it out. I have an abstract class "Driver" in that abstract class I added the following code to require all my derived classes to have a ToString() method
public new abstract string ToString();
When I remove it the problem goes away.