How to use ObservableCollection databinding? - c#

Hello all i am pretty new to c# mvvm or data binding. I saw some examples using
ObservableCollection As explanined, An ObservableCollection is a dynamic collection of objects of a given type. Objects can be added, removed or be updated with an automatic notification of actions.
I have the following code: a property of type ObservableCollection<StudentViewModel> but it also implements the INotifyPropertyChanged interface. So why it needs to implement the INotifyPropertyChanged interface with type ObservableCollection here ? why not just automatic notification?
public ObservableCollection<StudentViewModel> TheStudents
{
get
{
return _theStudents;
}
set
{
if (_theStudents == value)
return;
if (_theStudents != null)
{
foreach (var StudentViewModel in _theStudents)
{
DisconnectStudentViewModel(StudentViewModel);
}
}
_theStudents = value;
if (_theStudents != null)
{
foreach (var StudentViewModel in _theStudents)
{
ConnectStudentViewModel(StudentViewModel);
}
}
OnPropertyChanged("TheStudents");
}
}
more background: simply to say when click button, the following function GetStudentsAction() will trigger------>TheStudents = sth in turn should told the VIEW that property has changed.
public void GetStudentsAction()
{
TheStudents = GetStudentsDelegate();
IsSaveStudentsActionEnabled = true;
IsAddStudentsActionEnabled = true;
}

The ObservableCollection class sends notifications when the contents of the collection change, such as adding or removing an item. These are special "this collection has changed" events that WPF can listen for (as defined in INotifyCollectionChanged).
It does not send notifications when you swap out the collection itself for a new instance of the collection. That's the responsibility of the object that has the collection as a property. This is the same event you have to send whenever any property on your view model has changed (as defined in INotifyPropertyChanged).
As a rule of thumb, it's rarely a good idea to have collection properties that can be set externally, the way you're using them. Typically you would give your collection property a private setter, and only allow external classes to add, remove, and clear the items from it. That eliminates the need to have a notification if your collection changes: you instantiate it once, in the constructor, and it never changes after that.
(That advice may or may not apply to your situation, but typically when I see collection types with public setters, it indicates a flaw in the design.)

Related

Working with an ObservableCollection based on an Entity Framework one to many property

Working with WPF in MVVM. I have a ViewModel with a CurrentItem property. This is an Item object which is pulled directly from Entity Framework. Item has a collection of Property objects.
public virtual ICollection<Property> Properties { get; set; }
In the View, I need users to be able to add and remove objects from this collection. To do that, I need to create an ObservableCollection<Property>, which we'll call ItemProperties.
There are various ways to do this. The most obvious is to add a ObservableCollection<Property> property on the ViewModel. Then populate this in the constructor, like so:
ItemProperties = new ObservableCollection<Property>(CurrentItem.Properties);
It's also possible to create an ObservableCollection wrapper that sits over the top of the real collection:
public ObservableCollection<Property> ItemProperties
{
get
{
return new ObservableCollection<Property>(CurrentItem.Properties);
}
set
{
CurrentItem.Properties = value.ToList();
OnPropertyChanged("ItemProperties");
}
}
Which has its own problems. You can't just Add() to this collection, since it'll get first, meaning the collection remains unchanged. So you'd either have to spin up a new collection, add to that, and then assign its value to the property or raise the OnPropertyChanged event outside the property. Either of which also sounds like a maintenance issue.
Is there a more effective way of doing this, which allows you to access the EF property list directly?
On this you have advantage of decoupling between data layer and Presentation , No need to spin up the collection.
Try a LoadedEvent to load data from the server.
Sample event is below
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
RaisePropertyChanged(() => ItemProperties);
}
}
The loaded event
var result= await Task.Run(() => MyBusiness.GetMyData());
//Map to the viewModel if necessary
ItemProperties = result;
Add to the collection
var isSuccess = await Task.Run(()=>MyBusiness.Insert(x));
if(isSuccess)
{
ItemProperties.Add(x);
}
If you have access to your DbContext in your ViewModel class, you can use DbSet<TEntity>.Local property which it will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet, but first you need to filter to load into memory only the PropertyItems that belong to your CurrentItem.
public class YourViewModel
{
private context=new YourContext();
public YourViewModel()
{
context.ItemProperties.Where(ip=>ip.ItemId==CurrentItem.Id).Load();
ItemProperties=context.ItemProperties.Local;
}
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
OnPropertyChanged("ItemProperties");
}
}
public void SaveItemProperties()
{
context.SaveChanges();
}
}
To save the changes the only you need to do is create, for example, a command that calls the SaveItemProperties method. Also, it could be a good idea disable lazy loading to not load twice the ItemProperties related to your CurrentItem.
If you need to understand more about how this works you can read this article.
either way is good. But what you need to do is to define an handler to the event CollectionChanged present in the Observable Collection. Your underlying entity must have a default constructor too. So when the new item will be created in the grid, that event will be raised.
_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
}
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add
}
First of all, I don't think creating an ObservableCollection for every get is a good idea. Instead I would cache it in a field. Second, for the cached instance, you will probably want to subscribe to CollectionChanged event in which you will changes will be persisted to the underlying collection.

