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.
Related
I frequently have the following task:
I have a collection of objects (f.e. Customers) and want to provide the user with an editor for these objects. Typically I have some list control on the left side of the editor and a form on the right side. The form displays the properties of the object that is currently selected on the left side.
Regarding the confirmation of any changes, there are at least two strategies:
make the editor a modal dialog window and give it OK/Cancel buttons. On OK save all changes for all objects
give the editor a Save button above or below the form on the right side that would allow the user to confirm changes to the currently selected object.
My question is about the second strategy, implemented as an MVVM application with WPF:
I would like to give my user a feedback that there are unsaved changes. Applications like text editors often solve this by enabling the Save button when any changes occurred and disabling it again once the user pressed it to confirm her/his changes.
If I understand correctly I would have to monitor changes to any bound properties in my form (backed by a model class). Usually my model classes use auto properties (no explicit getters and setters). Do I have to write explicit getters for all my properties to enable the Save button when anything changed, or is there a smarter way to achieve this?
Following the MVVM pattern, your ViewModels should implement INotifyPropertyChanged interface, than you can easily subscribe to PropertyChanged event and monitor properties changes
If you don't want to write INPC aware getters and setters in your model classes, then another way is to write a equality compare method instead, and then have your save command availability callback call into that to compare the "live" object with the edited one. I'm assuming you have a cloned object that is being edited in order to rollback if the user chooses not to save.
WPF will call it automatically as the user clicks around and types, or you can give it a hint with CommandManager.InvalidateRequerySuggested()
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.
I was working on a c# project using a DataGridView and ran into an issue.
Basically, I have a grid that gets updated from any number of places and has a button column.
I want to capture button clicks (of course) and do something which involves the other cells from that button's row. As far as I know, the only way to associate a click with a row of the grid is via the RowIndex of the EventArgs.
What I'm worried about is that the grid may change between the user clicking and the event being delivered, causing an incorrect row to look like it was clicked.
Can this happen or am I being paranoid? If possible, is there anyway I can bind a reference to either the button or the row inside the eventargs so that I can differentiate the originating row even if its index has since changed?
GUI controls in WinForms are 'tied' to the UI thread.
When you click the row, grid implementation handles this and as a part of the click handling raises an event - all on the same UI thread. Even if other threads change the grid content, this has to be serialized to the UI thread (typically with posting Windows messages in the background, or BeginInvoke in WinForms terminology - not exactly the same, but close).
This means that even if there are concurrent changes, if your click is already registered, the event will not be interrupted by UI update.
Note, however, that background thread could change the data object to which the row is bound while your handler runs, or between click occurred and event was handled. Still, it makes no difference to you, as grid UI update would need to be serialized and would still happen after your event handler.
I have a TabControl with two tabs. One tab has a list of stores and the other has a list of employees. On the store tab I have a button that displays all employees of the store; to do that, I want to switch to the other tab and invoke a showEmployeesFromStore(store_id store) method from that tab's User Control. How would I do that?
You've got the wrong mental model. Just because the user control isn't visible on the TabControl doesn't mean that the code is invisible as well. Just call the control's method in your code, it needs to be public of course. Then change the tab control's SelectedIndex property to switch the active tab page.
The button should not be part of the 1st user control. Actually it is better not to use a button but to just trigger an event when the user selects another store.
I would expose an event on the store user control for SelectedStoreChanged or something to that effect. Pass back the newly selected store_id in the event delegate.
Subscribe to that event with your form. When the event fires, it is the form's job to decide with to do with that information. In this case, have it pull out the store_id from the store UserControl's SelectedStoreChanged event and pass it in to EmployeeUserControl.showEmployeesFromStore(store_id store)
Keep your controls ignorant of each other. Let the owner of the controls decide how to react to whatever events are raised by the controls. You'll sleep better with dreams of increased usability, better separation of subject areas, and more fewer working weekends due to untangling odd control flow... ;o)
Just realized I missed a detail. The button you're talking about should be on the form itself and not any of the user controls, assuming you don't want it to just update in real time using eventing described above. On button click, the form should go check StoreUserControl.SelectedStoreID() and pass the result to EmployeeUserControl.showEmployeesFromStore()
I have a form with several components, like TextBox and ComboBox, and I need to know when click in the out button if there was any changes in the form. Is there a way to do this?
You could create a generic change event handler which sets a flag on change, and then assign all the controls' Change events to it.
This could probably be done pretty easily by looping through all of your controls onload.
You could loop through all controls but this would have to be recursive because a control can contain controls, e.g. (no null checks for brevity):
private void IterateOverControls( Control parent )
{
ProcessControl( parent );
foreach( Control control in parent.Controls )
IterateOverControls( control );
}
In ProcessControl you could hook up event handlers to handle OnEnter (to store the state) and OnLeave (to check the current state against the stored state). You'd need to unhook all the event handlers when disposing. Also, the code to store check the state would have to change for different control types, e.g. TextBox would be the Text property, but a radio button would be an index, etc. Obviously this becomes simpler if you can compare form state to your underlying data store state, in which case you can just make the comparison on each OnLeave event.
One thing also to consider is do you need to track real changes? For example, I have 2 radio buttons: A and B. I check B (a change), so the out button or whatever has its Enabled property changes. I then click on A (i.e. back to my original state). Do you need to revert the button at that point?
This is why you should look towards a model view controller approach :)
The easiest way to do this would be to simply use a variable on the form named something like "IsChanged." Set it false when the form is initially displayed, and set it true if they make any changes.
Alternately, you could record the values of everything when the form is displayed, and when they finish, check the current values against the old ones to see if anything changed.
If this is already nearly finished, and you need something quick it's probably going to be easier to just always assume that something has changed, then in your update logic afterwards (whatever it's doing) don't update stuff that is still the same.
As someone else mentioned, it's very possible for someone to change something, then change it back. What would you want to do in that case? You won't be able to maintain a proper dirty state of the form without a fair bit of additional work.. this is something that you need to plan for before you start, really.