I have an ObservableCollection binded to a listbox
public ObservableCollection<InsertionVM> Insertions
{
get
{
return _insertions;
}
set
{
_insertions = value;
base.OnPropertyChanged("ChromosomeList");
}
}
Its member, InsertionVM implements INotifyPropertyChanged. It has a property that will be updated.
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
base.OnPropertyChanged("IsChecked");
}
}
Why doesn't the ObservableCollection refresh even though I implement the INotifyPropertyChanged interface for each property?
Update:
I tried the link given below, but the "more sensitive collection" is only updated when objects are removed / added.
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (InsertionVM item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (InsertionVM item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityViewModelPropertyChanged;
}
}
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Debugger does not reach here
}
Constructor:
public ChromosomeVM(Chromosome _chr, string insertionFilePath)
{
Chr = _chr;
_insertions.CollectionChanged += ContentCollectionChanged;
}
This is your code: (please see the comment also, made by me)
public ObservableCollection<InsertionVM> Insertions // propertyName == Insertions
{
get
{
return _insertions;
}
set
{
_insertions = value;
base.OnPropertyChanged("ChromosomeList"); // What is ChromosomeList??
}
}
Can you see the problem now? Change ChromosomeList to Insertions. Hope some problem at least will be fixed!
Always remember the following:
ObservableCollection<T> only notifies when number of items (it may stay same, when one item is added and one is removed, but you get the point) in it changes.
If an item in ObservableCollection<T> changes, collection is not responsible for propagating change notifications.
be sure to put the [Insertions] in the path of the binding and it will work
[Insertions] will be updated only if you change the reference like.
Insertions = new ObservableCollection<InsertionVM>( Items);
To make your code more effective add check if the value change in the set like
public ObservableCollection<InsertionVM> Insertions
{
get
{
return _insertions;
}
set
{
if(_insertions != value)
{
_insertions = value;
base.OnPropertyChanged("Insertions");
}
}
}
Related
I have a datagrid in a WPF app that is bound to an ObservableCollection like so
<DataGrid ItemsSource="{Binding Spring.SpringData, Mode=OneWay}" />
My data displays fine, and I can edit the data in my grid, but it does not fire the PublishSpringChange event when I manually edit the data in the grid. The underlying data changes, but the event does not fire, what am I missing?
With a model of Spring that has the following
public class Spring : INotifyPropertyChanged
{
private ObservableCollection<SpringData> _SpringData;
public ObservableCollection<SpringData> SpringData
{
get { return _SpringData; }
}
public Spring()
{
....
_SpringData = new ObservableCollection<SpringData>();
SpringData.CollectionChanged += PublishSpringChange;
...
}
private void PublishSpringChange(object sender, NotifyCollectionChangedEventArgs e)
{
// Code that does not run!
}
}
with a SpringData class of
public class SpringData: BindableBase
{
private double _force;
private double _displacement;
public SpringData(double displacement, double force)
{
Displacement = displacement;
Force = force;
}
public double Displacement
{
get { return _displacement; }
set { SetProperty(ref _displacement, value); }
}
public double Force
{
get { return _force; }
set { SetProperty(ref _force, value); }
}
}
INotifyCollectionChanged only fires when you actually modify the collection. This is when you Add, Remove, Move, Replace or Reset items in the collection. It will not fire when one of the properties in a SpringData object is changed.
In order to listen to changes for a SpringData object, assuming it implements INotifyPropertyChanged, you will need to hook up listeners to the PropertyChanged event of each of the items.
Its quite useful to have a single handler for all properties changing sometimes. Here's how you can do it.
Handle CollectionChanged as you are above:
_SpringData = new ObservableCollection<SpringData>();
SpringData.CollectionChanged += PublishSpringChange;
Now for all added and removed objects to the collection add a handler to PropertyChanged:
private void PublishSpringChange(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (INotifyPropertyChanged added in e.NewItems)
{
added.PropertyChanged += SpringDataOnPropertyChanged;
}
foreach (INotifyPropertyChanged removed in e.OldItems)
{
removed.PropertyChanged -= SpringDataOnPropertyChanged;
}
}
private SpringDataOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
//your code here
}
As the question ask, how is this done ?
From a DataGrid I can get the SelectedRow of that collection that has been bounded to its ItemSource, but I really need a setter and getter for an individual property that belongs inside the ObservableCollection.
For example, I need to catch when a user checks a bool property inside a datagrid, so then the setter would be set to "false/true". So something like
//But the Archive property is in the DataContext of the row item...
//so this wouldnt work, I think..
private bool m_Archived = false;
public bool Archived
{
get { return m_Archived; }
set
{
m_Archived = value;
OnPropertyChanged("Archived");
}
}
But remember this property is part of the ObservableCollection (DataContext)
Cheers
You need to register to each collection item property changed, then to control when the desired property has changed, then change the archived property. Check this sample code:
private ObservableCollection<TClass> _SomeObservableCollection;
public ObservableCollection<TClass> SomeObservableCollection
{
get { return _SomeObservableCollection ?? (_SomeObservableCollection = SomeObservableCollectionItems()); }
}
private ObservableCollection<TClass> SomeObservableCollectionItems()
{
var resultCollection = new ObservableCollection<TClass>();
foreach (var item in SomeModelCollection)
{
var newPoint = new TClass(item) {IsLocated = true};
newPoint.PropertyChanged += OnItemPropertyChanged;
resultCollection.Add(newPoint);
}
resultCollection.CollectionChanged += OnSomeObservableCollectionCollectionChanged;
return resultCollection;
}
private void OnSomeObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (TClass TClass in e.NewItems)
{
TClass.PropertyChanged += OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (TClass TClass in e.OldItems)
{
TClass.PropertyChanged -= OnItemPropertyChanged;
}
}
if (!Patient.HasChanges)
Patient.HasChanges = true;
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "ItemArchivedProperty") return;
// set Archived = true or set Archived = false
}
This is just an example, but it should works. Hope it helps.
I want to set the Visbility of an Expander based on the selected value of a ComboBox.
That ComboBox is already mapped to an object from the Model:
<ComboBox Name="SelectedCar" ItemsSource="{Binding Path=CarCategories}" SelectedValue="{Binding Path=Car.CarCategory, Mode=TwoWay}"/>
I've set a property in the VM that derives the Visbility this way:
private Visibility _extraCarDetailsVisibility;
public Visibility ExtraCarDetailsVisibility
{
get
{
if (ManagedPortfolioSelected != null)
{
var category = Car.CarCategory.ToLower();
if (category == "porsche")
{
_extraCarDetailsVisibility= Visibility.Visible;
}
}
_extraCarDetailsVisibility= Visibility.Collapsed;
return _extraCarDetailsVisibility;
}
set
{
_extraCarDetailsVisibility= value;
NotifyPropertyChanged("ExtraCarDetailsVisibility");
}
}
And this is how I use it:
<Expander Visibility="{Binding Path=ExtraCarDetailsVisibility}">
However this doesn't work since I think the CarCategories change event isn't subscribed (and it seems I cannot really as it comes from the Model) so the ExtraCarDetailsVisibility property is never recalled when I cahnge the Car Category...
How would you do this? Thank you!
There was couple of issues there:
I was missing to inherit from the INotifyPropertyChanged in my ICar interface although implemented in the Car object - so couln't reach the PropertyChanged event there.
I had to update the code in order to fire an event each time my ComboBox Catgory was changed. To do so, I hadto:
Hook some event to the PropertyChanged event of the Car object:
private ICar _carSelected;
public IPortfolio CarSelected
{
get { return _carSelected; }
set
{
if (_carSelected!= value)
{
if (_carSelected!= null)
{
_carSelected.PropertyChanged -= OnCarSelectedPropertyChanged;
}
_carSelected= value;
// Also subscribing to any change that happens to the Carselected
_carSelected.PropertyChanged += OnCarSelectedPropertyChanged;
}
// Notifying when the selected portfolio is changed
NotifyPropertyChanged("CarSelected");
}
}
Trigger the event once the CarSelected property changes:
void OnCarSelectedPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CarCategory")
{
NotifyPropertyChanged("ExtraCarDetailsVisibility");
}
}
... that will then update my visibility property I've hooked to the View:
private Visibility _extraCarDetailsVisibility;
public Visibility ExtraCarDetailsVisibility
{
get
{
_extraCarDetailsVisibility= Visibility.Collapsed;
if (PendingCarSelected != null)
{
var category = CarSelected.CounterpartyCategory.ToLower();
if (category == "porsche" || category == "porsches")
{
_extraCarDetailsVisibility= Visibility.Visible;
}
}
return _extraCarDetailsVisibility;
}
set
{
_extraCarDetailsVisibility= value;
NotifyPropertyChanged("ExtraCarDetailsVisibility");
}
}
I have an MVVM-based WPF application that relies on Caliburn.Micro.
In one view, I am displaying a DataGrid and a Button. The DataGrid displays a collection of items, where the item class derives from PropertyChangedBase.
The button should be enabled or disabled based on the contents in the editable DataGrid cells. What is the most reliable approach to achieve this with Caliburn.Micro?
Schematically, this is what my code looks right now:
public class ItemViewModel : PropertyChangedBase { }
...
public class ItemsViewModel : PropertyChangedBase
{
private IObservableCollection<ItemViewModel> _items;
// This is the DataGrid in ItemsView
public IObservableCollection<ItemViewModel> Items
{
get { return _items; }
set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
// This is the button in ItemsView
public void DoWork() { }
// This is the button enable "switch" in ItemsView
public bool CanDoWork
{
get { return Items.All(item => item.NotifiableProperty == some_state); }
}
}
As the code stands, there is no notification to ItemsViewModel.CanDoWork when one NotifiableProperty is changed, for example when the user edits one cell in the ItemsView´s DataGrid. Hence, the DoWork button enable state will never be changed.
One possible workaround is to add an (anonymous) event handler to every item in the Items collection:
foreach (var item in Items)
item.PropertyChanged +=
(sender, args) => NotifyOfPropertyChange(() => CanDoWork);
but then I also need to keep track of when (if) items are added or removed from the Items collection, or if the Items collection is re-initialized altogether.
Is there a more elegant and reliable solution to this problem? I am sure there is, but so far I have not been able to find it.
I think this is a case where INPC works well; to simplify registering/deregistering adds and deletes just add a CollectionChanged handler to your Items collection:
Items.CollectionChanged += OnItemsCollectionChanged;
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null && e.NewItems.Count != 0) {
foreach (ItemViewModel vm in e.NewItems)
vm.PropertyChanged += OnDetailVmChanged;
}
if (e.OldItems != null && e.OldItems.Count != 0) {
foreach (ItemViewModel vm in e.OldItems) {
vm.PropertyChanged -= OnDetailVmChanged;
}
}
}
Josh Smith wrote a PropertyObserver class here that I find more elegant than shotgun INPC tracking, but in a master-detail scenario like yours you would still have to track the adds and deletes.
EDIT by Anders Gustafsson
Note that for the above code to work in the general case requires that Items has been initialized with the default constructor before the event handler is attached. To ensure that OnDetailVmChanged event handlers are correctly added and removed, the Items property setter need to be extended to something like this:
public IObservableCollection<ItemViewModel> Items
{
get { return _items; }
set
{
// If required, initialize empty _items collection and attach
// event handler
if (_items == null) {
_items = new BindableCollection<ItemViewModel>();
_items.CollectionChanged += OnItemsCollectionChanged;
}
// Clear old contents in _items
_items.Clear();
// Add value item:s one by one to _items
if (value != null) foreach (var item in value) _items.Add(item);
NotifyOfPropertyChange(() => Items);
}
}
(And of course, with the above Items setter in place, the topmost Items.CollectionChanged event handler attachment should not be included in the code.)
Ideally, I would have used if (value != null) _items.AddRange(value);, but when the AddRange method triggers the OnItemsCollectionChanged event handler, e.NewItems appear to be empty (or null). I have not explicitly verified that e.OldItems is non-null when the Clear() method is invoked; otherwise Clear() would also need to be replaced with one-by-one removal of the item:s in _items.
Whenever a property changes fire RaiseCanExecuteChanged for the button cummand command?
For Example :
public DelegateCommand<object> MyDeleteCommand { get; set; }
string _mySelectedItem;
public string MySelectedItem
{
get { return _mySelectedItem; }
set
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
MyDeleteCommand.RaiseCanExecuteChanged();
}
}
My issue seems to be "scope", though I'm not certain that's the right terminology. I want to notify a read-only list to re-evaluate itself when a property within a custom object is set. I believe it is simply not aware of it's existence. Maybe there is an easy way around this I cannot think of, but I'm drawing a blank.
I find this hard to put into words, so here's simplified code with my comments on what I expect to happen.
Properties within object in which I am databinding to:
private CvarAspectRatios _aspectRatio = new CvarAspectRatios("none", GetRatio());
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{ // This setter never gets hit since I bind to this
if (value != null) // object's 'Value' property now.
{
_aspectRatio = value;
NotifyPropertyChanged("AspectRatio");
NotifyPropertyChanged("ResolutionList"); // I want to inform ResolutionList
} // that it needs to repopulate based
} // on this property: AspectRatio
}
private ResolutionCollection _resolutionList = ResolutionCollection.GetResolutionCollection();
public ResolutionCollection ResolutionList
{
get
{
ResolutionCollection list = new ResolutionCollection();
if (AspectRatio != null && AspectRatio.Value != null)
{
foreach (Resolutions res in _resolutionList.Where(i => i.Compatibility == AspectRatio.Value.Compatibility))
{
list.Add(res);
}
return list;
}
return _resolutionList;
}
}
CvarAspectRatios Class:
public class CVarAspectRatios : INotifyPropertyChanged
{
private string _defaultValue;
public string DefaultValue
{
get { return _defaultValue; }
set { _defaultValue = value; NotifyPropertyChanged("DefaultValue"); }
}
private AspectRatios _value;
public AspectRatios Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged("Value");
NotifyPropertyChanged("ResolutionList"); // This value gets set, and I'd like for ResolutionList to update
} // but it cannot find ResolutionList. No errors or anything. Just
} // no update.
public AspectRatios() { }
public AspectRatios(string defaultValue, AspectRatios val)
{
DefaultValue = defaultValue;
Value = val;
}
// Implementation of INotifyPropertyChanged snipped out here
}
What do you folks think? If you'd like a sample application I can whip one up.
Since CVarAspectRatios implements INotifyPropertyChanged, you can have the viewmodel class subscribe to the PropertyChanged event for the AspectRatio.
public class YourViewModel
{
public YourViewModel()
{
AspectRatio.PropertyChanged += AspectRatio_PropertyChanged;
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
NotifyPropertyChanged("ResolutionList");
}
}
Just bear in mind that if you discard that AspectRatio object (if the object reference changes and not just the value property of that object), you should unsubscribe from the event on the discarded one.
To just transform your existing code into something which should work:
private CvarAspectRatios _aspectRatio; //No field initialization because that would not attach event handler, you could do it though and take care of the handler alone in the ctor
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{
if (_aspectRatio != value) // WTH # "value != null"
{
_aspectRatio.PropertyChanged -= AspectRatio_PropertyChanged;
_aspectRatio = value;
_aspectRatio.PropertyChanged += new PropertyChangedEventHandler(AspectRatio_PropertyChanged);
NotifyPropertyChanged("AspectRatio");
}
}
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
NotifyPropertyChanged("ResolutionList");
}
}
Why don't you factor out re-populating ResolutionList into a separate private method which gets called from the setter of AspectRatios?
If a list needs to update based on a changed property, the list (or a list manager object, for better encapsulation) would normally need to subscribe to the PropertyChanged event of the object hosting the property. If the list is itself a property of the same object, as in this case, it would be simpler and leaner for the property's setter to call a method that updates the list.