Binding doesn't work after setting the property value to null - silverlight

I have a DataGrid witch is bound to a ObservableCollection<"Product">. The columns are bound to properties of Product. Most of then are of type double?(nullable).
In some time I have to set some property to null. After that, no matter the value I set, the binding don't work. The value is not updated in the view.
What happens to the binding when I set a property to null?
I tried what is shown in this blog post http://wildermuth.com/2009/11/18/Data_Binding_Changes_in_Silverlight_4 but it didn't worked to me.
Thanks!
Edit:
Below is the class I've created that implements the INotifyPropertyChanged
public class NotifyPropertyChangedAttribute : INotifyPropertyChanged
{
Dictionary<string, object> _propBag = new Dictionary<string, object>();
protected object Get(string propName)
{
object value = null;
_propBag.TryGetValue(propName, out value);
return value;
}
protected void Set(string propName, object value)
{
if (!_propBag.ContainsKey(propName) || Get(propName)!=null)
{
_propBag[propName] = value;
OnPropertyChanged(new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
This is my Product class. The DataGrid's ItemsSource property is bound to a ObservableCollection of Products:
public class Product : NotifyPropertyChangedAttribute
{
public string Name
{
get { return (string)Get("Name") ?? ""; }
set { Set("Name", value); }
}
public double? Price
{
get {return (double)Get("Price") ?? null;}
set { Set("Price", value);}
}
public void Reset()
{
var propertyInfo = typeof(Product).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
foreach (var p in propertyInfo)
{
p.SetValue(this , null, null);
}
}
}
Look the Reset() method. The binding stop working after I call this method.
In my app, I need that when the user press "Del" key, the DataGrid's row get empty, but can not be removed.

If you set the reference of the collection to null, the binding is broken between your control and source because the source doesn't exist anymore. In this case you have to explicitly rebind the items source in the control.
It is recommended to clear the collection instead of assigning null to it.
Update: For properties of items within the collection, make sure the item type implements INotifyPropertyChanged. The row in the DataGrid will be listening for changes through this interface on the item class itself.

Related

WPF How to set Validation.HasError property on controls manually?

I have a wpf window which fires validation when a user interacts with the control (got into the control and change the value which results in updated property) and upon property changed, validation fire and displayed as it should.
But I want to show all validation errors on the screen manually when a user clicks on the save button without traversing the controls, otherwise how it suppose to look if the user loads the screen and click on the save button.
Even if I create a method like IsValid() and call it upon clicking on the save button, it validates the whole form and tell me if it is valid or not but the red border around text boxes won't be showing(because Validation.HasError property is not being updated), which is I need because in a form of several
controls I need to notify the user about the exact control that is causing the problem.
You can get the sample project with the problem from this link
https://1drv.ms/u/s!AuCr-YEWkmWUiopdQ-eZ17IC7IAJnA
When we validate a property without traversing it. It won't update Validate.HasError property of the control. The solution to this was plain old simple NotifyPropertyChanged(propertyName).
I was using NotifyPropertyChanged when my property value changes(in the set) but without traversing it, it never fires.
So either we should call NotifyPropertyChanged when property's validation failed or we should call NotifyPropertyChanged(null) which notify all the control's to refresh their properties.
Adding full implementation of my INotifyDataErrorInfo
public class NotifyDataErrorInfoBase<T> : INotifyDataErrorInfo
{
public NotifyDataErrorInfoBase(T model)
{
Model = model;
}
public T Model { get; set; }
protected void SetValue<TValue>(string propertyName, TValue value)
{
typeof(T).GetProperty(propertyName).SetValue(Model, value);
ValidateProperty<TValue>(propertyName);
}
public bool ValidateAllProperties()
{
List<KeyValuePair<string, Type>> lstOfProperties = typeof(T).GetProperties().
Select(u => new KeyValuePair<string, Type>(u.Name, u.PropertyType)).ToList();
foreach (var property in lstOfProperties)
{
Type currentType = property.Value;
if (property.Value == typeof(string))
{
ValidateProperty<string>(property.Key);
}
else if (property.Value == typeof(int))
{
ValidateProperty<int>(property.Key);
}
}
return !HasErrors;
}
private void ValidateProperty<TValue>([CallerMemberName]string propertyName = null)
{
ClearErrors(propertyName);
var validationContext = new ValidationContext(Model) { MemberName = propertyName };
List<ValidationResult> results = new List<ValidationResult>();
var userName = GetValue<TValue>(propertyName);
Validator.TryValidateProperty(userName, validationContext, results);
if (results.Any())
{
foreach (var item in results)
{
AddError(propertyName, item.ErrorMessage);
}
}
}
protected TValue GetValue<TValue>(string propertyName)
{
return (TValue)typeof(T).GetProperty(propertyName).GetValue(Model);
}
Dictionary<string, List<string>> _lstOfErrors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => _lstOfErrors.Any();
public IEnumerable GetErrors(string propertyName)
{
return _lstOfErrors.ContainsKey(propertyName) ? _lstOfErrors[propertyName] : null;
}
protected void AddError(string propertyName, string errorMessage)
{
if (!_lstOfErrors.ContainsKey(propertyName))
{
_lstOfErrors[propertyName] = new List<string>();
}
_lstOfErrors[propertyName].Add(errorMessage);
}
protected void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
protected void ClearErrors(string propertyName)
{
if (_lstOfErrors.ContainsKey(propertyName))
_lstOfErrors.Remove(propertyName);
}
}

MVVM Binding to properties of an observable property

I am trying to figure out on how to trigger the PropertyChangedEvent when the middle layer of my binding changes. I will start with an example here:
public class MainViewModel :NotificationObject // Main DataContext
{
public SubViewModel SubVM{get; {_subVM = value; RaisePropertyChanged("SubVM");}} // observable property
public void DoChangeSubVM()
{
SubVM = new SubViewModel(); // doing this will not update the egControl
}
}
public class SubViewModel : NotificationObject
{
public Sub2ViewModel Sub2VM {get; set{_sub2VM = value; RaisePropertyChanged("Sub2VM");}} // observable property
}
public class Sub2ViewModel : NotificationObject
{
public int SomeProp {get; set {_someProp = value; RaisePropertyChanged("SomeProp");} // observable property
}
in the XAML:
<EgControl name="egControl" Content={Binding SubVM.Sub2VM.SomeProp} />
Now if I change the Sub2VM Property the egControl doesn't automagically get updated with the SomeProp value of the new Sub2VM instance. How does someone go about achieving this, with out manually having to raise all the Sub2ViewModel propertychanged events from Sub2VM property setter?
Using: Prism .NET 4.0
How does someone go about achieving this, with out manually having to raise all the Sub2ViewModel propertychanged events from Sub2VM property setter?
Answer
You have several possibilities:
Raise all property changed events in the setter, which you said you wanted to avoid. But it's a valid strategy to consider. If you know which properties are dependant on the results of another, then they will need to raise property changed in the setter for many properties.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
Raise all property changed events in the method, after the new ViewModel has been constructed.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
RaisePropertyChanged("FirstName");
}
}
public void UpdateFirstName(string firstName)
{
_FirstName = firstName;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
Use the setters to set some properties, thus triggering the already present property changed event.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
RaisePropertyChanged("FirstName");
}
}
public Person ClonePerson(Person rootPerson)
{
Person clone = new Person()
{
FirstName = rootPerson.FirstName;
LastName = rootPerson.LastName;
}
return clone;
}
}
Make a method that raises all property changed events, and call it in edge cases where you need to raise multiple changed events.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
this.RaiseAllPropertyChanges();
}
}
public void RaiseAllPropertyChanges()
{
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
The end result is this: For any bound UI element to know that it must update, the property changed event for that property must be raised.
Okay, not sure about Prism, but generally speaking all three classes should be implementing property change notification. The simplist way to do so is with INotifyPropertyChanged. So SubViewModel should be more like:
public class SubViewModel : INotifyPropertyChanged
{
private Sub2ViewModel sub2vm;
public Sub2ViewModel Sub2VM
{
get
{
return sub2vm;
}
set
{
sub2vm = value;
OnPropertyChanged("Sub2VM");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Without property change notification, the UI doesn't know when to udpate a bound property.
One way to go about it is to create a constructor for both the SubViewModel and Sub2ViewModel that initializes all the properties to some default value. This will ensure that your properties are initialized and give you the ability to set the initial values.

Silverlight: INotifyPropertyChanged does not seem to work

I haven't implement this pattern for a while (and when I did it was in 2, as opposed to 3), and I have several examples that all seem straight forward, but I can't work out what I have done wrong in the below piece of code (The Items are not updated when the property event fires):
public partial class Index : Page
{
private IndexViewModel _vm;
public Index()
{
InitializeComponent();
_vm = new IndexViewModel(19);
this.TheDataGrid.ItemsSource = _vm.Rows;
}
public class IndexViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
this.PropertyChanged(this, e);
}
public SortableCollectionView Rows
{
get
{
return _rows;
}
set
{
if (_rows == value)
return;
_rows = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Rows"));
}
}
This does not refresh my datagrid... as a 'hack' I have had to pass the datagrid object into my viewmodel and bind it there:
public IndexViewModel(int containerModelId, DataGrid shouldNotNeed)
{
ContainerModelId = containerModelId;
LoadOperation<vwColumn> headings = _ttasContext.Load(_ttasContext.GetRecordColumnsQuery(ContainerModelId));
headings.Completed += (sender2, e2) =>
{
//load data
LoadOperation<vwDataValue> data = _ttasContext.Load(_ttasContext.GetRecordsQuery(ContainerModelId, null));
data.Completed += (sender3, e3) =>
{
Rows = FormatData(data, headings);
shouldNotNeed.ItemsSource = Rows;
};
};
}
Assigning _vm.Rows to TheDataGrid.ItemsSource does not wire any change notification callback automatically. Try this:
in xaml:
<... x:Name=TheDataGrid ItemsSource={Binding Rows}>
In code:
this.DataContext = _vm;
As Codism points out your main problem is you need to use binding to take advantage of an INotifyPropertyChanged. However I would recommend this implementation pattern:-
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name);
}
...
set
{
if (_rows != value)
{
_rows = value;
NotifyPropertyChanged("Rows");
}
}
Note that this approach minimises the impact on a an object instance whose properties are not being observed. In the original pattern you create instances of PropertyChangedEventArgs and calls to the event delegate going off regardless of whether anything is actually listening.
this.TheDataGrid.ItemsSource = _vm.Rows
When a collection is assigned as the ItemsSource of a DataGird , any changes made to the collection can be observed by the DataGrid if the source implements INotifyCollectionChanged.
From your code sample , I can't tell if the type SortableCollectionView implements INotifyCollectionChanged or inherits from ObservableCollection.
Implementing INotifyCollectionChanged would mean that you can't reset the backing field _rows for property Rows , you can clear items in the collection and add them as needed.
Hope this helps

CollectionViewSource Filter not refreshed when Source is changed

I have a WPF ListView bound to a CollectionViewSource. The source of that is bound to a property, which can change if the user selects an option.
When the list view source is updated due to a property changed event, everything updates correctly, but the view is not refreshed to take into account any changes in the CollectionViewSource filter.
If I attach a handler to the Changed event that the Source property is bound to I can refresh the view, but this is still the old view, as the binding has not updated the list yet.
Is there a decent way to make the view refresh and re-evaluate the filters when the source changes?
Cheers
Updating the CollectionView.Filter based on a PropertyChanged event is not supported by the framework.
There are a number of solutions around this.
1) Implementing the IEditableObject interface on the objects inside your collection, and calling BeginEdit and EndEdit when changing the property on which the filter is based.
You can read more about this on the Dr.WPF's excellent blog here : Editable Collections by Dr.WPF
2) Creating the following class and using the RefreshFilter function on the changed object.
public class FilteredObservableCollection<T> : ObservableCollection<T>
{
public void RefreshFilter(T changedobject)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, changedobject, changedobject));
}
}
Example:
public class TestClass : INotifyPropertyChanged
{
private string _TestProp;
public string TestProp
{
get{ return _TestProp; }
set
{
_TestProp = value;
RaisePropertyChanged("TestProp");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
FilteredObservableCollection<TestClass> TestCollection = new FilteredObservableCollection<TestClass>();
void TestClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TestProp":
TestCollection.RefreshFilter(sender as TestClass);
break;
}
}
Subscribe to the PropertyChanged event of the TestClass object when you create it, but don't forget to unhook the eventhandler when the object gets removed, otherwise this may lead to memory leaks
OR
Inject the TestCollection into the TestClass and use the RefreshFilter function inside the TestProp setter.
Anyhow, the magic here is worked by the NotifyCollectionChangedAction.Replace which updates the item entirely.
Are you changing the actual collection instance assigned to the CollectionViewSource.Source, or are you just firing PropertyChanged on the property that it's bound to?
If the Source property is set, the filter should be recalled for every item in the new source collection, so I'm thinking something else is happening. Have you tried setting Source manually instead of using a binding and seeing if you still get your behavior?
Edit:
Are you using CollectionViewSource.View.Filter property, or the CollectionViewSource.Filter event? The CollectionView will get blown away when you set a new Source, so if you had a Filter set on the CollectionView it won't be there anymore.
I found a specific solution for extending the ObservableCollection class to one that monitors changes in the properties of the objects it contains here.
Here's that code with a few modifications by me:
namespace Solution
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e != null) // There's been an addition or removal of items from the Collection
{
Unsubscribe(e.OldItems);
Subscribe(e.NewItems);
base.OnCollectionChanged(e);
}
else
{
// Just a property has changed, so reset the Collection.
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
protected override void ClearItems()
{
foreach (T element in this)
element.PropertyChanged -= ContainedElementChanged;
base.ClearItems();
}
private void Subscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged += ContainedElementChanged;
}
}
private void Unsubscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged -= ContainedElementChanged;
}
}
private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
// Tell the Collection that the property has changed
this.OnCollectionChanged(null);
}
}
}
Maybe a bit late to the party but just in case
You can also use CollectionViewSource.LiveSortingProperties
I found it through this blog post.
public class Message : INotifyPropertyChanged
{
public string Text { get; set; }
public bool Read { get; set; }
/* for simplicity left out implementation of INotifyPropertyChanged */
}
public ObservableCollection<Message> Messages {get; set}
ListCollectionView listColectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(Messages);
listColectionView.IsLiveSorting = true;
listColectionView.LiveSortingProperties.Add(nameof(Message.Read));
listColectionView.SortDescriptions.Add(new SortDescription(nameof(Message.Read), ListSortDirection.Ascending));
I found a relatively simple method to do this.
I changed the readonly ICollectionView property to get/set and added the raised property event:
Property TypeFilteredCollection As ICollectionView
Get
Dim returnVal As ICollectionView = Me.TypeCollection.View
returnVal.SortDescriptions.Add(New SortDescription("KeyName", ListSortDirection.Ascending))
Return returnVal
End Get
Set(value As ICollectionView)
RaisePropertyChanged(NameOf(TypeFilteredCollection))
End Set
End Property
Then to update, i just used:
Me.TypeFilteredCollection = Me.TypeFilteredCollection
This clearly won't work if you don't have somewhere to trigger that update though.

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.

Resources