WPF Bind once and never update?

I have a ListView and a GridView that lists users in an application by names. Whenever the user selects an user to edit, I add a new tab to a TabControl, and bind all editable properties to the WPF controls.
However, when the user is editing in the Edit Tab, the information in the List (specifically, the name field) is also being updated.
Currently I'm making a copy of the object to be edited and leaving the original so it doesn't update the ListView, but isn't there a better/easier way to do this?
I've tried setting the Binding Mode=OneWay, didn't work, and also UpdateSourceTrigger=Explicit in the GridView but also didn't work.
Is there any easier way to do this?
Edit: The way I implemented my INotifyPropertyChanged class is part of the issue, since I have this:
public partial class MyTabControl : UserControl
{
public MyTabControl()
{
InitializeComponent();
//Here, DataContext is a List<Users>
//Users being my Model from the Database
//Some of it's properties are bound to a GridView
//User doesn't implement INPC
}
public void OpenTab(object sender, RoutedEventArgs e)
{
User original = (sender as Button).DataContext as User;
// - This will create a new ViewModel below with the User I'm sending
MyTabControl.AddTab(original);
}
}
And my ViewModel of Users is:
public class UserViewModel : INotifyPropertyChanged
{
public User Original { get; private set; }
public string Name { get { return Original.Name; } set { Original.Name = value; OnPropertyChanged("Name"); } }
public UserViewModel(User original)
{
Original = original ?? new User();
}
// - INPC implementation
}
Since my ViewModel is the one reporting the property changes, I didn't expect my original User to report it as well to the GridView.
The Mode=OneWay causes the information flow to go from the bound data entity to the target UI property only, any change to the UI property will not be bound back.
The reason why the UI content is changing is because the underlying property is read/write (i.e. has a getter and a setter) and is notifying any value change (due to the implementation of the INPC interface).
Presuming that it is a list of User objects you've bound to the GridView, you have two simple options to fix this. Which one is best depends on how much scope for change you have:
change the current Name property on the User object, remove the setter for it. Replace the setter with a method to set the property (i.e. SetUserName(string name)) which then sets the private member variable. Or pass the name as an argument to the constructor of the User entity.
create a new property with only a getter which returns the name and set your binding to that; i.e. public string UserName { get { return Name; }}. As there is only a getter there will be no notification of this property, so if the name does change it won't be propagated via this new property.

Bound data in WPF not updating

