How to cancel an edit to an object using MVVM? - wpf

How can I implement cancelation of editing an object using MVVM.
For example: I have a list of customers. I choose one customer an click the button "Edit", a dialog window(DataContext is binded to CustomerViewModel) opens and I start editing customer's fields. And then I decide to cancel editing, but the fields of the customer have been already changed, so how can I return a customer to its previous state in MVVM?

Check out the IEditableObject interface. Your Customer class should implement that, and your commands can execute BeginEdit / CancelEdit / EndEdit as appropriate.

You can use binding with UpdateSourceTrigger=Explicit. Here you can find more information how this can be implemented.

One super easy way, if your object is already serializable, such as if you are using WCF. You can serialize your original object into an internal field. If, your object isn't serializable, then just use AutoMapper to create a copy of your object with one line of code.
Order backup = Mapper.Map<Order, Order>(order);
When you handle your CancelCommand, just call AutoMapper in reverse. Since your properties already have a change notification everything just works. Its possible you could combine these techniques with IEditableObject, if you need and want to write the extra code.

In this article, Raul just reload the object from the DB. I guess it's less trouble than the solution Kent proposes.
internal void Cancel(CustomerWorkspaceViewModel cvm)
{
Mainardi.Model.ObjectMapping.Individual dc = cvm.DataContext
as Mainardi.Model.ObjectMapping.Individual;
int index = 0;
if (dc.ContactID > 0 && dc.CustomerID > 0)
{
index = _customerCollectionViewModel.List.IndexOf(dc);
_customerCollectionViewModel.List[index] =
_customerBAL.GetCustomerById(dc.CustomerID);
}
Collection.Remove(cvm);
}

Based on Камен Великов's answer:
You can mark your bindings as to be updated manually by defining
<TextBox Name="yourTextBox" Text="{BindingPath=YourBinding, UpdateSourceTrigger=Explicit}" />
in your view (XAML). Then, you have to write the changes from your UI in ViewModel by calling
yourTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
when Save is clicked.
Please note, if there are updated to the binding source triggered from anything else, they are still shown directly in the UI.

I had this problem too. I solved it using "The Memento Pattern Design". With this pattern you could easy save a copy of your original object and, in selectedIndexChange (of a control) or in the Cancel button, you could restore easy the prior version of your object.
An example of use of this pattern is available at How is the Memento Pattern implemented in C#4?
An example of code:
If we have a class User with properties UserName Password and NombrePersona we need to add methods CreateMemento and SetMemento:
public class Usuario : INotifyPropertyChanged
{
#region "Implementación InotifyPropertyChanged"
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private String _UserName = "Capture su UserName";
public String UserName
{
get { return _UserName; }
set { _UserName = value; RaisePropertyChanged("UserName"); }
}
private String _Password = "Capture su contraseña";
public String Password
{
get { return _Password; }
set { _Password = value; RaisePropertyChanged("Password"); }
}
private String _NombrePersona = "Capture su nombre";
public String NombrePersona
{
get { return _NombrePersona; }
set { _NombrePersona = value; RaisePropertyChanged("NombrePersona"); }
}
// Creates memento
public Memento CreateMemento()
{
return (new Memento(this));
}
// Restores original state
public void SetMemento(Memento memento)
{
this.UserName memento.State.UserName ;
this.Password = memento.State.Password ;
this.NombrePersona = memento.State.NombrePersona;
}
Then, we need a class Memento that will contain the "copy" of our object like this:
/// <summary>
/// The 'Memento' class
/// </summary>
public class Memento
{
//private Usuario _UsuarioMemento;
private Usuario UsuarioMemento { get; set; }
// Constructor
public Memento(Usuario state)
{
this.UsuarioMemento = new Usuario();
this.State.UserName = state.UserName ;
this.State.Password = state.Password ;
this.State.NombrePersona = state.NombrePersona ;
}
// Gets or sets state
public Usuario State
{
get { return UsuarioMemento; }
}
}
And we need a class that will generate and contains our memento object:
/// <summary>
/// The 'Caretaker' class
/// </summary>
class Caretaker
{
private Memento _memento;
// Gets or sets memento
public Memento Memento
{
set { _memento = value; }
get { return _memento; }
}
}
Then for implement this pattern we have to create an instance of Caretaker class
Caretaker creadorMemento = new Caretaker();
And create our memento object when a new user was selected for edit, for example in selectedIndexChange after the SelectedUser has been initializing, I use the method for event RaisPropertyChanged like this:
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
if (prop == "RowIndexSelected") // This is my property assigned to SelectedIndex property of my DataGrid
{
if ((this.UserSelected != null) && (creadorMemento .Memento != null))
{
this.UserSelected.SetMemento(creadorMemento .Memento);
}
}
if (prop == "UserSelected") // Property UserSelected changed and if not is null we create the Memento Object
{
if (this.UserSelected != null)
creadorMemento .Memento = new Memento(this.UserSelected);
}
}
An explication for this, when selectedIndexChanged change value we check if UserSelected and our memento object are not null means that our actual item in edit mode has changed then we have to Restore our object with the method SetMemento.
And if our UserSelected property change and is not null we "Create our Memento Object" that we will use when the edit was cancel.
For finish, we have use the SetMemento method in every method that we need to cancel the edition, and when the edit has commited like in the SaveCommand we can set null our memento object like this this.creadorMemento = null.

