Bound data in WPF not updating - c#

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.

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.

Update ObservableCollection where the items are received from another List

I want to display all my CONTACTS in a ItemsControl dynamically.
I have a List Contacts in my Logic (this one gets updated if someone removed me or if someone accepted my request) and I've added this List to a ObservableCollection<> which is bound to the ListBox.
C#
Contacts = new ObservableCollection<Contact>(MyLogic.Current.Contacts);
XAML
<ItemsControl ItemsSource="{Binding Contacts}" x:Name="MainPanel">
And here's the problem:
When I want to add a contact to my Contacts LIST, the ObservableCollection doesn't get updated
MyLogic.Current.Contacts.Add(new Contact("Fred", true));
This is not the best solution but if you want to see where the problem is, the following code updates your UI:
var newContact = new Contact("Fred", true));
MyLogic.Current.Contacts.Add(newContact);
Contacts.Add(newContact);
A better solution is when MyLogic.Current.Contacts changes notify your UI via events.
Edit:
The problem is that I can only update the LIST and not the
ObservableCollection(the list itself is in a different project)... so
I need a way to update the GUI when that LIST is updated
To notify UI when ever your data changes you can use events as follow:
First define an EventArgs which shows newly added items like this:
public class ModelAddedEventArgs<TModel> : EventArgs
{
public ModelAddedEventArgs(TModel newModel)
{
NewModel = newModel;
}
public TModel NewModel { get; set; }
}
Then define an EventHandler in your MyLogic calss as follow:
public event EventHandler<ModelAddedEventArgs<Contact>> ContactAdded;
public void AddModel(Contact model)
{
// first add your contact then:
if (ActivityGroupAdded != null)
ActivityGroupAdded(this, new ModelAddedEventArgs<Contact>(model));
}
And finally use your EventHandler to notify UI:
private void YourUIConstructor()
{
MyLogic += OnContactAdded;
}
private void OnContactAdded(object sender, ModelAddedEventArgs<Contact> e)
{
Contacts.Add(e.NewModel);
}
ObservableCollection is not work with source List. When you create it from existing List it only copies elements from it to inner storage.
So, you should add elements to ObservableCollection itself to get what you want etc.
The problem you are having is that you are expecting the ObservableCollection to be automatically updated when you add to your List. This is not true.
When you instantiate your ObservableCollection, you are taking a copy of your List, not a reference to the list itself.
Therefore, when you add a new item, you should add to your ObservableCollection, not your List. Or both. But I can't see why you would need both, I would recommend ditching your List altogether.

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.

Implementing ICollectionViewLiveShaping

