Does an "Atomic Move" operation exist for C# collections? - c#

Lets say I have a ViewModel in a WPF application with an ObservableCollection. This collection is synchronized with a Model collection so that updating either one updates the other. The model has a business rule that states it must contain 2 Thing's at all times, but the order in the collection can be changed. The user will see a list that has two Thing's in it, and has buttons they can click to move a Thing up or down. The model class subscribes to it's collection's change handler and throws an exception if there are ever less than 2 Things in the collection.
Something like this (very simplified, edge cases not handled etc)
public class ViewModel
{
private Model _model;
public ObservableCollection<Thing> Things { get; set; }
public Thing SelectedThing { get; set; }
public ViewModel(Model model)
{
_model = model;
Things = new ObservableCollection<Thing>(_model.Things);
// Some other code that ensures the model and viewmodel collections stay in sync
// ...
}
public void MoveUp()
{
var selected = SelectedThing;
int i = Things.IndexOf(selected);
Things.Remove(selected); // Throws exception
Things.Insert(i - 1, selected);
}
}
public class Model
{
public ObservableCollection<Thing> Things { get; set; }
public Model()
{
Things = new ObservableCollection
{
new Thing(),
new Thing()
};
Things.CollectionChanged += CollectionChangedHandler;
}
private void CollectionChangedHandler(object sender, CollectionChangedEventArgs e)
{
if(e.Action == CollectionChangedAction.Remove)
{
if(Things.Count < 2)
{
throw new YouCantDoThatException();
}
}
}
}
Basically, when a user clicks "Move Up", the selected item is temporarily removed from the collection and inserted above where it was before. This is an invalid state for the model though, and I can't change the model because it's an API for a lot of other things. Is there a way in C# to do an "Atomic Move" operation that will allow me to move the item without first removing it?

I see two options:
Use the "Move" function provided by ObservableCollection. The disadvantage is that your code is in a state with just one item (albeit for a very short amount of time), but your exception will not throw since CollectionChanged isn't raised until the move is complete.
Reverse your logic, and perform the insert before the remove. You could do this with the InsertItem(index, item) function, followed by the RemoveAt(index) command.
In general, there is no such thing as an "Atomic" move because of how "swap" operations work. You need to assign the "first" value to a temp value, assign the second to the first, and then assign the temp value to the second. Certain assembly operations can make this "Atomic" in the purest sense, but that won't help with NotifyCollectionChanged.
As an aside, you can derive from ObservableCollection and implement your own move algorithm, which you shouldn't need to do unless you need Move to have additional functionality. (MSDN).

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.

Create two-way relationship list properties on class (Employee-Job)

