I was just after peoples opinion on when the best time to save an object (or collection of objects) is. I appreciate that it can be completely dependent on the situation that you are in but here is my situation.
I have a collection of objects "MyCollection" in a grid. You can open each object "MyObject" in an editor dialogue by double clicking on the grid. Selecting "Cancel" on the dialogue will back out any changes you have made, but should selecting "ok" commit those changes back to the database, or should they commit the changes on that object back to the collection and have a save method that iterates through the collection and saves all changed objects?
If i have an object "MyParentObject", that contains a collection of childen "MyChildObjectCollection", none of the changes made to each "MyChildObject" would be commited to the database until the "MyParentObject" was saved - this makes sense. However in my current situation, none of the objects in the collection are linked, therefore should the "Ok" on the dialogue commit the changes to the database?
Appreciate any opinions on this.
Thanks
Generally it should save when the user thinks it is saving. In this case, yes, OK should save the objects. While I'm sure there are exceptions, I've never run into a situation where a user expected something not to be permanent once they clicked "OK", unless you also have a separate save button elsewhere on the same screen.
It really depends on your and the users needs, but as a user I would assume that it will be persisted. To make everything much more easier for the user (and more complicated for you) you should provide an undo functionality.
If the lower level dialog does the saving, I'd give it OK / Cancel buttons and just have a Close button on the higher level one (with additions / deletions being applied + saved as they're done), otherwise OK / Cancel buttons on both levels would be my preferred approach - with a cancel at either level 'doing the right thing'.
I've always been an advocate of the "dumb presentation layer" approach; So you'd have a Thing class that is editable in a ThingEditor - the ThingEditor merely reports whether the end user selected accept or cancel - the opening presentation object can make the decision as to when to persist or not. By having a dumb editor, differing consumers of the editor can use it in different ways, so...
Along these lines (and to agree with Karussel's answer) I would say that editing a topmost object (even if it is in a collection) is typically when an end user/customer might expect the save to be committed, so do it then; But for when you're editing sub objects in collections, I'd not persist those changes until the parent is requested to be persisted.
Related
I have a C1DataGrid with one column and I want to be able to validate the data when I commit a new row in the grid.
So far I tried to throw an exception in the setter of this property.
This validates the data while I am typing it in the text box correctly (throws an exception), but I am still able to commit the new row.
Furthermore I would like to only do the validation when I commit my new row and not after every new character I write.
Could someone show me how to do it? Many thanks!
You should implement inotifydataerrorinfo
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifydataerrorinfo?view=net-7.0
The easy way to do that is use the community mvvm toolkit
Inherit the viewmodel you're using for each item from Observablevalidator
Add your validation attribute(s), or custom validation
https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/observablevalidator
You could then check IsValid or HasErrors in the CommittingEdit event and stop it committing
https://www.grapecity.com/componentone/docs/wpf/online-datagrid/C1.WPF.DataGrid.4.5.2~C1.WPF.DataGrid.C1DataGrid~CommittingEdit_EV.html
The datagrid itself might automatically check HasErrors, I'm not familiar with C1Datagrid
Bear in mind that what inotifydataerrorinfo is doing is telling you your viewmodel has bad data in it. You then have to do something about it. Revert the change from a cached version or something.
It is because of this that I would usually keep any "original" OK data.
Have the user edit a copy of any item separately from that and then only replace that original item ( or add a new one to a collection ) if it is definitely valid.
With a datagrids where the user can just edit like it's excel, you're better stopping them actually entering any bad data.
I only use this for quite simplistic scenarios like only entering integers or some such. The way I handle that is with an attached behavior which will essentially just not let the user type or paste invalid data in. There are obvious inherent limitations to this approach.
Another thing to consider is a binding ValidationRule.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-binding-validation?view=netframeworkdesktop-4.8
These work as the user enters and will stop invalid data transferring to the viewmodel property. You still have bad data in the datagrid though. So the user types bad stuff, the cell should respond and turn red or whatever. But you still have your bad stuff there in your view.
I'm using an TreeListView (a sub type of ObjectListView) in my current project. Each item in the list is given an icon, but the icon my vary depending on the state of the item. For example if the item is readonly I want to use an icon with a little lock symbol.
When the items are first added to the TreeListView the icons are show correctly, yet later when the state of an item changes the icons are not updating. How do I force the control to regenerate all the icons?
Since the icon is gotten when the ImageGetter for the first column is called, and since that is only called typically when the rows regenerate, then it seems like you could simply call BuildList and force all rows to rebuild. This might be expensive, though, depending on the complexity and number of rows.
A better solution, if possible, would be to call RefreshItem or RefreshObject as soon as you know data has changed. This requires you to know either the specific OLVListItem (rows) that need their icons updated, or the underlying model objects that changed (and thus would cause the icon to differ were its associated rows rebuilt).
How is data binding in C# WinForms supposed to work when you have a Save button? I don't want the data updated until I press Save!
I have two forms (list and detail) backed by a BindingList<T> collection and my custom object from that collection, respectively. I can bind each form to the list or object appropriately. However, any changes made in the detail form are immediately reflected in the list form - I don't want to save the changes and update the details shown in the list until the Save button is pressed.
Is data binding designed to support this? Is there a common pattern for doing so?
Whichever way I look at it, binding doesn't seem to be able to support this scenario. I've considered the following:
Pass a clone of the object to the detail form, but then I have to reconcile the changes on Save - changes may have been made to the copy in the list in the meantime.
Implementing IEditableObject and calling EndEdit on save almost works as I can prevent the list being notified of the changes made until Save is pressed, but if something else causes a refresh the list is updated with the interim data.
I'm currently left with dispensing with data binding in my detail view, and doing it all manually. Which is rather annoying.
Data binding really wasn't designed for this kind of thing, unfortunately.
The best solution we've found here is to edit object clones, then reconcile the changes, as you mentioned. You may want to come up with a custom collection and/or object interface that helps you manage this type of thing.
Even if the cloning method is slightly more work up front, it's going to be wayyyy less complicated and frustrating than using IEditableObject trying to catch all the possible events that update the data. Not only that, it's a more straightforward approach, and you won't end up with spaghetti code.
If you are set on using a binding list, your best bet would be to implement IBindingList to create the functionality that you desire. It may also be possible to pull this off by simply inheriting from BindingList and overriding the appropriate methods to change the binding list's behavior.
http://msdn.microsoft.com/en-us/library/system.componentmodel.ibindinglist.aspx
If you are not set on using a binding list, it is probably best to do the data manipulations manually based off of the control's events.
Best of luck.
I have application full of various controls databound to my classes. I would like to ask user "You are closing application and you made some changes. Do you want to save your changes?". For this I need to recognize that user made any changes.
How to catch user made changes in databound controls? Is textBoxXXX_TextChanged the only way to do this?
Thanks in advance for all your answers.
It depends on the datasource; for example DataTable and DataSet sources contain the GetChanges() methods which allow you to easily see if rows have been added/removed/modified. Other data sources will have their own implementations, if any. If there is no implementation then it's up to you to determine how to check for those changes.
In any event this is something you should do at the data-level, not the UI (by watching for "changed" events). Watching events doesn't scale beyond a couple controls and maintenance can be iffy.
Update: Not sure why I didn't think of it, but a second option is to add a BindingSource to your UI object and use it as a databinding proxy (your UI controls databind to the BindingSource and the BindingSource binds to the real datasource). It provides a better approach than handling all your individual "Control_Changed" events, and requiring rework of your other layers (esp. if they aren't custom data-types).
You need to provide custom logic for that, there's not really an automatic way of doing this. As I see it there are several options:
At the start of the editing, save a copy of the original data object, and when you need to check, compare the current object with the saved one. The comparison can be custom (field by field) or semi-automatic by use of serialization (compare the serialized forms) - if it is serializable.
In each of your data object's property set accessors, test for a change in value and mark the object as 'dirty'.
As been discussed, there are many ways to do this depending on how granular you want to get.
A relatively easy way using client side javascript would be to do something like the following:
Hook into the onchange events of the form elements. You could do this dynamically on page load using javascript/DOM.
When the onchange error handler is called, you could set a page level variable: pageHasChanged = true;
Hook into the page's beforeonunload event (occurs when the user tries to navigate away from the page) and check the pageHasChanged variable to see if any changes were made. If changes were made you could alert the user.
This doesn't give you the detail of what changed, but would be fairly easy to modify to track which form elements changed.
I'm using the DevExpress Xtra TreeList control to display a hierarchical set of questions and responses - think of a complex survey form, containing sections, subsections and a variety of questions.
The form is working in unbound mode, with no dataset nor any databinding.
As a part of the information displayed for each question, some background is obtained by calling a webservice on a background thread; results from these webservice calls are then used to populate the TreeList via calls to TreeListNode.SetValue().
Presently, these calls to SetValue() are causing any active editors to close, discarding the user's current input - a very user unfriendly experience.
How can I ensure the user's editing process is unaffected by these background updates?
The only similar questions I've found have been on the DevExpress forums, where the suggestion is a forced commit of the user's entries, which avoids loss of data but otherwise does nothing to fix a poor user experience. Since these all dated from 2007, I'm hoping the situation has now changed. Is it possible to update nodes without altering the state of the users own activity?
Background: A typical screen would have 500+ rows, with the webservice call for for each row taking around 0.6s to return. Forcibly committing or cancelling the user's actions every 0.6s is simply not acceptable, and forcing users to wait for processing to complete (>5minutes) before they can make any changes is equally poor.
Short answer: You can't
Changing a value in the TreeList will result in any current user editing being cancelled, reguardless of the use of Binding or not.
Official response from DevExpress:
Unfortunately, there is no way to prevent an active editor from being closed when the data source values are changed. It is impossible to achieve because the TreeList should be always synchronized with the underlying data. This functionality is implemented via the IBindingList interface in a usual manner. In response to the "change" notification the treeList must refresh itself and, as a result, reload data. This causes the active editing state to be reset.
However, there are several different ways to introduce required functionality. For example, you can create a separate form that will contain a set of editors that will provide the ability to edit a specific object directly. Another possible way to achieve this goal is to create some intermediate storage that will cache all changes. The synchronization with the TreeList's data source should be performed by user request.
Well one way i achieved to do it is by ShowEditor() after a small time delay after an event which does an update (say 100 miliseconds). I would get a problem because of an notifypropertychanged update and i would hook on FocusedNodeChanged. For instance:
FocusedNodeChanged += OnNodeChanged;
private void OnNodeChanged(object s, FocusedNodeChangedEventArgs e)
{
_delayer.Start();
}
private void _delayer_Tick(object sender, EventArgs e)
{
ShowEditor();
_delayer.Stop();
}
_delayer is a Timer class with tick event. A bit crude but it does the trick.