Collection properties should be read only - c#

I am using FxCop for my WPF MVVM assembly and it gives me the error
Collection properties should be read only
But in my property i need to RaisePropertyChangedEvent, now if i set the property to read only by removing its set section, how could i raise this event.
Syntax is somewhat like this
public List Employees
{
get { return _employees; }
set
{
if (ReferenceEquals(_employees, value))
return;
_employees = value;
RaisePropertyChanged("Employees");
}
}

You should rarely need to raise a PropertyChanged event on a collection. Make the collection observable so that it notifies any bindings whenever items are added or removed:
public IList<Employee> Employees
{
get;
private set;
}
// in your constructor:
this.Employees = new ObservableCollection<Employee>();

If you make your collection an ObservableCollection then the "important" events will be when items are added and removed from the collection, not when the collection is instatiated. I agree, with FxCop. Make the collection readonly, but make it an ObservableCollection

Related

ObservableCollection<T> isn't getting notified of change in property of <T>

I have a an ObservableCollection of Component, a class with another ObservableCollection, a String and a ComponentVersion. The SelectedComponentVersion is being updated via my view correctly but I'm unable to get the property of Components to fire it's setter/OnPropertyChanged.
private ObservableCollection<Component> components
public ObservableCollection<Component> Components
{
get { return foo; }
set { foo = value; OnPropertyChanged(); }
}
Here is the class of Component.
public class Component : ViewModelBase
{
private string componentName;
private ObservableCollection<ComponentVersion> componentVersions;
private ComponentVersion selectedComponent;
public string ComponentName
{
get { return componentName; }
set { componentName = value; OnPropertyChanged(); }
}
public ObservableCollection<ComponentVersion> ComponentVersions
{
get { return componentVersions; }
set { componentVersions = value; OnPropertyChanged(); }
}
public ComponentVersion SelectedComponent
{
get { return selectedComponent; }
set { selectedComponent = value; OnPropertyChanged(); }
}
public Component(string componentName, List<ComponentVersion> componentVersion)
{
ComponentName = componentName;
ComponentVersions = componentVersion.ToObservableCollection();
}
}
I then have a binding from a listview onto the SelectedComponent property inside of Component.
I have read countless stack overflows about setting up CollectionChanged and have tried to implement it with no luck.
Components.CollectionChanged += stuff;
private void stuff(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new System.NotImplementedException();
}
but this is never hit as tested with breakpoints.
Am I missing something entirely, losing my mind or daft! Someone please give me a point in the right direction, if any of this makes any sense at all.
P.S another solution I though of would be to place an invisible button inside the listview and have that send a command to tell the vm that a selected item has been updated.
In cases like this where I want to do something when the property on an item inside the collection changes, I usually hook up a PropertyChanged event in the CollectionChanged event
Here's a code example :
public MyViewModel()
{
// Setup Collection, with a CollectionChanged event
Components = new ObservableCollection<Component>();
Components.CollectionChanged += Components_CollectionChanged;
}
// In the CollectionChanged event (items getting added or removed from collection),
// hook up the PropertyChanged event
void Components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(MyType item in e.NewItems)
item.PropertyChanged += Component_PropertyChanged;
if (e.OldItems != null)
foreach(MyType item in e.OldItems)
item.PropertyChanged -= Component_PropertyChanged;
}
// In the PropertyChanged event, run some code if SelectedComponent property changed
void Component_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedComponent")
DoWork();
}
Note that we are using two separate events here.
CollectionChanged event runs when the collection itself changes. This means it gets set to a new collection, or item(s) get added or removed from the collection.
This captures newly added items and hooks up the PropertyChanged
handler, or detaches the PropertyChanged handler for items being
removed from the collection.
PropertyChanged event runs when a property changes and fires the event. You'll use this event to run code when the SelectedComponent property on any item in the collection changes
Well, components hasn't changed. I'm not sure if you set up your handler correctly, but even if you did, a CollectionChanged event is only fired, if the collection changed (item added or removed).
Lets say you have a collection of cars and all are red.
You pick one car and set it's color to blue.
The collection has not changed. It's still the very same cars. No car is missing, no car was added.
You probably want to attach yourself to the handlers of all cars instead of the handler of the collection.
So to sum it up:
ObservableCollection<T> isn't getting notified of change in property of <T>
That's true and it's by design.
When adding items to the collection you want to "hook up" the events you are interested in for those items, then you can act as needed. As mentioned above, the collection isn't changing it is the individual items that are changing.
One thing you could do is extend the ObservableCollection class and override the functionality so that when the collection is changed, an item is either added or removed, you then go through the items in the collection and "hook up" the events you're interested in. One thing to note is you may have to go through the collection and remove the event handlers and "hook up" them again in order to stop getting multiple event handlers being set for the items in the list.
ObservableCollection<T> does not send notifications about property changes of the elements. However, the System.ComponentModel namespace contains another collection, which supports that: you can consider to use BindingList<T> instead, which is also supported by WPF (elements must implement INotifyPropertyChanged).
Please note though that BindingList<T> scales poorly and its performance starts to decline above hundreds and thousands of elements as it always searches for the changed element sequentially in order to return the element index in its ListChanged event.

