I've a WPF form using a ModelView. This ModelView has a List of items. The problem is that when I change an item to on the list, the form continues having the old item.
Does anybody knows how can I make notice the form that he has to update it's data with the ModelView?
Use ObservableCollection and implement INotifyPropertyChanged in the ViewModel
Example
If you have a List of Persons, make the List an ObservableCollection<Person>
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<Person> m_persons;
public ObservableCollection<Person> Persons
{
get
{
return m_persons;
}
set
{
m_persons = value;
OnPropertyChanged("Persons");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Implement INotifyPropertyChanged in Person
public class Person : INotifyPropertyChanged
{
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Related
Code Behind File:
namespace WindowsTrainingTasks
{
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
public class SampleViewModel :INotifyPropertyChanged
{
private string _name="Johnson";
public string Name
{
get
{
return _name;
}
set
{
_name = value;
onPropertyChanged("Name");
}
}
private string _mobile="9876543210";
public string Mobile
{
get
{
return _mobile;
}
set
{
_mobile = value;
onPropertyChanged("Mobile");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void onPropertyChanged(string property)
{
PropertyChangedEventHandler _PropertyChanged = PropertyChanged;
_PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public SampleViewModel()
{
}
}
}
I'm not sure why you've chosen to declare your own INotifyPropertyChanged interface - it's already in the framework.
Additionally you need to do a null check before invoking the delegates on the event:
public void onPropertyChanged(string property)
{
var _PropertyChanged = PropertyChanged;
if (_PropertyChanged != null)
_PropertyChanged(this, new PropertyChangedEventArgs(property));
}
otherwise if nothing has been registered for the event then you will get a NullReferenceException.
When using INotifyPropertyChanged it is possible to do something like this to get the name of the property where the method invoking the event was called.
public void RaisePropertyChanged([CallerMemberName] string prop = "")
{
if (PropertyChanged != null)
{
PropertyChanged(new object(), new PropertyChangedEventArgs(prop));
}
}
Is there some other type of attribute to use to also get a reference to the class that contains that property? I want to be able to call RaisePropertyChanged() from any property from any of my viewmodel classes. All my viewmodel classes derive from a base so I'm thinking I can do something like this.
public void RaisePropertyChanged([CallerMemberName] string prop = "", [CallerClassRef] VmBase base = null)
{
if (PropertyChanged != null)
{
PropertyChanged(base, new PropertyChangedEventArgs(prop));
}
}
The keyword to access the current class reference is called this:
public void RaisePropertyChanged([CallerMemberName] string prop = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
This will work no matter how many times you derive this class, this is always the instance this function was called on.
Try using Fody - PropertyChanged add in. It helps you to inject INotifyPropertyChanged implementation to IL code.
Source code :
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
}
When compiled
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get { return givenNames; }
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged("GivenNames");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Source code copied from : https://github.com/Fody/PropertyChanged
I have a MainPage.xaml where is ListBox and Button. When I click on the button then MainPage is navigated to AddPage.xaml. This page is for adding new items, there are two TextBoxes and submit Button. When I click on that submit Button,then data from TextBoxes are saved to XML file and then is called GoBack().
I need to refresh ListBox in my MainPage.xaml when Im going back from AddPage.xaml, but it doesnt work automaticly. How can I do that?
My MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Context> Contexts { get; private set; }
public MainViewModel()
{
this.Contexts = new ObservableCollection<Context>();
}
public bool IsDataLoaded
{
get;
private set;
}
public void LoadData()
{
try
{
var file = IsolatedStorageFile.GetUserStoreForApplication();
XElement xElem;
using (IsolatedStorageFileStream read = file.OpenFile("contexts.xml", FileMode.Open))
{
xElem = XElement.Load(read);
}
var contexts = from context in xElem.Elements("Context")
orderby (string)context.Element("Name")
select context;
foreach (XElement xElemItem in contexts)
{
Contexts.Add(new Context
{
Name = xElemItem.Element("Name").Value.ToString(),
Note = xElemItem.Element("Note").Value.ToString(),
Created = xElemItem.Element("Created").Value.ToString()
});
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
this.IsDataLoaded = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and Context.cs
public class Context : INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private string _note;
public string Note
{
get
{
return _note;
}
set
{
if (value != _note)
{
_note = value;
NotifyPropertyChanged("Note");
}
}
}
private string _created;
public string Created
{
get
{
return _created;
}
set
{
if (value != _created)
{
_created = value;
NotifyPropertyChanged("Created");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You'll need to tell the main page that there is new data to reload.
At it's simplest, something like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back)
{
(this.DataContext as MainViewModel).LoadData();
}
}
Tip: You aren't raising a property changed notification for your View Model's properties.
On the load event of the MainPage, call LoadData. You should also clear the observable collection when you call the LoadData method before adding anything to it because simply loading the data will cause duplicate entries in your collection.
Is there a way to trigger some kind of OnPropertyChanged event for a computed property that uses a property of a "child entity" in a collection?
A small example:
I have a simple WPF application with a DataGrid showing Customer properties.
I am using Entity Framework 5, CodeFirst approach, so I wrote my classes manually with my own INotifyPropertyChanged implementation.
public partial class Customer : INotifyPropertyChanged
{
private string _firstName;
public virtual string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("FullName");
}
}
private string _lastName;
public virtual string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("FullName");
}
}
public virtual ICollection<Car> Cars { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
...
}
Now in that same class I created 3 computed properties:
public string FullName
{
get { return (FirstName + " " + LastName).Trim(); }
}
public int TotalCars
{
get
{
return Cars.Count();
}
}
public int TotalOpenInvoices
{
get
{
if (Invoices != null)
return (from i in Invoices
where i.PayedInFull == false
select i).Count();
else return 0;
}
}
The FullName is automatically updated in the DataGrid because I'm calling OnPropertyChanged("FullName");
I found an example of the INotifyCollectionChanged implementation that I can probably use to auto update the TotalCars when something is added to or removed from the ICollection:
http://www.dotnetfunda.com/articles/article886-change-notification-for-objects-and-collections.aspx
But what is the best approach to trigger the OnPropertyChange("TotalOpenInvoices") when a property (PayedInFull) inside the ICollection (Invoices) changes?
Doing something like OnPropertyChanged("Customer.TotalOpenInvoices"); in the Invoice class doesn't seem to do the trick... :)
Do you have control of what goes inside your collections? If you do, you can create a Parent property on your Invoice object and when it is added to the collection, set the parent to your Customer. Then when PaidInFull gets set, run your Customer.OnPropertyChanged("TotalOpenInvoices") or call a method on the Customer object to recalculate your invoices.
Enforcing parent-child relationship in C# and .Net
This assumes that Invoice and Customer both implement IPropertyChanged. Simply change your collection to an ObservableCollection, and watch the CollectionChanged property.
When new Invoices are added, hook up an event handler to the PropertyChanged event of that Invoice. When an item is removed from the collection, remove that event handler.
Then, just call your NotifyPropertyChanged function on your TotalOpenInvoices property.
For example (not completely tested, but it should be close):
Invoice
public class Invoice : INotifyPropertyChanged
{
private bool payedInFull = false;
public bool PayedInFull
{
get { return payedInFull; }
set
{
payedInFull = value;
NotifyPropertyChanged("PayedInFull");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Customer
public class Customer : INotifyPropertyChanged
{
public Customer()
{
this.Invoices.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Invoices_CollectionChanged);
}
ObservableCollection<Invoice> Invoices
{
get;
set;
}
public int TotalOpenInvoices
{
get
{
if (Invoices != null)
return (from i in Invoices
where i.PayedInFull == false
select i).Count();
else return 0;
}
}
void Invoices_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (Invoice invoice in e.NewItems)
{
invoice.PropertyChanged += new PropertyChangedEventHandler(invoice_PropertyChanged);
NotifyPropertyChanged("TotalOpenInvoices");
}
foreach (Invoice invoice in e.OldItems)
{
invoice.PropertyChanged -= new PropertyChangedEventHandler(invoice_PropertyChanged);
NotifyPropertyChanged("TotalOpenInvoices");
}
}
void invoice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "PayedInFull")
NotifyPropertyChanged("TotalOpenInvoices");
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Note that each class could be abstracted to a single class that implements INotifyPropertyChanged, such as a ViewModelBase class, but that is not necessarily required for this example.
I am working in vs2010.
I have created a DataGrid which is bounded to
ObservableCollection List;
the Class_CMD looks like this :
public class Class_RetrieveCommand
{
public string CMD { get; set; }
public bool C_R_CMD { get; set; }
public bool S_CMD { get; set; }
public bool C_S_CMD { get; set; }
}
i have 4 delegates which i pass to another window, and this window needs to update the list during runtime. During the runtime i can see the string column of the grid updated all the time but the DataGridCheckBoxColumns are never updated.
the DataGrid -
<DataGrid Background="Transparent" x:Name="DataGrid_CMD" Width="450" MaxHeight="450" Height="Auto" ItemsSource="{Binding}" AutoGenerateColumns="True">
one of the delegates which updates the bool is -
public void UpdateC_S_CMD(string Msg)
{
foreach (Class_CMD c in List.ToArray())
{
if (c.CMD.Equals(Msg))
c.C_S_CMD = true;
}
}
I don't understand why the bool columns are not updated....
can anyone help please?
thanks.
Your class Class_RetrieveCommand needs to implement the INotifyPropertyChanged interface. Otherwise the individual rows databound to the instances of the class don't know that the underlying properties have changed. If you change it to something like this, you should see the changes reflected in your grid:
public class Class_RetrieveCommand : INotifyPropertyChanged
{
private bool _cRCmd;
private bool _cSCmd;
private string _cmd;
private bool _sCmd;
public string CMD
{
get { return _cmd; }
set
{
_cmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("CMD"));
}
}
public bool C_R_CMD
{
get { return _cRCmd; }
set
{
_cRCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("C_R_CMD"));
}
}
public bool S_CMD
{
get { return _sCmd; }
set
{
_sCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("S_CMD"));
}
}
public bool C_S_CMD
{
get { return _cSCmd; }
set
{
_cSCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("C_S_CMD"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
You should implement INotifyPropertyChanged in the Class_RetrieveCommand like this:
public class Class_RetrieveCommand : INotifyPropertyChanged
{
private string _CMD;
public string CMD
{
get { return _CMD; }
set { _CMD = value; OnPropertyChanged("CMD"); }
}
... similar for the other properties
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Unfortunately you can't use auto properties anymore then (except you resort to proxygenerators).