I have a WinForms Project with a DataGridView, which is data-bound to a collection of objects.
One column contains the serial-number of a sensor, which the user can select from a list. If the user selects a serial-number which is already used in another item, I want so swap some (but not all) of the fields between the items.
This breaks the normal data-binding mechanism. Although the user has only edited one item, I will be actually be modifying two items.
What is the best way to handle this?
The easiest way appears to be to handle it in the CellValidating event. This definitely occurs before the data-bound item is updated (which is convenient in this case). Other possible events (CellEndEdit, CellValueChanged, CellValidated) occur later.
However, I don't like this, because CellValidating should be used for validation and not for other random stuff.
Alternatively, I could implement a mechanism for the item to call a function in its parent object and handle everything in the model, independent of the UI. That seems like a better structure (MVVM-like), but way more complicated.
Related
I have a DataGrid bound via a ListCollectionView to an ObservableCollection of Objects with type Job, say. Each cell in the DataGrid contains a UserControl which has a dependency property named Job which is bound to the DataGridRow.DataContext (using Mode=TwoWay). Everything displays correctly.
The problem is that I have a background process which mutates objects referenced by the Job object and those get displayed by the UserControl. Obviously, Job does not change so the view does not change.
How can I get the user controls in each cell to update themselves with the new data?
With lists there are 3 kinds of ChangeNotification you have to take care of:
the one for each property of the Job Class.
the one if elements are added or removed from the collection. That is the only part ObservableCollection takes care off.
the one on the property exposing the list/CollectionView/whatever. ObservableCollection lacks a AddRange function so on large operations (like a rebuild of the list) it would spam the UI with changes. The solution is to build a new list in the background, then expose it in this property.
One particular issue might be the Background Process too, if it is Multithreading. GUI's are protected against being written from the wrong Thread. But threads are also notoriously good at swallowing Exceptions. Usually you need to write a faulty Catch, but they do it for free. As a result, your might run int oa CrossThread exception and never notice it.
For a better answer, we need some actuall code. Like teh job class, the BackgroundProcess, etc.
TLDR version...
We're trying to create as basic Panel subclass with an observable Items property. The control then uses those data items to create one or more related child UI objects per data item.
Our first thought naturally was to simply subclass ItemsControl, but that doesn't seem to fit because it uses an ItemContainerGenerator which only generates one 'container' per item whereas again, we need to potentially create several (which aren't containers anyway.) Plus, all the created items have to be direct children on the panel, not held in a container which is why we can't go the route of data templates.
As such, I'm just using a standard Control and I'm trying to find the proper place/event where I should be instantiating/destroying the resulting child UI elements in response to changes in the Items collection.
Now the details...
First things first. If there was something like a ItemMultiContainerGenerator, that would be perfect, but I know of no such thing.
Ok, so simply monitor the collection for changes and put the UI generation in the CollectionChanged event! Right? That was our first guess too. The problem there is for every new 'Add' or 'Remove', we have to spin through all existing controls to 'defrag' certain indexing properties on them (think along the lines of a Grid.Row or ZIndex property) meaning if you add ten items, you run the defrag ten times, not once at the end.
Plus, that change event may come in on a different thread. If you attempted to dispatch to the main thread, your performance takes a nose-dive.
Our other attempt was to use MeasureOverride, since that was called only once in response to an InvalidateMeasure call, regardless of how many children we added or removed. The issues (there are many) with this approach is we lose context of whether something was added or removed, meaning we had to throw away all children and re-add back all new ones making this extremely inefficient. Plus, mucking around with the visual tree or setting bindings could cause the layout pass to execute multiple times since we were changing something that affects the layout, namingly the panels children.
What I'm trying to find is something that happens as part of the overall rendering process (i.e. from the time the control is told its invalid until it renders), but before the Measure/Layout passes are called. That way I can cache the adds/removes in the CollectionChanged event and simply mark the control as invalid, wait for this mystery event, then process the changes en mass, then send off the results to the layout engine and be done with it.
Using Reflector, I've tried to find out where the ItemsControl adds its children to the panel, but I didn't get too far considering the complexity of the control/ItemContainerGenerator pairing.
So where is the best place to create/add UI elements to a control based on data item changes?
I think you're going to have to listen to collection changes manually. There are a few reasons:
Changing your visual children will invalidate your layout. If you change your children in measure or arrange, you will have an infinite loop.
Rendering happens after layout. By changing children after layout you are asking for an infinite loop.
Hopefully you won't have to support any collections other than ObservableCollection, because it is obviously easier to deal with the one type of collection. But, I think you can definitely make a responsive control that will do these things. Here are some tips
Implement a custom Collection that has support for AddRange and RemoveRange (the INotifyCollectionChanged event supports multiple items being added or removed) so you don't have to do the same work 10 times for 10 new items.
In your collection change handler, use the e.AddedItems and e.RemovedItems instead of accessing the underlying collection. This will prevent you from receiving exceptions due to the collection changing while you're enumerating it.
Use BeginInvoke to prevent blocking the producer thread when dispatching back to the UI thread
To address your initialization concerns, implement ISupportInitialize, and use it to suspend your "defrag" process if you have to add or remove multiple items one at a time. WPF will automatically add the Begin/End calls for you when your control is created in XAML.
Derive from FrameworkElement if you don't want a ControlTemplate. Lower overhead.
If this still isn't working because the speed at which your underlying collection changes is too fast, and children are fairly simple, perhaps you should draw them in OnRender
One other option that just occurred to me is that you can schedule all these operations on the Dispatcher, so that if multiple changes happen, you would only need to do it once. Basically, you would store a reference to the operation like so:
private DispatcherOperation pendingDefragOperation;
protected void ScheduleDefrag()
{
if (pendingDefragOperation == null)
{
pendingDefragOperation = Dispatcher.BeginInvoke(
DispatcherPriority.Render, // You may want to play around with this
new Action(Defrag));
}
}
And you would call this on a CollectionChanged. And in your Defrag call, you'd set pendingDefragOperation to null.
I'm writing a tabbed WinForms application that contains multiple DataGridView controls which are bound to BindingSources. The BindingSources, in turn, are bound to BindingLists of business objects. My business objects implement INotifyPropertyChanged.
My TabControl has three TabPages (A, B and C). If my user has made changes to the data in the DataGridView control on tab A and then tries to switch to tab B or C without committing the changes (in this case, writing the data to disk), I need to prompt them to save the changes or lose them.
In order to determine if changes have been made on a given tab, I created "pendingChange" variables for each tab. Each DataGridView control has it's own CellValidating event handler where I validate the data as the user makes changes. At the end of that event handler, if the change has passed all of the validation checks, I set the pendingChange variable for that tab to true. When my user clicks the button to write the changes back to disk, I reset the pendingChange variable. So anytime my user attempts to change to a different tab, I check this variable to determine if there are any changes that need to be saved first.
Now I've realized that a big flaw with this design is that the CellValidating event for the DataGridView control is fired every time a cell loses focus. So now I need to find a different way to track when a cell value has been changed. Is there another event that the DataGridView control exposes that would be better suited to this purpose? Or perhaps there's an event for the BindingList that would help me achieve this functionality? If I have to, I can add a boolean "modified" property to my business object and update that as required. If it's possible to avoid that, I would like to because one of the BindingLists contains 150,000 objects at any given time and iterating through that might not be terribly fast.
BindingSource has Events. E.g. CurrentChanged event occurs when the currently bound item changes.
I have a need to provide as options multiple objects from a particular data set, and populate a list so that an end-user can select, none,some, all, or all + possibly missing data fields (user-input).
I originally planned to extend a System.Windows.Forms.ListView to include a whitespace item that contained a checkmark, then specially handle the case where a user had clicked this blank line item.
I would like the ability to remove these user-input line items if possible. I do not have to use a System.Windows.Forms.ListView, but its design seems to best-fit this particular use.
Is there a control with this functionality already , or an attribute of the System.Windows.Forms.ListView I have missed that may handle these situtations?
---Update---
ListBox is changed to System.Windows.Forms.ListView
DataGrid sounds like it would be a good fit; otherwise I'd suggest looking at 3rd party controls.
I am experimenting with WPF and MVVM in our system. However iam having a problem with keeping things selected in lists using only MVVM ( without doing extra CollectionViews ).
What i currently have is the list
ObservableCollection<ReservationCustomerList> Customers;
And then a property storing the selected Customer
ReservationCustomerList SelectedCustomer;
In my opinion now, when the list reloads (actually from another thread async), the selection should be able to be kept, however this does not happen.
Does someone have a nice clean way of achieving this ?
The way we did it was that we did not replace the collection. We added/removed the entries and updated existing entries if required. This maintains the selection.
You can use LINQ methods like Except to identify items that are new or removed.
In case the reloaded list still contains the last selected item and you want that item to be selected, then you can raise the PropertyChange event for the property SelectedCustomer after your collection gets reloaded.
Please make your sure your viewmodel class implements INotifyPropertyChanged interface.
you can use the ICollectionView to select the entity you want.
ICollectionview view = (ICollectionView)CollectionViewSource.GetDefaultView(this.Customers);
view.MoveCurrentTo(SelectedCustomer);
in your Xaml the itemsControl must have IsSynchronizedWithCurrentItem=true
or if the ItemsControl has a SelectedItem property you can simply bind it to your SelectedCustomer Property.
When you "reload" your collection you basically replace all values in it with new values. Even those that look and feel identical are in fact new items. So how do you want to reference the same item in the list when it is gone? You could certainly use a hack where you determine the item that was selected by its properties and reselect it (i.e. do a LINQ search through the list and return the ID of the matching item, then reselect it). But that would certainly not be using best practices.
You should really only update your collection, that is remove invalid entried and add new entries. If you have a view connected to your collection all the sorting and selecting and whatnot will be done automagically behind the scenes again.
Edit:
var tmp = this.listBox1.SelectedValue;
this._customers.Clear();
this._customers.Add(item1); this._customers.Add(item2);
this._customers.Add(item3); this._customers.Add(item4);
this.listBox1.SelectedValue = tmp;
in the method that does the reset/clear works for me. I.e. that is the code I put into the event handling method called when pressing the refresh button in my sample app. That way you dont even need to keep references to the customer objects as long as you make sure that whatever your ID is is consistent. Other things I have tried, like overwriting the collections ´ClearItems()´ method and overwriting ´Equals()´ and ´GetHashCode()´ didn't work - as I expected.