I have an application which shows some ListBox's. These ListBox's are bound to data. One of the lists is a list of Doors, while the other list is a list of Users.
The list of Doors are coming from a DataManager class which communicates with the database. The list of Users is coming from an other class which does some calculations.
I've bound the two ListBox's to their appropiate ObservableList getter setter.
For the door:
public ObservableList<Door> Doors
{
get { return DataManager.Doors; }
}
and for the user:
public ObservableList<User> Users
{
get { return classLogic._users; }
}
Here comes the problem. When I add or remove a Door, the list on the UI gets updated. When I add or remove a User, the list doesn't get updated. I have to reload the view (restart the application) to update it. What am I missing? Why isn't it working?
an observable collection raises PropertyChanged for properties of each item
like if you had a IsDoorClosed Property it would update
removing an element raises a CollectionChanged event on Doors but the UI is not updated since
a PropertyChanged event was not Raised on the Bound Property Doors .
you need to Raise A PropertyChanged event on Doors on each CollectionChanged of Doors .
something along the lines of : this is psado code , it was written here for as an example
for your benefit , so check for any syntax errors .
Doors.CollectionChanged += OnDoorsCollectionChanged;
private static void OnDoorsCollectionChanged(object sender , CollectionChangedEventArgs e)
{
PropertyChanged(sender,new PropertyChangedEventArgs("Doors"));
}
I've found out myself that there are three steps to complete. I don't believe a PropertyChanged event is needed to update the ListBox. This may be since .NET 4.0 because I've read in versions below, the databinding isn't really correct yet.
The first step is that the list has to be a private static ObservableList<...>. The second is that the getter of this list has to be the appropiate type as well. This means in my case, the following code needs to be in ClassLogic:
private static readonly ObservableList<User> _users= new ObservableList<User>();
public static ObservableList<User> Users
{
get { return _users; }
}
And the third thing is, when calling this function (getter) in the DataContext class to bind the data to the ListBox, the classname has to be used instead of an instance of that class!
So, in this case it would be:
/// <summary>
/// Gets the Users that are managed by the ClassLogic class
/// </summary>
public ObservableList<User> Users
{
get { return ClassLogic.Users; }
//wrong would be:
//get { return classLogic.Users }
}
These 3 steps bound my data and made sure the ListBox updated when the contents of the list was updated as well.

ObservableCollection Behavior in MVVM Child Instance