Currently I have two classes, one is Employee, the other is Job.
Job has a binding list of Employees and both are a subclass of INotifyPropertyChanged
At this stage, Employee also has a binding list of Jobs.
Each time I create a job, I add employee's to it. Then for those employees I add the jobs.
This is where things go pear shaped, the situation becomes infinitely recursive.
It seems wrong for each object to have a binding list of their occupancy. Perhaps I should just pass some sort of reference, Like in the case of the Jobs list of employees I could pass their name, or vice versa.
Any suggestions are welcome.
here is my current code: (NOTE: This version is attempting to using AddingNew events. Which I think I'm doing incorrectly)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Assignment_3
{
public class Employee : INotifyPropertyChanged
{
static readonly object _object = new object();
private string name_;
private string returnString_;
private Occupation occupation_;
private decimal rate_;
private BindingList<Job> employeeJobs_;
public event AddingNewEventHandler PropertyChanged;
public BindingList<Job> employeeJobs
{
get
{
return employeeJobs_;
}
set
{
employeeJobs_ = value;
makeJobList();
}
}
public void makeJobList()
{
//PropertyChanged(jobList, new PropertyChangedEventArgs("jobList"));
returnString_ = "";
foreach (Job currentJob in employeeJobs_)
returnString_ += currentJob.jobNumber.ToString() + " ,";
}
public Employee(BindingList<Job> employeeJobs, string name, Occupation occupation, decimal rate)
{
employeeJobs_ = employeeJobs;
employeeJobs_.AddingNew += new AddingNewEventHandler(employeeJobs__ListChanged);
name_ = name;
occupation_ = occupation;
rate_ = rate;
makeJobList();
}
private void employeeJobs__ListChanged(object sender, AddingNewEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
makeJobList();
}
public string name
{
get
{
return name_;
}
set
{
name_ = value;
}
}
public Occupation occupation
{
get
{
return occupation_;
}
set
{
occupation_ = value;
}
}
public decimal rate
{
get
{
return rate_;
}
set
{
rate_ = value;
}
}
public string jobList
{
get
{
makeJobList();
return returnString_;
}
}
}
}
The Job code is pretty much the same.
private void createNewEmployeeButton__Click(object sender, EventArgs e)
{
BindingList<Job> selectedJobs = ExtensionMethods.MakeBinding<Job>(jobsForNewEmployeeBox_.SelectedRows);
MessageBox.Show(selectedJobs.Count.ToString());
Employee newJoe = new Employee(selectedJobs, nameTextBox.Text, (Occupation)occupationComboBox.SelectedItem, rateNumericUpDown.Value);
foreach (Job job in selectedJobs)
{
job.employees.Add(newJoe);
job.makeEmployeeList();
}
employeeList_.Add(newJoe);
this.Close();
}
Why don't you factor out the relationship to its own class? Instead of having an instance of a list of Employees in the Job and vice versa, you'd have only one list. This one list can never go out of sync - a problem that you inevitably have when you have separate lists on all Jobs and Employees objects.
EmployeeJobAssignment class
This class contains a reference to an Employee and a Job object and depicts the relationship between them.
EmployeeJobAssignmentCollection class
This class publishes an ObservableList<EmployeeJobAssignment> as a property and also offers methods that allow to change the registered relationship, i.e.:
SetEmployeeJobs(employee, collection of jobs): removes all assignments for an employee and adds the new assignments.
SetJobEmployees(job, collection of employees): removes all assignments for a job and adds the new assignments.
Of course, you can define other methods so like AddEmployeeToJob that only asserts that a specific relationship is present and adds it if required.
The ObservableList<EmployeeAssignment> that is published by the class asserts that other classes can register to be notified when changes to the relationships are made.
Employee/Job classes
When you instantiate an Employee or a Job object, you hand it a reference of the EmployeeJobAssignmentCollection object. The newly instantiated objects subscribe to the CollectionChanged event of the ObservableCollection<EmployeeAssignment>. This way, they are notified abount changes.
The classes keep their collection properties of Jobs or Employees respectively and ask the EmployeeAssignmentCollection to return the relevant objects based on their own id.
Once the object is notified about a change in the relations, they also signal that there was a change in their collection of Jobs or Employees. You might want to implement a mechanism that verifies whether the change is relevant to a specific Employee or Job in order to avoid unnecessary events.
UPDATE: In order to signal the change to the bindings of Jobs/Employees, the easy way would be to raise a PropertyChanged event for the collection properties. As an alternative, you could also return a collection that implements INotifyCollectionChanged. This can be an ObservableCollection<Job> or ObservableCollection<Employee> respectively. Downside is, that you'd need to sync these collections whenever a change in the EmployeejobAssignmentCollection occurs. You can also implement a "fake" collection that implements INotifyCollectionChanged and passes through the Jobs/Employees based on the contents of the EmployeeJobAssignmentCollection that are relevant for an Employee or Job respectively. The effort for this would be higher as you'd need to be able to discern whether items have been added or removed. END OF UPDATE
If the classes need to change the Jobs or Employees, they call the SetEmployeeJobs or SetJobEmployees methods. The related objects are notified through the events. As they also rely on the EmployeeJobAssignmentCollection, they do not need to change a list and by that trigger CollectionChanged events by themselves.
Also you need to make sure that the Jobs and Employees unregister their handlers by implementing IDisposable.
Putting it all together
When you set up your data, you also create and set up the EmployeeAssignmentCollection. It should at least contain the relationships of the relevant Jobs and Employees.
Next, you create the instances of Jobs and Employees and provide the EmployeeAssignmentCollection.
Use the objects as before.
Dispose of the objects as soon as you don't need them anymore.
Hope this rough outline of the model helps. If not, let me know in the comments.

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.

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.

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