You could also, in your ViewModel copy the model's state to internal fields, and then expose these and then only set them on the model, when the user actually commits the change.
Problem could be, that on-the-fly validation will be more troublesome if validation relies on the entity being updated - if this is a requirement you could create a clone of the model to work on and then merging the clone with the actual entity when it is saved.

Related

Add, rename, remove item in treeview with MVVM WPF

I refer excellent tutorial of Josh Smith to work with treeview.
https://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode
I try to modified with this code to add, remove, rename item to this treeview but I don't know why it not update
Rename item command
#region RenameCommand
/// <summary>
/// Returns the command used to execute a search in the family tree.
/// </summary>
public ICommand RenameCommand
{
get { return _renameCommand; }
}
private class RenameFamilyTreeCommand : ICommand
{
readonly FamilyTreeViewModel _familyTree;
public RenameFamilyTreeCommand(FamilyTreeViewModel familyTree)
{
_familyTree = familyTree;
}
public bool CanExecute(object parameter)
{
return true;
}
event EventHandler ICommand.CanExecuteChanged
{
// I intentionally left these empty because
// this command never raises the event, and
// not using the WeakEvent pattern here can
// cause memory leaks. WeakEvent pattern is
// not simple to implement, so why bother.
add { }
remove { }
}
public void Execute(object parameter)
{
//MessageBox.Show("Rename command");
_familyTree._rootPerson.Children[0].Children[0].Header = "Hello";
if (_familyTree._rootPerson.Children[0] == null)
return;
// Ensure that this person is in view.
if (_familyTree._rootPerson.Children[0].Parent != null)
_familyTree._rootPerson.Children[0].Parent.IsExpanded = true;
_familyTree._rootPerson.Children[0].IsSelected = true;
}
}
#endregion // RenameCommand
Add item command
#region AddCommand
/// <summary>
/// Returns the command used to execute a search in the family tree.
/// </summary>
public ICommand AddCommand
{
get { return _addCommand; }
}
private class AddFamilyTreeCommand : ICommand
{
public FamilyTreeViewModel _familyTree;
public AddFamilyTreeCommand(FamilyTreeViewModel familyTree)
{
_familyTree = familyTree;
}
public bool CanExecute(object parameter)
{
return true;
}
event EventHandler ICommand.CanExecuteChanged
{
// I intentionally left these empty because
// this command never raises the event, and
// not using the WeakEvent pattern here can
// cause memory leaks. WeakEvent pattern is
// not simple to implement, so why bother.
add { }
remove { }
}
public void Execute(object parameter)
{
Person newPerson = new Person();
newPerson.Header = "New Person";
newPerson.Name = "1.1.1.75";
PersonViewModel newPersonViewModel = new PersonViewModel(newPerson);
////_rootPerson.Children.Add(newPersonViewModel);
//_rootPerson.Children.Add(newPersonViewModel);
//if (newPersonViewModel.Parent != null)
// newPersonViewModel.Parent.IsExpanded = true;
//newPersonViewModel.IsSelected = true;
_familyTree._rootPerson.Children[0].Children.Add(newPersonViewModel);
if (_familyTree._rootPerson.Children[0] == null)
return;
// Ensure that this person is in view.
if (_familyTree._rootPerson.Children[0].Parent != null)
_familyTree._rootPerson.Children[0].Parent.IsExpanded = true;
_familyTree._rootPerson.Children[0].IsSelected = true;
}
}
#endregion // AddCommand
Add command working fine but it's seem to be GUI not update. Rename command is not working but GUI is updated. I don't know reason why, And it's hard to access person class (use parent, person, children,..)
Is there anyone successfully update add, rename, remove command to Josh Smith project.
p/s: I debug by messagebox.show and see binding command for add and rename are working well, But the problem is I don't know what exactly to use Add, remove, rename person in Josh Smith project
Adding items is not reflected in the UI, because the source collection Person.Children doesn't implement INotifyCollectionChanged.
Whenever you need dynamic collections, where add, remove or move operations should update the binding target, you should use the ObservableCollection<T>, which implements INotifyCollectionChanged.
Similar applies to the Person.Name property. If you want a property's change to be reflected to the UI, then your view model must implement INotifyPropertyChanged and raise the INotifyPropertyChanged.PropertyChanged event whenever the binding source (the view model property) has changed.
Generally, when a class serves as a binding source for data binding, then this class must implement INotifyPropertyChanged (if this interface is not implemented, then the performance of data binding becomes very bad).
When the modification of a property should update the UI (binding.target) by invoking the data binding, then the modified property must raise the INotifyPropertyChanged.PropertyChanged event.
When the modification of a collection should update the UI (binding target) by invoking the data binding, then the modified collection must implement INotifyCollectionChanged and raise the INotifyCollectionChanged.CollectionChanged event. ObservableCollection provides a default implementation of INotifyCollectionChanged.
The following example follows the above rules. The changes made to the Person class should fix your issues. Changes to the data model will now be reflected in the TreeView:
public class Person : INotifyPropertyChanged
{
private ObservableCollection<Person> _children = new ObservableCollection<Person>();
public ObservableCollection<Person> Children
{
get { return _children; }
}
private string name
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

How to raise property changed event when child property changes [duplicate]

I have a Person class:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name{
get { return _name; }
set {
if ( _name != value ) {
_name = value;
OnPropertyChanged( "Name" );
}
}
private Address _primaryAddress;
public Address PrimaryAddress {
get { return _primaryAddress; }
set {
if ( _primaryAddress != value ) {
_primaryAddress = value;
OnPropertyChanged( "PrimaryAddress" );
}
}
//OnPropertyChanged code goes here
}
I have an Address class:
public class Address : INotifyPropertyChanged
{
private string _streetone;
public string StreetOne{
get { return _streetone; }
set {
if ( _streetone != value ) {
_streetone = value;
OnPropertyChanged( "StreetOne" );
}
}
//Other fields here
//OnPropertyChanged code goes here
}
I have a ViewModel:
public class MyViewModel
{
//constructor and other stuff here
private Person _person;
public Person Person{
get { return _person; }
set {
if ( _person != value ) {
_person = value;
OnPropertyChanged( "Person" );
}
}
}
I have a View which has the following lines:
<TextBox Text="{Binding Person.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
<TextBox Text="{Binding Person.Address.StreetOne, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
Both values show up in the text box ok when the view loads.
Changes to the first text box will fire OnPropertyChanged( "Person" ) in MyViewModel. Great.
Changes to the second text box ("Person.Address.StreetOne") does NOT fire OnPropertyChanged( "Person" ) inside MyViewModel. Meaning it doesn't call the Person object's SET method. Not great. Interestingly the SET method of StreetOne inside the Address class is called.
How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???
Do I need to flatten my data so SteetOne is inside Person and not Address??
Thanks!
While adding 'pass-through' properties to your ViewModel is a fine solution, it can quickly become untenable. The standard alternative is to propagate changes as below:
public Address PrimaryAddress {
get => _primaryAddress;
set {
if ( _primaryAddress != value )
{
//Clean-up old event handler:
if(_primaryAddress != null)
_primaryAddress.PropertyChanged -= AddressChanged;
_primaryAddress = value;
if (_primaryAddress != null)
_primaryAddress.PropertyChanged += AddressChanged;
OnPropertyChanged( "PrimaryAddress" );
}
void AddressChanged(object sender, PropertyChangedEventArgs args)
=> OnPropertyChanged("PrimaryAddress");
}
}
Now change notifications are propagated from Address to person.
Edit: Moved handler to c# 7 local function.
if you want the viewmodel SET to be called you could create a street property
public class MyViewModel
{
//constructor and other stuff here
public string Street{
get { return this.Person.PrimaryAddress.StreetOne; }
set {
if ( this.Person.PrimaryAddress.StreetOne!= value ) {
this.Person.PrimaryAddress.StreetOne = value;
OnPropertyChanged( "Street" );
}
}
}
xaml
<TextBox Text="{Binding Street, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged />
but this solution has its drawbacks. i go with Reeds answer in my projects
How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???
Why do you want to do this? It should not be required - you only need the StreetOne property changed event to fire.
Do I need to flatten my data so SteetOne is inside Person and not Address??
If you want to actually cause this to trigger, you don't need to flatten it (though that is an option). You can subscribe to the Address's PropertyChanged event within your Person class, and raise the event for "Address" within Person when it changes. This shouldn't be necessary, however.
Since I wasn't able to find a ready-to-use solution, I've done a custom implementation based on Pieters (and Marks) suggestions (thanks!).
Using the classes, you will be notified about any change in a deep object tree, this works for any INotifyPropertyChanged implementing Types and INotifyCollectionChanged* implementing collections (Obviously, I'm using the ObservableCollection for that).
I hope this turned out to be a quite clean and elegant solution, it's not fully tested though and there is room for enhancements. It's pretty easy to use, just create an instance of ChangeListener using it's static Create method and passing your INotifyPropertyChanged:
var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged +=
new PropertyChangedEventHandler(listener_PropertyChanged);
the PropertyChangedEventArgs provide a PropertyName which will be always the full "path" of your Objects. For example, if you change your Persons's "BestFriend" Name, the PropertyName will be "BestFriend.Name", if the BestFriend has a collection of Children and you change it's Age, the value will be "BestFriend.Children[].Age" and so on. Don't forget to Dispose when your object is destroyed, then it will (hopefully) completely unsubscribe from all event listeners.
It compiles in .NET (Tested in 4) and Silverlight (Tested in 4). Because the code in seperated in three classes, I've posted the code to gist 705450 where you can grab it all: https://gist.github.com/705450 **
*) One reason that the code is working is that the ObservableCollection also implements INotifyPropertyChanged, else it wouldn't work as desired, this is a known caveat
**) Use for free, released under MIT License
There is a spelling mistake in your property change notification:
OnPropertyChanged( "SteetOne" );
should be
OnPropertyChanged( "StreetOne" );

What is the best way to force the WPF DataGrid to add a specific new item?

I have a DataGrid in a WPF application which has for its ItemsSource a custom collection that I wrote. The collection enforces that all its items satisfy a certain requirement (namely they must be between some minimum and maximum values).
The collection's class signature is:
public class CheckedObservableCollection<T> : IList<T>, ICollection<T>, IList, ICollection,
INotifyCollectionChanged
where T : IComparable<T>, IEditableObject, ICloneable, INotifyPropertyChanged
I want to be able to use the DataGrid feature in which committing an edit on the last row in the DataGrid results in a new item being added to the end of the ItemsSource.
Unfortunately the DataGrid simply adds a new item created using the default constructor. So, when adding a new item, DataGrid indirectly (through its ItemCollection which is a sealed class) declares:
ItemsSource.Add(new T())
where T is the type of elements in the CheckedObservableCollection. I would like for the grid to instead add a different T, one that satisfies the constraints imposed on the collection.
My questions are: Is there a built in way to do this? Has somebody done this already? What's the best (easiest, fastest to code; performance is not an issue) way to do this?
Currently I just derived DataGrid to override the OnExecutedBeginEdit function with my own as follows:
public class CheckedDataGrid<T> : DataGrid where T : IEditableObject, IComparable<T>, INotifyPropertyChanged, ICloneable
{
public CheckedDataGrid() : base() { }
private IEditableCollectionView EditableItems {
get { return (IEditableCollectionView)Items; }
}
protected override void OnExecutedBeginEdit(ExecutedRoutedEventArgs e) {
try {
base.OnExecutedBeginEdit(e);
} catch (ArgumentException) {
var source = ItemsSource as CheckedObservableCollection<T>;
source.Add((T)source.MinValue.Clone());
this.Focus();
}
}
}
Where MinValue is the smallest allowable item in the collection.
I do not like this solution. If any of you have advice I would be very appreciative!
Thanks
This problem is now semi-solvable under 4.5 using the AddingNewItem event of the DataGrid. Here is my answer to a similar question.
I solved the problem by using DataGrid's AddingNewItem event. This almost entirely undocumented event not only tells you a new item is being added, but also [allows lets you choose which item is being added][2]. AddingNewItem fires before anything else; the NewItem property of the EventArgs is simply null.
Even if you provide a handler for the event, DataGrid will refuse to allow the user to add
rows if the class doesn't have a default constructor. However, bizarrely (but thankfully) if you do have one, and set the NewItem property of the AddingNewItemEventArgs, it will never be called.
If you choose to do this, you can make use of attributes such as [Obsolete("Error", true)] and [EditorBrowsable(EditorBrowsableState.Never)] in order to make sure no one ever invokes the constructor. You can also have the constructor body throw an exception
Decompiling the control lets us see what's happening in there...
For anybody interested, I ended up solving the problem by just deriving from BindingList<T> instead of ObservableCollection<T>, using my derived class as the ItemsSource in a regular DataGrid:
public class CheckedBindingList<T> : BindingList<T>, INotifyPropertyChanged where T : IEditableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Predicate<T> _check;
private DefaultProvider<T> _defaultProvider;
public CheckedBindingList(Predicate<T> check, DefaultProvider<T> defaultProvider) {
if (check == null)
throw new ArgumentNullException("check cannot be null");
if (defaultProvider != null && !check(defaultProvider()))
throw new ArgumentException("defaultProvider does not pass the check");
_check = check;
_defaultProvider = defaultProvider;
}
/// <summary>
/// Predicate the check item in the list against.
/// All items in the list must satisfy Check(item) == true
/// </summary>
public Predicate<T> Check {
get { return _check; }
set {
if (value != _check) {
RaiseListChangedEvents = false;
int i = 0;
while (i < Items.Count)
if (!value(Items[i]))
++i;
else
RemoveAt(i);
RaiseListChangedEvents = true;
SetProperty(ref _check, value, "Check");
ResetBindings();
}
}
}
public DefaultProvider<T> DefaultProvider {
get { return _defaultProvider; }
set {
if (!_check(value()))
throw new ArgumentException("value does not pass the check");
}
}
protected override void OnAddingNew(AddingNewEventArgs e) {
if (e.NewObject != null)
if (!_check((T)e.NewObject)) {
if (_defaultProvider != null)
e.NewObject = _defaultProvider();
else
e.NewObject = default(T);
}
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e) {
switch (e.ListChangedType) {
case (ListChangedType.ItemAdded):
if (!_check(Items[e.NewIndex])) {
RaiseListChangedEvents = false;
RemoveItem(e.NewIndex);
if (_defaultProvider != null)
InsertItem(e.NewIndex, _defaultProvider());
else
InsertItem(e.NewIndex, default(T));
RaiseListChangedEvents = true;
}
break;
case (ListChangedType.ItemChanged):
if (e.NewIndex >= 0 && e.NewIndex < Items.Count) {
if (!_check(Items[e.NewIndex])) {
Items[e.NewIndex].CancelEdit();
throw new ArgumentException("item did not pass the check");
}
}
break;
default:
break;
}
base.OnListChanged(e);
}
protected void SetProperty<K>(ref K field, K value, string name) {
if (!EqualityComparer<K>.Default.Equals(field, value)) {
field = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
This class is incomplete, but the implementation above is enough for validating lists of statically-typed (not built by reflection or with the DLR) objects or value types.

PropertyChanged notification for calculated properties

I'm developing an application in Silverlight2 and trying to follow the Model-View-ViewModel pattern. I am binding the IsEnabled property on some controls to a boolean property on the ViewModel.
I'm running into problems when those properties are derived from other properties. Let's say I have a Save button that I only want to be enabled when it's possible to save (data has been loaded, and we're currently not busy doing stuff in the database).
So I have a couple of properties like this:
private bool m_DatabaseBusy;
public bool DatabaseBusy
{
get { return m_DatabaseBusy; }
set
{
if (m_DatabaseBusy != value)
{
m_DatabaseBusy = value;
OnPropertyChanged("DatabaseBusy");
}
}
}
private bool m_IsLoaded;
public bool IsLoaded
{
get { return m_IsLoaded; }
set
{
if (m_IsLoaded != value)
{
m_IsLoaded = value;
OnPropertyChanged("IsLoaded");
}
}
}
Now what I want to do is this:
public bool CanSave
{
get { return this.IsLoaded && !this.DatabaseBusy; }
}
But note the lack of property-changed notification.
So the question is: What is a clean way of exposing a single boolean property I can bind to, but is calculated instead of being explicitly set and provides notification so the UI can update correctly?
EDIT: Thanks for the help everyone - I got it going and had a go at making a custom attribute. I'm posting the source here in case anyone's interested. I'm sure it could be done in a cleaner way, so if you see any flaws, add a comment or an answer.
Basically what I did was made an interface that defined a list of key-value pairs to hold what properties depended on other properties:
public interface INotifyDependentPropertyChanged
{
// key,value = parent_property_name, child_property_name, where child depends on parent.
List<KeyValuePair<string, string>> DependentPropertyList{get;}
}
I then made the attribute to go on each property:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class NotifyDependsOnAttribute : Attribute
{
public string DependsOn { get; set; }
public NotifyDependsOnAttribute(string dependsOn)
{
this.DependsOn = dependsOn;
}
public static void BuildDependentPropertyList(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
var obj_interface = (obj as INotifyDependentPropertyChanged);
if (obj_interface == null)
{
throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name));
}
obj_interface.DependentPropertyList.Clear();
// Build the list of dependent properties.
foreach (var property in obj.GetType().GetProperties())
{
// Find all of our attributes (may be multiple).
var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false);
foreach (var attribute in attributeArray)
{
obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name));
}
}
}
}
The attribute itself only stores a single string. You can define multiple dependencies per property. The guts of the attribute is in the BuildDependentPropertyList static function. You have to call this in the constructor of your class. (Anyone know if there's a way to do this via a class/constructor attribute?) In my case all this is hidden away in a base class, so in the subclasses you just put the attributes on the properties. Then you modify your OnPropertyChanged equivalent to look for any dependencies. Here's my ViewModel base class as an example:
public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
// fire for dependent properties
foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname)))
{
PropertyChanged(this, new PropertyChangedEventArgs(p.Value));
}
}
}
private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>();
public List<KeyValuePair<string, string>> DependentPropertyList
{
get { return m_DependentPropertyList; }
}
public ViewModel()
{
NotifyDependsOnAttribute.BuildDependentPropertyList(this);
}
}
Finally, you set the attributes on the affected properties. I like this way because the derived property holds the properties it depends on, rather than the other way around.
[NotifyDependsOn("Session")]
[NotifyDependsOn("DatabaseBusy")]
public bool SaveEnabled
{
get { return !this.Session.IsLocked && !this.DatabaseBusy; }
}
The big caveat here is that it only works when the other properties are members of the current class. In the example above, if this.Session.IsLocked changes, the notification doesnt get through. The way I get around this is to subscribe to this.Session.NotifyPropertyChanged and fire PropertyChanged for "Session". (Yes, this would result in events firing where they didnt need to)
The traditional way to do this is to add an OnPropertyChanged call to each of the properties that might affect your calculated one, like this:
public bool IsLoaded
{
get { return m_IsLoaded; }
set
{
if (m_IsLoaded != value)
{
m_IsLoaded = value;
OnPropertyChanged("IsLoaded");
OnPropertyChanged("CanSave");
}
}
}
This can get a bit messy (if, for example, your calculation in CanSave changes).
One (cleaner? I don't know) way to get around this would be to override OnPropertyChanged and make the call there:
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "IsLoaded" /* || propertyName == etc */)
{
base.OnPropertyChanged("CanSave");
}
}
You need to add a notification for the CanSave property change everywhere one of the properties it depends changes:
OnPropertyChanged("DatabaseBusy");
OnPropertyChanged("CanSave");
And
OnPropertyChanged("IsEnabled");
OnPropertyChanged("CanSave");
How about this solution?
private bool _previousCanSave;
private void UpdateCanSave()
{
if (CanSave != _previousCanSave)
{
_previousCanSave = CanSave;
OnPropertyChanged("CanSave");
}
}
Then call UpdateCanSave() in the setters of IsLoaded and DatabaseBusy?
If you cannot modify the setters of IsLoaded and DatabaseBusy because they are in different classes, you could try calling UpdateCanSave() in the PropertyChanged event handler for the object defining IsLoaded and DatabaseBusy.

Silverlight DataBinding cross thread issue

I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Have you tried implementing INotifyPropertyChanged?
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
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));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}

Resources