How can I (cleanly) bind a composed property to a view (MVM)? - c#

For my question, let's assume I have a viewmodel, model and view (MVVM). The viewmodel contains a couple of properties that change (and change the view via INotifyPropertyChanged event where they are binded).
Now I want to do something like a poly-property (i.e. take together a couple of string properties and put it into one property). How can I do this properly?
For example, I have three properties: Prop1, Prop2, Prop3 and want to make one property out of these: PropNew = String.Format("{0}, {1} {2}", Prop1, Prop2, Prop3);.
A possibility is to create a new property in the viewmodel (and update this if another property changes) which is then bound to the view. But I fear this is not clean... Are there other possibilities?
THank you!

If I understand the question correctly, then you want to update the UI binding to a property on the view model that is derived from the current state of the class.
Notifying the UI in WPF is done by raising the PropertyChanged event on the interface INotifyPropertyChanged. In the code snippet below I am using the NotificationObject from the PRISM library:
public class MyViewModel: NotificationObject
{
private string _lastName;
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged(() => Greeting);
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
RaisePropertyChanged(() => Greeting);
}
}
public string Greeting
{
get { return string.Format("Hello {0} {1}!", _firstName, _lastName); }
}
}
Updating either of the FirstName or LastName properties will cause the UI to be notified that the Greeting property has changed, and that the data should be rebound.

Have you considered using MutiBinding and interface IMultiValueConverter?

Related

Binding to an object

I have a simple Person model:
public class Person
{
public string FirstName { get; set; }
public DateTime LastUpdated { get; set; }
}
Lets say that I have a View that has a TextBox, which is binded to LastUpdated field:
<TextBox Grid.Column="1" Margin="5" Text="{Binding Person.FirstName}"/>
Now I need to implement PropertyChanged somehow. Let's use Prism Snippet.
What I need is to perform SetProperty on a Person class:
private Person person;
public Person Person
{
get { return person; }
set { SetProperty(ref person, value); }
}
and NOT on the field LastUpdated in Person class:
private DateTime? lastUpdated;
public DateTime? LastUpdated
{
get { return lastUpdated; }
set { SetProperty(ref lastUpdated, value); }
}
This is not the matter of dependencies in the model. I got the model through DataContract ( WCF service ) so I cannot changed it. So is there a way to observe a class for changes and bind class field to some UI control.
So is there a way to observe a class for changes and bind class field to some UI control.
No. You need to raise the PropertyChanged event for the object of the property that you are actually binding to.
If you get the Person object from some third-party service for which you cannot modify the code to raise the PropertyChanged event in the setter of the FirstName property, you should not bind to these objects.
Instead you should create your own view model class and bind to this one. The view model can simply wrap the WCF class, e.g.:
public class PersonViewModel
{
private readonly Person _person;
public PersonViewModel(Person person)
{
_person = person;
}
public string FirstName
{
get { return _person.FirstName; }
set { _person.FirstName = value; RaisePropertyChanged(); }
}
}
If you're using Prism, then you likely are using the MVVM pattern. If so, then the one approach is using the view model for binding. Instead of exposing Person as a property, expose the individual properties you want to bind against - FirstName and LastUpdated:
Create a property on the view model that forwards calls to your model.
Bind your view to the view model property.
You can freely implement your change notifications in the view model.

Raising a property change event for class object