How is ICollectionViewLiveShaping implemented for the purpose of filtering? Is it something like:
public ICollectionView WorkersEmployed { get; set; }
WorkersEmployed = new CollectionViewSource { Source = GameContainer.Game.Workers }.View;
I'm not using GetDefaultView because I need multiple instances of filters on this collection. If it matters, GameContainer.Game.Workers is an ObservableCollection.
ApplyFilter(WorkersEmployed);
private void ApplyFilter(ICollectionView collectionView)
{
collectionView.Filter = IsWorkerEmployed;
}
public bool IsWorkerEmployed(object item)
{
Worker w = item as Worker;
return w.EmployerID == this.ID;
}
This all works, but of course it must be manually refreshed, which is why I'm trying to use ICollectionViewLiveShaping. How does live filtering working?
Update: It appears that the only way to add a property to ICollectionViewLiveShaping's LiveFilteringProperties collection is via a string. Given that limitation, is it even possible to filter by properties in another class (Workers' EmployerID in this case)?
Is what I'm trying to do in this situation is even a viable option?
All you need to do is add a property in LiveFilteringProperties for which you want the filter to call on property change and set IsLiveFiltering to true for your collection to enable live filtering.
Make sure PropertyChanged event gets raised whenever EmployerID property changes i.e. your Worker class should implement INotifyPropertyChangedEvent.
This will work then -
public ICollectionViewLiveShaping WorkersEmployed { get; set; }
ICollectionView workersCV = new CollectionViewSource
{ Source = GameContainer.Game.Workers }.View;
ApplyFilter(workersCV);
WorkersEmployed = workersCV as ICollectionViewLiveShaping;
if (WorkersEmployed.CanChangeLiveFiltering)
{
WorkersEmployed.LiveFilteringProperties.Add("EmployerID");
WorkersEmployed.IsLiveFiltering = true;
}
We are using WPF + MVVM + Visual Studio 2017.
We want to convert this to add live filtering:
public ObservableCollection<RowViewModel> Rows { get; set; }
The method below has two key advantages:
It's designed to work efficiently with the WPF runtime to minimise on-screen rendering using bulk updates.
So it's fast.
And because the boilerplate code is listed below, it's easier to follow compared to any other docs you will find on the web.
Please let me know if this worked for you, any issues and I'll update the instructions to make easier.
And the steps:
Step 1: Non-notifying Collection Wrapper
Create a special ObservableCollection that does not fire update events. This is a one-off. We want to fire the update bulk update event ourselves, which is faster.
public class NonNotifyingObservableCollection<T> : ObservableCollection<T>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { /* Do nothing */ }
}
Step 2: Convert to NonNotifyingObservableCollection
Convert to a private variable which uses this new collection.
private NonNotifyingObservableCollection<RowViewModel> rows;
// ... and in constructor
rows = new NonNotifyingObservableCollection<RowViewModel>();
Step 3: Add Wrapper
Add these variables:
private ICollectionView rowsView;
public ICollectionViewLiveShaping RowsLiveView { get; set; }
And in the Initialise() call after the ViewModel is constructed (or perhaps in the constructor):
// Call on the dispatcher.
dispatcher.InvokeAsync(() =>
{
this.rowsView = CollectionViewSource.GetDefaultView(this.rows);
this.rowsView.Filter = o =>
{
// This condition must be true for the row to be visible on the grid.
return ((RowViewModel)o).IsVisible == true;
};
this.RowsLiveView = (ICollectionViewLiveShaping)this.rowsView;
this.RowsLiveView.IsLiveFiltering = true;
// For completeness. Changing these properties fires a change notification (although
// we bypass this and manually call a bulk update using Refresh() for speed).
this.RowsLiveView.LiveFilteringProperties.Add("IsVisible");
});
Step 4: Add items
Now we add items to the backing collection, then call .Refresh() to refresh the view:
this.rowsView.Add(new RowViewModel( /* Set properties here. */ ));
We then bind the grid to RowsLiveView, (instead of binding to Rows in the original code).
Step 5: Update live filtering
Now we can update the IsVisible property, then call .Refresh() to redraw the grid.
rows[0].IsVisible=false;
this.rowsView.Refresh(); // Hides the first row.
Update
Update: This answer could be simplified. The whole point of ICollectionViewLiveShaping is to autorefresh without the need to call .Refresh(). Given that we have a NonNotifyingObservableCollection and we are manually controlling everything with a .Refresh(), could remove public ICollectionViewLiveShaping RowsLiveView { get; set; } and, directly to RowsView (make it a property with { get; set; }, and use normal ObservableCollection<>. In other words - ICollectionViewLiveShaping is great for a small amount of rows (e.g. <100), but for anything more, ICollectionView in combination with a bulk update and a manual Refresh() is better from a speed point of view.
I experimented with this and it looks like it is not designed for what you (and me) want: Automatic filtering when you change filtering conditions. It filters automatically when some properties of DataGrid's item source changes, but not when filter conditions change - you must call ICollectionViewSource.Refresh manually.

MVVM property depends on a graph of objects

I am working with WPF+MVVM.
I have a VM which contains a Customer property. The Customer has an ObservableCollection of Orders. Each Order has an ObservableCollection of Items. Each Items has a Price.
Now, I have the following property on my VM:
public double TotalPrice
{
return Customer.Orders.Sum(x => x.Items.Sum(y => y.Price));
}
The problem is whenever a change occurs at any point in this graph of objects - the UI should be notified that TotalPrice had changed - but it doesn't...
For example if the Customer will be altered from A to B, or an order will be added, or an item will be deleted, or an item's price will be altered etc.
Does anyone has an elegant solution for this?
Thanks.
Have you supported INotifyPropertyChanged / INotifyCollectionChanged interfaces in ViewModels? You should be able trigger any property manually, for instance in setter of any property you can trigger OnPropertyChanged("TotalPrice") so UI bindings for TotalPrice would be updated as well.
To handle dependent objects changes you can provide events or something like that so ViewModel would be able to subscribe and handle underlying object changes, for instance you have some service which is in chanrge of reloading of the Orders from a database, so as soo as new changes come you would update UI as well. In this case OrdersService should expose event OrdersUpdated and ViewModel can subscribe for this event and in trigger PropertyChanged events for affected properties.
Let's consider some case as an example, for instance Order price has been changed. Who is in charge of this changes? Is this done via UI by an user?
You can find here an interesting post written by me few days ago and it talks exactly about this problem (and its solution...)
You might implement "accumulator" properties which store the sum of values in a collection of objects. Listen for changes to those values and update the accumulator appropriately.
(BTW - I forgot to mention that this one is really just for situations where the value is expensive to calculate. Otherwise Sll's answer is definitely the way to go.)
Something like this, with the boring stuff left out:
class BasketOfGoods : INotifyPropertyChanged
{
ObservableCollection<Good> contents = new ObservableCollection<Good>();
public decimal Total
{
get { /* getter code */ }
set { /*setter code */ }
}
public BasketOfGoods()
{
contents.CollectionChanged += contents_CollectionChanged;
}
void contents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var newGood in e.NewItems) ((Good)newGood).PropertyChanged += BasketOfGoods_PropertyChanged;
}
void BasketOfGoods_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Price") Total = contents.Select(x => x.Price).Sum();
}
}
class Good : INotifyPropertyChanged
{
public decimal Price
{
{
get { /* getter code */ }
set { /*setter code */ }
}
}
I think my latest WPF endeavor MadProps handles this scenario pretty well. Take a look at this example master-detail scenario. As long as there a path from the Item being edited to the VM (for example, VM.TheCustomer.SelectedOrder.SelectedItem or simply VM.SelectedItem), the PropChanged event will propogate up to the VM and you can write:
public readonly IProp<Customer> TheCustomer;
public readonly IProp<double> TotalPrice;
protected override void OnPropChanged(PropChangedEventArgs args)
{
if (args.Prop.IsAny(TheCustomer, Item.Schema.Price))
{
TotalPrice.Value = TheCustomer.Value.Orders
.Sum(order => order.Items.Sum(item => item.Price.Value));
}
}
Regarding adding and removing Items or Orders, I would just put an explicit update in the ICommand code, something like VM.UpdateTotalPrice();. Managing subscriptions and unsubscriptions to ObservableCollections and the items in them can be tricky.

Categories