MVVM Binding, Object Property Notification changed - c#

Relativly new to the MVVM stuff, i have Trouble with the following:
I have an object "User", this object exposes some properties, like Username, Email, etc..
In the mvvm model i have a property:
private IUser currentUser;
public IUser CurrentUser
{
get
{
return this.currentUser;
}
set
{
this.currentUser = value;
this.OnPropertyChanged("CurrentUser");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In XAML a TextBox is bound as follows:
Text="{Binding CurrentUser.Email, Mode=TwoWay}"
When changing the Email Address the OnPropertyChanged is not fired and thus other code (as the ICommands) are not "working".
Is there a way that when the user changes the Text in the TextBox the OnPropertyChanged fires??
TIA,
Paul

You are firing PropertyChanged when CurrentUser changes, but current user is not changing you are just changing the Email property on it. A quick fix could be to have the Email property propagate the OnChange event for CurrentUser.
public string Email
{
//get
set
{
this.email = value;
this.OnPropertyChanged("CurrentUser");
}
}
Actually I think this.OnPropertyChanged("Email") would work too, but the setter of CurrentUser is definitely not getting called when you change a property of it.

Where is your INotifyPropertyChanged Interface?
I think it is necessary.

Property Change Notification does not watch the properties of your IUser class.
It is only watching for changes to the ViewModel Property CurrentUser (the reference).
You need
a) make the IUser implement INotifyPropertyChanged
or
b) pull out EmailAddress and add it to the ViewModel like so:
public string Email
{
get
{
return this.CurrentUser.Email;
}
set
{
this.CurrentUser.Email = value;
this.OnPropertyChanged("Email");
}
}

Related

Refresh UI in WPF

I have a question, I am building small login system, basically its ready and working, but still having some problems with UI, si if I take such button click action
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
Nullable<bool> creditencialFile = _controls.CredencialsFileDialog.ShowDialog();
if (creditencialFile == true)
{
ContextStatic.Filename = _controls.CredencialsFileDialog.FileName;
FileInfo creditencialsFileInfo = new FileInfo(ContextStatic.Filename);
ContextStatic.RootFolder = creditencialsFileInfo.DirectoryName;
model.LeapCreditencials = CredentialHelper.LoadCredentials(ContextStatic.Filename);
}
}
It loads credentials from file, and they are saved in object attribute:
model.LeapCreditencials = CredentialHelper.LoadCredentials(ContextStatic.Filename);
Now i want to refresh or reload UI so I all information windows would be set up to with new info. Question is should I need to reload per one control, or there is a smart way to reload Ui with new object values?
Yes, you should implement INotifyPropertyChanged in your model
msdn description to implement INotify Interface
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
When value of model is changed it will reflect in the UI.
Xaml
<TextBox Text="{Binding Mymodel.CustomerName,
UpdateSourceTrigger=PropertyChanged}" />
Model
public class DemoCustomer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
Yeah, there's a smart way. It's called MVVM (a.k.a. Model View View Model). It is not so hard to understand. You just bind your view to values in ViewModel, and when a value is changed UI is automatically updated.

How to correctly update the Model after the ViewModel has changed?

Suppose we have a Model (class Model) with the following property.
public string InputFileName
{
get { return m_InputFileName; }
set
{
m_InputFileName = value;
RaiseNotifyPropertyChanged("InputFileName");
}
}
The above model implements the INotifyPropertyChanged interface, so we have also the following method and the following event. The RaiseNotifyPropertyChanged method below is used to update the ViewModel.
#region INotifyPropertyChanged Implementation
private void RaiseNotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
The following are the main sections of the class that implements the ViewModel.
public class ViewModel : INotifyPropertyChanged
{
#region Members
private Model m_Model;
private string m_InputFileStr;
private readonly ICommand m_SubmitCommand;
#endregion
#region Constructors
public ViewModel()
{
m_Model = new Model();
m_Model.PropertyChanged += new PropertyChangedEventHandler(this.Model_PropertyChanged);
m_InputFileStr = string.Empty;
// ...
// initialize m_SubmitCommand
}
#endregion
// ...
#region Properties
public string InputFileStr
{
get { return m_InputFileStr; }
set
{
if (value == m_InputFileStr) return;
m_InputFileStr = value;
OnPropertyChanged("InputFileStr");
m_SubmitCommand.RaiseCanExecuteChanged();
}
}
#endregion
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
// This method is called when the model changes, so the Model notified the ViewModel.
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "InputFileName")
{
InputFileStr = m_Model.InputFileName;
}
else if (args.PropertyName == "OutputFileName")
{
OutputFileStr = m_Model.OutputFileName;
}
else if (args.PropertyName == "ReportText")
{
ReportTextStr = m_Model.ReportText;
}
}
}
The following are the main sections of the class that implements the View:
MainWindow.xaml
<TextBox Name="inputfileTextBox"
Text="{Binding Path=InputFileStr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Name="submitButton"
Content="Submit"
Command="{Binding SubmitCommand}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
The above implementation works correctly:
the View and the ViewModel correctly update each other;
the Model correctly updates the ViewModel.
With the aim of enabling the ViewModel to update the Model, I thought I would add the following call inside the set property InputFileStr of ViewModel:
m_Model.InputFileName = value;
However, this solution of updating the Model causes an obvious unintended effect:
The user modified the View.
The ViewModel is automatically modified.
The ViewModel updates the Model (m_Model.InputFileName = value;).
The Model is updated...
... so it notifies the ViewModel about the changes
Is the above behavior a correct behavior? I expect that if the ViewModel updates the Model, then the Model does not have to re-notify the ViewModel about the same change... As an alternative solution I thought I'd add an Update method to the Model: this method should update the Model without using the Model Properties.
public void Update(string inputFileName) // this method does not notifies the ViewModel
{
m_InputFileName = inputFileName;
}
Is this alternative solution a correct solution or are there better solutions?
Depending on what your model is, you will usually just invoke a "Save" method or similar. Most models (say, a database) don't need/want to have every change given to them in real-time.
So in general, the flow would be:
User invokes "save" operation
View model receives this as a command
View model invokes "save" operation on the model with the new data
If your DTO objects are shared between the model and view model, you don't even need to worry about synchronization. Otherwise, this is a good time to sync them.
On a similar note, using PropertyChanged in a model class is usually a bad idea. For starters, its no fun at all to listen to. Instead, if the model receives new data, raise a more semantically clear event to the VM with the new data.
tldr; Basically, don't worry so much about keeping your model and view model in sync. Very often, the model won't be keeping a copy of the current state at all! Even when it is, just update it when the view model is ready to "commit" changes, and notify the View Model of external changes to the model via normal events.