I have a class as follows:
public class UserData : INotifyPropertyChnaged
{
public string strUserName;
public string UserName
{
get { return strUserName; }
set { SetProperty(ref strUserName, value); }
}
private string strPhoneNumber;
public string UserPhoneNumber
{
get { return strPhoneNumber; }
set { SetProperty(ref strPhoneNumber, value); }
}
private List<UserMailID> listUserMailID;
public List<UserMailID> ListOfUserMailID
{
get { return listUserMailID; }
set { SetProperty(ref listUserMailID, value); }
}
}
I'm accessing this class at some location and this class object I am binding to my view:
private UserData cActiverUser;
public UserData ActiverUser
{
get { return cActiverUser; }
set { SetProperty(ref cActiverUser , value); }
}
Suppose there is UserName field in my view; my data binding goes like this:
TextBox.Text="{Binding ActiverUser.StrUserName, UpdateSourceTrigger=PropertyChanged}
Binding works well, but I am unable to raise the property changed event when I change this user name field on my view. I have tried setting Mode="TwoWay" also.
In your XAML binding, try setting NotifyOnTargetUpdated=True, or if that doesn't work NotifyOnSourceUpdated=True.
I had the same issue with a project I did a while ago, and it was one of those that got it working.
Also on this line:
public string strUserName;
Should this be private? and you should be specifying the binding as UserName, not strUserName?
I believe the bindings are also case sensitive.
If you're using Prism, derive UserData and whatever class has UserData as its property from BindableBase, which implements INotifyPropertyChanged, but also lets you use SetProperty(...). I'm not even sure how you were able to get it to work before, but I suspect you're not showing us your original code, since you misspelled INotifyPropertyChanged -- INotifyPropertyChnaged.
Additionally, are you actually instantiating the object? Don't forget to do that.
Lastly, you're binding to ActiverUser.StrUserName, but UserData does not have a property StrUserName. It has UserName, which is what you want to bind to.

Databinding Entity Framework navigation properties - handling change