I have a strange behavior going on. I'm using MVVM pattern, i have a binding to an Observable collection named AlarmCollection to a grid control in a View named AlarmView. When i create multiple instances of a AlarmModelView class, and add items to AlarmCollection, all the instances display the changes.
Any changes to the ObservableColelction AlarmCollection, affects all the bound ItemSources of the grid controls.
I have tried to lock the dispatcher thread, from a similar post here, to no avail.
Is there anyway to keep the changes to this Observable collection, within each instance of the ViewModel? So that each modification does not affect any other collection in the UI thread.
Any help is appreciated.
[edit below]
It is strange scenario, I need to zoom/drill into what is rendered by creating the new instances of the Child MV, which in turn adds tabs to the Parent MV. The Child Views are all bound to the same Collection names, and all are being updated by a WCF Async call. I need X number multiple instances, based on the how deep the zoom level goes, so i need 1 ModelView object.
How would i achieve this using CollectionChanged event or creating the ModelView's own CollectionView?
private MainViewModel _parentViewModel;
public MainViewModel ParentViewModel
{
get { return _parentViewModel; }
set
{
if (ParentViewModel == value) { return; }
SetPropertyValue(ref _parentCircuitViewModel, value, "ParentViewModel");
}
}
private ObservableCollection<DetailEntity> _alarmCollection;
public ObservableCollection<DetailEntity> AlarmCollection
{
get
{
if (_alarmCollection == null)
_alarmCollection = new ObservableCollection<DetailEntity>();
return _alarmCollection;
}
private set { _alarmCollection = value; }
}
ServiceNode _selectedNode;
public ServiceNode SelectedNode
{
get { return _selectedNode; }
set
{
SetPropertyValue(ref _selectedNode, value, "SelectedNode");
// render selected child node service path
RenderSubPath(_selectedNode);
// reset storage value
_selectedCircuitNode = null;
}
}
// Constructor
public RenderViewModel(string servicePath CircuitMainViewModel parentViewModel)
{
ServicePath = servicePath,
ParentCircuitViewModel = parentViewModel;
// event to handler for completed async calls
Client.GetAlarmsByNodeListCompleted += new EventHandler<GetAlarmsByNodeListCompletedEventArgs>(Client_GetAlarmsByNodeListCompleted);
}
void RenderSubPath(ServiceNode childNode)
{
if (childNode == null)
return;
// create a new child instance and add to parent VM tab
_parentViewModel.AddServiceRenderTab(new ViewModel.Workspaces.RenderViewModel(childNode.elementPath _parentViewModel);
}
// wcf async webservice call to add alarm to ObservableCollection
// ** This is updating all Collections in all Views.
void Client_GetAlarmsByNodeListCompleted(object sender, AlarmServiceReference.GetAlarmsByNodeListCompletedEventArgs e)
{
try
{
if (e.Result == null)
return;
// add to parent Netcool alarm collection
foreach (DetailEntity alarm in nodeAlarms)
{
_alarmCollection.Add(alarm);
}
}
}
From your description, it sounds as though all your views are bound to the same underlying collection. For any collection you bind to, WPF will actually bind to a collection view (ICollectionView) wrapped around that collection. If you don't explicitly create your own collection view, it will use a default one. Any binding to the same collection will result in the same collection view being used.
It's hard to say without seeing your code, but it's likely you want to either use a separate instance of the underlying view model (and, hence, the collection) or you want to explicitly create separate collection views and bind to them instead.

WPF: Replacing databound collection contents without Clear/Add

When using WPF databinding, I obviously can't do something along the lines of MyCollection = new CollectionType<Whatever>( WhateverQuery() ); since the bindings have a reference to the old collection. My workaround so far has been MyCollection.Clear(); followed by a foreach doing MyCollection.Add(item); - which is pretty bad for both performance and aesthetics.
ICollectionView, although pretty neat, doesn't solve the problem either since it's SourceCollection property is read-only; bummer, since that would have been a nice and easy solution.
How are other people handling this problem? It should be mentioned that I'm doing MVVM and thus can't rummage through individual controls bindings. I suppose I could make a wrapper around ObservableCollection sporting a ReplaceSourceCollection() method, but before going that route I'd like to know if there's some other best practice.
EDIT:
For WinForms, I would bind controls against a BindingSource, allowing me to simply update it's DataSource property and call the ResetBindings() method - presto, underlying collection efficiently changed. I would have expected WPF databinding to support a similar scenario out of the box?
Example (pseudo-ish) code: WPF control (ListBox, DataGrid, whatever you fancy) is bound to the Users property. I realize that collections should be read-only to avoid the problems demonstrated by ReloadUsersBad(), but then the bad code for this example obviously wouldn't compile :)
public class UserEditorViewModel
{
public ObservableCollection<UserViewModel> Users { get; set; }
public IEnumerable<UserViewModel> LoadUsersFromWhateverSource() { /* ... */ }
public void ReloadUsersBad()
{
// bad: the collection is updated, but the WPF control is bound to the old reference.
Users = new ObservableCollection<User>( LoadUsersFromWhateverSource() );
}
public void ReloadUsersWorksButIsInefficient()
{
// works: collection object is kept, and items are replaced; inefficient, though.
Users.Clear();
foreach(var user in LoadUsersFromWhateverSource())
Users.Add(user);
}
// ...whatever other stuff.
}
If the object MyCollection is of implements INotifyPropertyChanged, you can simply replace the collection.
An example:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Whatever> _myCollection;
private void NotifyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<Whatever> MyCollection
{
get
{
return _myCollection;
}
set
{
if (!ReferenceEquals(_myCollection, value))
{
_myCollection = value;
NotifyChanged("MyCollection");
}
}
}
}
With this, when you assign a collection, WPF detects this and everything gets updated.
This is how I'd solve this.
The link below explains how to implement an AddRange method.
http://web.archive.org/web/20150715112054/http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
It looks like you're stuck with implementing a sub-class that handles this case correctly.
Apparently, certain controls don't support batched collection change notifications. At least they didn't when that article was written. Though now you should have a bit more information if you want to investigate further.

Categories