ItemsSource binding do not update values

I need to make list of items. I binded collection of users to listbox. Everything works quite well, but items in listbox aren't updated in real time. They aren't updated at all by this binding. So when I remove any user from the list, listbox isn't updated even if its source is correctly changed.
Source is located in data view model at path DataViewModel.Instance.AllUsers; Whenever I add to this list new item or remove one, layout does not update. Other bindings work well. I tried to update listbox layout, to raise event of source update, other way of adding/removing items, but nothing worked.
I tried to debug binding, but I have too many bindings to find the error.
Thanks in advance for any useful advice.
Listbox:
<ListBox x:Name="ListboxUsers" ItemsSource="{Binding Path=AllUsers, Mode=OneWay}" Grid.Column="1" Margin="0" Grid.Row="5" Background="DimGray" BorderThickness="0" Visibility="Hidden" SelectionChanged="ListboxUsers_SelectionChanged"/>
Code-behind:
CatalogueGrid.DataContext = DataViewModel.Instance; //whole view model added as datacontext
DataViewModel class:
public class DataViewModel : INotifyPropertyChanged
{
private static DataViewModel _dataViewModel;
private Collection<UserModel> allUsers;
public Collection<UserModel> AllUsers
{
get
{
return allUsers;
}
set
{
allUsers = value;
NotifyPropertyChanged("AllUsers");
}
}
private DataViewModel()
{
AllUsers = new Collection<UserModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
.
.
.
}
use ObservableColLection instead if Collection wich implements the INotifyCollectionChanged Interface :
private ObservableCollection<UserModel> allUsers;
public ObservableCollection<UserModel> AllUsers
{
get
{
return allUsers;
}
set
{
allUsers = value;
NotifyPropertyChanged("AllUsers");
}
}
For changes to the collection to propagate to the UI, the collection class needs to implement INotifyCollectionChanged.
A very useful class that already implements this is ObservableCollection<T> (MSDN). Use that instead of Collection<T>.
You have bound your listbox to a Collection<T> - that is just a list, which does not issue any notifications to bound properties that its contents has changed. Hence, your listbox cannot possibly know when the collection has changed.
Instead, you can use the ObservableCollection<T> class (or, more precisely, any collection that also implements INotifyCollectionChanged), and changes will be automatically propagated to the listbox.
Note that your property does not have to be typed as ObservableCollection<T>, you can also just declare your public property as IEnumerable<T> or IList<T>; the binding will find out on its own whether the returned class also implements INotifyCollectionChanged. Like this, you are free to replace your actual underlying collection class later on, for example with a ReadOnlyObservableCollection<T>, in case you want to disallow changes from the outside.
Speaking of this, a note on your code: You have provided your AllUsers property with a setter. This may lead to undesired consequences, as you open up possibilities for some other code to set the property to null, which (depending on the rest of your code) might lead to exceptions. Unless you actually want to allow assigning new values, for the ItemsSource property binding, a read-only property is fully sufficient, as long as the returned collection object implements INotifyCollectionChanged.

Is INotifyPropertyChanged needed for binding ObservableCollection?

When I'm binding, say a Label to a string, I define the string this way:
private string _lbl;
public string Lbl
{
get
{
return = _lbl;
}
set
{
_lbl=value;
OnPropertyChanged("Lbl");
}
}
With the INotifyPropertyChanged interface implemented in my class.
Should I define the same way an ObservableCollection or I just could leave it this way?
public ObservableCollection<File> myFiles {get; set;}
As a general rule, I tend to define ObservableCollections like this:
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get { return _items ?? (_items = new ObservableCollection<Item>()); }
}
This is called "Lazy initialization", where the ObservableCollection is only instantiated where it is first accessed.
This is a good way to ensure your Collection Will Never Be Null
Notice that it does not have a setter, because an ObservableCollection is not something that you usually assign to. Instead, if you want to completely replace items, do:
Items.Clear();
//... Add all the new items
This avoids WPF having to rebind all the CollectionChanged events and stuff in order to listen to and react to items added / removed from the collection. You only have 1 instance of the collection, forever. Whatever items you place on it, the collection remains the same.
This is not to be confused with the PropertyChange notification inside the ITEMS of the collection. WPF handles these concepts separately, because property changes are notified by ITEMS, but Collection changes (Item added or removed) are notified by the Collection itself.
If the myFiles property can change, then yes, you should raise the PropertyChanged event. If not (that is, if it's got no setter, or it has a private setter that is only set once, e.g. in the constructor), then you don't need to raise the event. The collection itself will raise its own PropertyChanged and CollectionChanged events, but the object that contains the collection must raise PropertyChanged if the property that contains the collection changes.

ObservableCollection loses binding when I "new" it

I have a ListBox on my UI that is bound to a property of ObservableCollection. I set a new instance of the ObservableCollection into the property in the view model's constructor and I can add items to it with a button on the form. These are visible in the list.
All is good.
However, if I reinitialize the property with new in the button callback, it breaks the binding and the UI no longer shows what is in the collection.
I assumed the binding would continue to look up the values of the property, but its presumably linked to a reference which is destroyed by the new.
Have I got this right? Can anyone expand on how this is linked up? Is there a way to rebind it when my view model has no knowledge of the view?
Make sure you are raising a PropertyChangedEvent after you reintialize your collection. Raising this event will allow the view to handle changes to the property with the model having no knowledge of the view.
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
I think based on your description that what you need to do is refactor the property that exposes your ObservableCollection so that it raises a PropertyChanged event also when it is assigned a new value. Example:
public ObservableCollection<int> Integers
{
get { return this.integers; }
set {
if (this.integers != value)
{
this.integers = value;
RaisePropertyChanged("Integers");
}
}
}
Supposing you've implemented INotifyPropertyChanged on your ViewModel, you can raise the property changed event on your ObservableCollection whenever you assign a new value to it.
public ObservableCollection<string> MyList { get; set; }
public void SomeMethod()
{
MyList = new ObservableCollection<string>();
RaisePropertyChanged("MyList");
}
Updates to the ObservableCollection are handled by hooking in to the CollectionChanged event so when you create a new ObservableCollection your observer is still looking at the old collection.
Two simple suggestions would be either to implement INotifyPropertyChanged on the class that contains the ObservableCollection and raising the PropertyChanged event in the setter of the collection property (don't forget to unhook from the old one first in your observer if it's your own code).
private ObservableCollection<string> _myCollection = new ObservableCollection<string>();
public ObservableCollection<string> MyCollection
{
get { return _myCollection; }
set
{
if(_myCollection == value)
return;
_myCollection = value;
RaisePropertyChanged("MyCollection");
}
}
A second, and the option I generally prefer is to just clear and repopulate the collection with your new data when it arrives.
public void HandleCollectionData(IEnumerable<string> incomingData)
{
MyCollection.Clear();
foreach(var item in incomingData)
{
MyCollection.Add(item);
}
}

Property setter not getting called with CollectionEditor

I have a custom control that has an Items property. I Have applied an EditorAttribute with a UITypeEditor of type CollectionEditor.
Collection Type:
[Serializable]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public class ListItemsCollection : CollectionBase
{
// methods
}
Property Declaration In The Control:
private new ListItemsCollection _Items;
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public new ListItemsCollection Items
{
get
{
return _Items;
}
set
{
_Items = value;
// do other UI changes
}
}
Problem:
When I drop this control to the designer surface, I am able to add items to the Items property using the PropertyGrid. But, the when I click the Ok button of the CollectionEditor the setter of the Items property is not getting called.
AFAIK when a value is returned from the EditValue method of a UITypeEditor class the setter block of the property is supposed to be called.
This is driving me insane. I even tried adding Event's to the ListItemsCollection, so that when Items are added, I can whatever I want with the control's ui.
This is not supposed to be hard. What am I doing wrong?
I try to reprodeuce your situation: using following code, I get a message box showing whenever I edit the list from VS property window. Beware that you have to create the list by yourself. If you don't create it, VS create a temp list which you can edit from property window, but does not set your property to this list (so your setter will never be called)
public UserControl1()
{
InitializeComponent();
list = new BindingList<ListViewItem>();
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
}
void list_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show(e.ListChangedType.ToString());
}
private BindingList<ListViewItem> list;
public BindingList<ListViewItem> List1
{
get { return list; }
}
Collection properties should be read-only. It's the collection that is retrieved through the getter, and adjusted. The setter never enters into it, because that would mean setting a new collection.

Categories

Resources