So I'm building my first larger application and I'm using WPF for Windows and stuff and Entity Framework for retrieving, updating and storing data.So far using a pattern similar to the MVVM pattern, I had a couple of issues but was able to resolve them and am quite far into design.
Also, I'm using database first approach.
But I have just ran into a brick wall that I should have anticipated. It has to do with nested properties in entities and the way changes to them are handled. Let's explain.
For the purpose of simplicity I will not be using my actual class names.
So let's say I have three entities in my EF Model: Department, Manager and PersonalInfo.
I modified my *.tt Template file so that all my entities also implement INotifyPropertyChanged interface, but only for their NON NAVIGATION properties since Navigation properties are declared as virtual and WILL be overridden by EF when their date gets set.
So let's say my generated classes look like this:
public partial class Department : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChange(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public Department() { }
int _id;
public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }
int _someproperty;
public int SomeProperty { get { return _someproperty; } set { _someproperty= value; OnPropChange("SomeProperty"); } }
int _managerid;
public int ManagerID { get { return _managerid; } set { _managerid = value; OnPropChange("ManagerID"); } }
public virtual Manager Manager { get; set; }
}
public partial class Manager : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChange(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public Manager() { }
int _id;
public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }
public virtual PersonalInfo PersonalInfo { get; set; }
}
public partial class PersonalInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChange(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public PersonalInfo() { }
int _id;
public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }
string _firstname;
public string FirstName { get { return _firstname; } set { _firstname = value; OnPropChange("FirstName"); } }
string _lastname;
public string LastName { get { return _lastname; } set { _lastname = value; OnPropChange("LastName"); } }
}
Now this works pretty well if I want to let's say display a list of Departments with their Managers. First I load the data into the EF Context like so
Context.Departments.Include(d => d.Manager.PersonalInfo).Load();
Departments = Context.Deparments.Local;
And than in the XAML I can do:
<DataGrid ItemsSource="{Binding Departments}" SelectedItem="{Binding CurrentDepartment, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}"/>SomeProperty
<DataGridTextColumn Binding="{Binding SomeProperty }" Header="Property"/>
<DataGridTextColumn Binding="{Binding Manager.PersonalInfo.FirstName}" Header="FirstName"/>
<DataGridTextColumn Binding="{Binding Manager.PersonalInfo.LastName}" Header="LastNameName"/>
</DataGrid.Columns>
</DataGrid>
And all of this works wonderfully. I can add and remove items with no problems by simply removing them from Context and saving changes. Since entity sets are ObservableCollections any additions or removal from them automatically raises appropriate events which update the datagrid. I can also modify any nonnavigation property of the Department and can refresh the data in CurrentDepartment like so:
Context.Entry(CurrentDepartment).Refresh();
and it automatically refreshes the data in the datagrid.
Problems start when I change one of the navigation properties. Let's say that I opened a window in which I edited the Department, where I changed the current manager from Bob Bobington to Dave Daveston. When I return to this window calling:
Context.Entry(CurrentDepartment).Refresh();
It will only refresh non navigation properties, and First and Lastname columns will still say Bob Bobington. But that is Refresh function working as intended. But if I load the correct data into the context like this:
Context.Entry(CurrentDepartment).Reference(d=>d.Manager);
Context.Entry(CurrentDepartment.Manager).Reference(m=>m.PersonalInfo);
is still won't change the contents of the first and last name columns since they are still bound to the OLD manager. They will only refresh if the change happens on Bob Bobington instance of PersonalInfo.
I can sort of solve this level of problem by binding the column directly to Manager property, and converting Manager to text either via a ValueConverter or by overriding ToString for Manager. But that won't help since WPF won't ever be notified that Manager property has changed since changes to that property don't raise PropertyChanged event.
Navigation properties can not raise that event since even if I edited the tt template so it generates the code for the navigation property like so:
Manager _manager;
public virtual Manager Manager { get{return _manager;}
set{
_manager=value;
OnPropChange("Manager");
}
}
all this code will likely be overridden by the EF framework itself.
Sooo, what is the best thing to do in these cases? Please don't tell me that conventional wisdom is to copy the data from EF Poco classes into your own and use them. :(
UPDATE:
Here goes a potentially stupid solution for this problem. But it works.
ObservableCollection<Department> tempd = Departments;
Department temp = CurrentDepartment;
Departments = null;
CurrentDepartment = null;
Context.Entry(temp).Refresh();
Context.Entry(temp).Reference(d=>d.Manager).Load();
Context.Entry(temp.Manager).Reference(m=>m.PersonalInfo).Load();
Departments = tempd;
CurrentDepartment = temp;
As you can clearly see the key is in forcing the DataGrid to rebind itself from scratch. This way it will use no shortcuts and will rebind itself properly. BUT this method is quite silly. I shiver at the thought of having to do this to datagrids with hundreds of rows.
So I'm still waiting for a proper solution, but I'll be continuing onwards using this. Something is better than nothing.
Well, conventional wisdom is to copy the data across to another POCO, or at least make your ViewModel class peek through to an underlying Model class. You have combined your Model and ViewModel classes such that Model-based constraints (virtual methods required by your ORM) are interfering with your ViewModel-based constraints (to allow databinding, you must be able to raise events from property setters).
If your Model and ViewModel were properly separated (Separation of Concerns) then you could have your virtual methods and database-required fields on your Model (a DB persistable object) and your purely View-based functions (PropertyChanged events) on your ViewModel. Your DB code should never care about your PropertyChanged events anyway.
You can make it easier by making the ViewModel a look-through class so every property getter-setter looks like:
public string PropertyThing
{
get { return _myModel.PropertyThing; }
set { _myModel.PropertyThing = value; PropChanged("PropertyThing"); }
}
If you're already doing code generation this shouldn't be a major chore.
Alternatively, you could duplicate all the values with something like AutoMapper to separate out your Model and ViewModel to separate classes.
It's not what you wanted to hear, but your ORM and your UI are clashing and that's the sort of thing that MVVM architecture (specifically separating the Model and ViewModel) are supposed to make better.

MVVM - How to keep formatted properties up-to-date?