How to call one viewmodel's method from another viewmodel

I am developing Windows Universal app. I have one GridView which has one textblock and a button. The gridview gets data of un-purchased objects from a service. The button is for purchasing particular object. So if user clicks on button that object is purchased & gridview gets refreshed to remove purchased item from it.
I am illustrating my requirement in simplified manner. I tried two ways, both are not working. Can you please suggest me solution regarding it.
First way I used is to inherit Model class with ViewModel class so I can access methods of ViewModel class, but it throws StackOverflowException in ViewModelBase at SetProperty<T> method.
P.S. - I don't want to migrate to any framework like MVVMLight, etc.
ViewModel.cs
public class ViewModel : ViewModelBase
{
public ViewModel()
{
DataCollection = new ObservableCollection<Model>();
for (int i = 1; i < 10; i++)
{
DataCollection.Add(new Model { Number = i });
}
}
private ObservableCollection<Model> _DataCollection;
public ObservableCollection<Model> DataCollection
{
get { return _DataCollection; }
set { this.SetProperty(ref this._DataCollection, value); }
}
}
Model.cs
public class Model : ViewModel
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
var obj = DataCollection.Where(varNum => varNum.Number == x).FirstOrDefault();
if (obj != null)
{
DataCollection.Remove(obj);
}
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
2nd way I keep that isolated, so I was not able to access the methods.
ViewModel.cs is same as above
Model.cs
public class Model : ViewModelBase
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
// How to access ViewModel's DataCollection property or
// a method which sets un-purchased objects in DataCollection property
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Well, in the first example you're getting a StackOverflowException because your ViewModel instantiates 9 Models each time - and since your Model is an extension of ViewModel, each one of those instantiates 9 more Models and an infinite recursion happens. That doesn't answer your main question, though :)
Your class names are confusing to me, because in MVVM a "Model" is simply a representation of the data and methods to manipulate it, whereas the ViewModel requests this data from the Model and presents it via publicly accessible properties that are retrieved from the View via binding. The View knows about the ViewModel, the ViewModel knows about the Model and the Model just knows about the data. In any case you shouldn't be binding directly from the View to the Model!
You'll want to house the RelayCommand in your ViewModel so your View can bind to it, and depending on what you want to happen when a user purchases an item (store it in a database, track this in another variable, simply remove from the view without doing anything else, etc.) you may or may not need to write additional logic for when this occurs. Generally you'll want the ViewModel to handle user input and update both the presentation object as well as notify the Model a change was made, if this is something your app requires. Think of it as the Model holds the actual data whereas the ViewModel only holds what the user sees.
Unfortunately, without knowing what you're trying to do in a little more detail it's hard to give more specific advice than this!

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.

Binding to a ViewModel's Child Class Properties

I'm struggling to be able to bind a StatusBarItem content element in my view to a subclasses property in my ViewModel, I'm using the MVVM-Light framework/
ViewModel:
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
For full clarity the LoggedOnUser Class is defined as follows
public class LoggedOnUser : INotifyPropertyChanged
{
private string _Initials;
public event PropertyChangedEventHandler PropertyChanged;
public LoggedOnUser()
{
}
[DataMember]
public string Initials
{
get { return _Initials; }
set
{
_Initials = value;
OnPropertyChanged("Initials");
}
}
protected void OnPropertyChanged(string propValue)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propValue));
}
}
}
My Views DataContext is being set and is working as I am able to see other bindings working, but my attempts to bind to UserInfo.Initials property in my XAML are producing an empty result.
XAML:
<StatusBarItem Grid.Column="0" Content="{Binding UserInfo.Initials}" Margin="5,0,0,0" VerticalAlignment="Center" Focusable="False" />
The UserInfo property is set after the viewModel is created due to several factors but I thought with my propertychanged events this would be ok.
Any Advice on this would be greatly appreciated.
You do not appear to have a getter on UserInfo, the binding will be out of luck.
(Also check for binding errors when having trouble with bindings, they probably will tell you about all their problems)
add the getter to your userinfo
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
get {return _UserInfo;}
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
and like H.B. said - check your output window for binding errors
I am not quite sure why you have the initials_ attribute bound inside the UserInfo_ attribute.
You can't access the initials_ attribute without a getter of the UserInfo_ attribute.
I would suggest to bind to the latter separately.

Categories

Resources