I am working on a Windows Phone app that uses MVVM, but am struggling with the implementation of MVVM for properties that need to be formatted from the model class to show in the view.
Let's say that I have a simple model class called Person.
public class Person {
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
There is a list of Person objects that are loaded from a locally saved file, and I want to show a list of persons on a list page and then let the user tap on a person to navigate to a details page where there are more details about this person.
On the list page, I want to show the person's birthday as "Birthday: 2/22/1980" (where "2/22/1980" is the person's formatted Birthday)
On the details page, I want to show the person's birthday in a different format: "Eric's birthday is 2/22/1980" (where "Eric" is the person's Name and "2/22/1980" is the person's formatted Birthday).
Normally, I would just create a view model that formats the Birthday properly:
public class PersonViewModel {
private Person person;
public PersonViewModel(Person person) {
this.person = person;
}
public string BirthdayForList {
get {
return "Birthday: " + person.Birthday.ToString("ddd", CultureInfo.CurrentCulture);
}
}
public string BirthdayForDetails {
get {
return person.Name + "'s birthday is " + person.Birthday.ToString("ddd", CultureInfo.CurrentCulture);
}
}
}
In order to show these values in the UI, I would create a collection of these view model objects (and bind them to the view):
ObservableCollection<PersonViewModel> Items
Now, what do I do when a person's birthday is updated (somewhere on the details page) and make sure that Items has been updated with up-to-date BirthdayForList and BirthdayForDetails properties, while at the same time saving the Person locally?
I want to keep everything simple and not have to update both the saved list of Person objects and list of PersonViewModel objects manually each time a value needs to be updated.
What is the best way to do this? Should I be using an ObservableCollection of PersonViewModel objects? Also, I have read in several places on this website that the model class should not implement NotifyPropertyChanged.
(Note: I have simplified the problem for this question. You should assume that there are many other ways that I need to format the Birthday property throughout the application as well as other properties from the model class that need to be formatted differently on different pages.)
Why don't simply do the whole thing in xaml and don't use the "calculated properties"?
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}'s birthday is {1:ddd}">
<Binding Path="Person.Name">
<Binding Path="Person.BirthDay">
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Then all you need to do is implement INotifyPropertyChanged in the Person class and raise the event in the setter.
EDIT: I would also recommend using a framework like MVVM light so you can use the ViewModel and ObservableObject base classes for your objects and simply be able to use their implementation of INotifyPropertyChanged
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged(() => FirstName);
}
}
private string _firstName;
Converters and XAML formatting are good solutions when they work, but sometimes you reall just need to do it in the ViewModel. Typically, you'd need to implement INotifyPropertyChanged and raise the PropertyChanged event for the calculated property when any of its dependencies change.
Managing these dependencies is a royal pain in the ... In fact I got so fed up with this very problem that I an MVVM framework called Catwalk that allows you to do these types of calculated properties in your ViewModel. If you use the framework, you can have code like
public string BirthdayForDetails
{
get
{
return Calculated(() => this.Name + "'s birthday is " + this.Birthday.ToString("ddd", CultureInfo.CurrentCulture));
}
}
Where the base class for model will automatically raise a PropertyChanged event for BirthdayForDetails if either Name or Birthday change. You just have to inherit from ObservableModel and Birthday & Name have to be observable properties like
public string Name
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
If you decide to try it out, let me know what you think.
You have two options:
Just format the date in XAML. Here is an example.
For more complex conversions, you can use converters.
What you should NOT do is store the format in the view model. The format of the data is a view/presentation concern only. So, the benefit of the above approach is that you don't need to keep separate lists just because of formatting.
Putting all your PersonViewModels in an ObservableCollection only solves the issue that your UI needs to update when a new PersonViewModel is added / removed from the collection.
That however does not solve the problem, that one object inside your collection changes. So if the Birthdate of the first person in the list changes, the collection stays the same.
So what you need to achive is to notify the UI that one object inside this collection changed.
You can do so either by letting your ViewModel implement INotifyPropertyChanged or deriving it from DependencyObject (discussion on what's the better solution: INotifyPropertyChanged vs. DependencyProperty in ViewModel).
I'd recommend using INotifyPropertyChanged. Implementing that interface will give you an PropertyChanged event. You need to raise that event everytime one of your properties change. Unfortunately this also requires you to create additional properties in the ViewModel so that you get notified when the changes happen.
The simplest (definitly not the best) way would be to just call OnPropertyChanged for every property that is dependent.
public class PersonViewModel : INotifyPropertyChanged
{
private Person person;
public PersonViewModel(Person person)
{
this.person = person;
}
public DateTime Birthday
{
get { return person.Birthday; }
set
{
person.Birthday = value;
OnPropertyChanged("Birthday");
OnPropertyChanged("BirthdayForList");
OnPropertyChanged("BirthdayForDetails");
}
}
public string Name
{
get { return person.Name; }
set
{
person.Name = value;
OnPropertyChanged("Name");
OnPropertyChanged("BirthdayForDetails");
}
}
public string BirthdayForList
{
get
{
return "Birthday: " + Birthday.ToString("ddd", CultureInfo.CurrentCulture);
}
}
public string BirthdayForDetails
{
get
{
return Name + "'s birthday is " + Birthday.ToString("ddd", CultureInfo.CurrentCulture);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I already mentioned that this solution is not the best. You are very likely to add another dependent property and you would have to remember adding the OnPropertyChanged to the Property you depend on.
So you could either use converters for your ViewModel (that now notifies on property change) and remove your calculated properties, or you stick to your properties and find an easier way to mark dependent properties.
Thankfully somebody allready solved the issue of dependent / calculated properties.
When googeling "INotifyPropertyChanged dependent properties" a lot of results show up. One that I really like is this one (Handling INotifyPropertyChanged for dependant properties) because it uses clean and readable attributes to mark the dependencies.
Also there are several MVVM Frameworks out there, that include solutions for said problem.
I hope one of the suggested solutions does help you fix your problem.
You can simply call the PropertyChanged method in the setter of your "person" property
like this
private Person myPerson;
public Person MyPerson
{
get { return myPerson; }
set
{
myPerson = value;
PropertyChanged("MyPerson");
PropertyChanged("BirthdayForList");
PropertyChanged("BirthdayForDetails");
}
}
You can use a combination of multiple elements embedded inside a TextBlock element
<TextBlock Foreground="DarkGray" VerticalAlignment="Bottom" Margin="0,0,0,8"><Run Text="total length "/><Run Text="{Binding TotalHours}" FontSize="48"/><Run Text="h "/><Run Text=":" FontSize="48"/><Run Text="{Binding TotalMinutes}" FontSize="48"/><Run Text="m "/></TextBlock>
Somewhat like this sample https://stackoverflow.com/a/8130843/3214300

WPF MVVM design ViewModel

I have the following model:
public class Person
{
public string LastName{get;set;}
public City City {get;set;}
}
public class City
{
public string Name {get;set;}
}
I have two Views:
One for display all Persons with LastName and the Name of the city in
a DataGrid(AllPersonsViewModel)
One for adding a new Person (PersonViewModel)
My AllPersonsViewModel:
public class AllPersonViewModel : ViewModel
{
public ObservableCollection<PersonViewModel> PersonViewModels {get;set;}
}
I started with the following PersonViewModel:
public class PersonViewModel : ViewModel
{
private Person _person;
public string Name
{
get { return _person.Name;}
set { _person.Name = value; RaisePropertyChange("Name");}
}
public string CityName
{
get { return _person.City.Name;}
}
}
Then I added the properties for adding a new Person. In the View I need a Textbox for the PersonName and a Combobox for selection of a City:
public class PersonViewModel : ViewModel
{
private Person _person;
public string Name
{
get { return _person.Name;}
set { _person.Name = value; RaisePropertyChange("Name");}
}
public string CityName
{
get { return _person.City.Name;}
}
public City SelectedCity
{
get { return _person.City;}
set { _person.City = value; RaisePropertyChange("SelectedCity");}
}
public ObservableCollection<City> Cities {get;set;}
}
Is this the right approach? It seems a little bit redundant to me. In the Grid of AllPersonsView I could also bind directly to the "SelectedCity.Name" instead of the extra property CityName. The grid is also readonly.
you have multiple problems;
1 - you do not need to declare an observable collection of viewmodels in AllPersonViewModel. Just declare an ObservableCollection of Person.
2 - do not add the CityName property; not needed as you have stated.
3- do not add the Name property. Bind the textbox to Name property of the Person.
Your question really boils down "is it OK to expose my model directly to the view?" Some purist will say no while other will say that having a view model that wraps a model without adding any new functionality is redundant.
In my opinion it depends on the task at hand but "skiping" a view model may come back and bite you later when you need to add additional state that doesn't belong in the model. If in doubt use a view model but for instance when exposing simple model objects in a list you often don't need the extra layer the view model provides.
In your case you have opted for the "purist" solution and because your model object doesn't support INotifyPropertyChanged you can't get rid of the view model if a model property is changed by multiple sources. But instead of providing a CityName property you could bind to SelectedCity.Name. WPF supports property navigation in data binding expressions.
For more insight into this topic you can google mvvm expose model.

Categories

Resources