Lainkes 0 Posted June 15 (edited) Good evening, I have a program that shows a grid with records (client data). I was wondering what the best practices is to use this form for adding a new client or editing a new client. I was thinking to use a boolean flag (new_client := True or false). If the flag is True, I know that the form is for adding a new client, so I use the append option. When False, I know it's just for editing the data. Or do I use 2 different forms for this purpose? Thanks for your feedback. Greetings Lainkes Edited June 15 by Lainkes Share this post Link to post
Remy Lebeau 1436 Posted June 15 (edited) You are editing a client, period. It shouldn't matter whether it is a new client or an existing client. So, using a simple flag (or other indicator telling you the client's state) to adjust the Form's behavior accordingly should suffice, no need to duplicate the work unnecessarily. Edited June 15 by Remy Lebeau 1 Share this post Link to post
JonRobertson 72 Posted June 15 (edited) I wouldn't use a separate flag unless necessary. If you are using a TDataSet descendent, you can look at the State property to determine whether the record is being inserted (State = dsInsert) or edited (State = dsEdit). If it is necessary to call Post in code, you can use if ds.State in dsEditModes to determine if the current record needs to be posted. Edited June 15 by JonRobertson 2 Share this post Link to post
David Schwartz 430 Posted June 17 I've generally found it's easier to make one form to ADD a NEW item, and another to VIEW / EDIT the item (either object instances or DB records). But it can depend what's in the form. I've often tried using one for all three, but I often end up with so much code that does one thing for Adding and another for Viewing / Editing that it's just too much of a headache. One way around that is to Insert a new record with default values, then open it in Edit mode. But the problem with that is if the user Cancels out of the form, then you have to be sure to delete the added record. If there's a possibility that someone might issue a query that attempts to access that record, then you'll need a field that maintains a state flag saying it's temporary. If something throws an exception and blows out of the Edit process but doesn't delete the temp item, that can create all sorts of headaches. The areas that cause trouble are fields that are references to chunks of data from elsewhere, fields that contain indices that point to items in a lookup table, along with field validation read errors, among others. To address some of these disparities, adding a new record can require data fields that are flagged as "required" but their data hasn't been selected yet by the user. You have to treat those fields as "required but empty" which is technically an error condition. So you have to put code around that to guard it from throwing an error while tabbing into or out of such a field, because for NEW records it's NOT an error. See my point? Using DB-aware fields figures out a lot of this stuff. But to make it work, you need to add field validations to check this stuff when they click Post on a NEW form. But wait ... those field validation routines now have to figure out if it's Add or View or Edit as well and take appropriate action. So you end up simply shifting that logic from one place to another place. It would be nice to bury it inside of a class, making the objects self-validating, but that's a whole nuther can of worms! Using one form for Adding and another one for Viewing / Editing eliminates all of that selection logic and is a lot easier to maintain. It's also simpler than dealing with inserting a temp object / record first then Editing it because you don't have to deal with deleting the temp if the user cancels the edits or an exception is raised. BTW, the difference between View and Edit is that View sets all of the fields to read-only, and going into Edit mode turns off all of the ones that the user can edit (ie, applies permissions). A copy of the data needs to be kept when you go into Edit mode so you can compare which fields changed and only update them, or restore the field values if they Cancel the Edit after making some changes. Again, DB-Aware controls can do this automatically, but using lists of objects this way may not provide the same logic. Share this post Link to post
JonRobertson 72 Posted June 17 (edited) That sounds like much more code than necessary to me. I have a base form with a TDataSet field named StateDataSet and a virtual method called UpdateUIState. Each derived form assigns StateDataSet during FormCreate and overrides UpdateUIState to update the UI based on various conditions, such as TDataSet.State and user security for that form/data. Both the base form and the derived user form is "aware" of the record operation (view, insert, edit) by looking at StateDataSet.State. Every form has a ValidateForm method to ensure that required fields are provided and any referring data is valid before the record is posted. I avoid writing anything to the database unnecessarily. Adding a row temporarily that will be removed if the edit is canceled is wasteful network and disk I/O as well as increasing db index and page fragmentation. Some customers now use a "cloud hosted" database server, so database read/writes also have extra latency. Doing as much as possible in memory is a huge performance gain. I find it much easier to maintain a single form (per table/record). I don't have to take the time to update two or three forms when changes are needed and ensure that the implementation is consistent across multiple forms/units. FWIW, my data edit forms have a lot of common functionality that is implemented in the base form, not just StateDataSet and UpdateUIState. Edited June 17 by JonRobertson 2 Share this post Link to post
David Schwartz 430 Posted July 8 (edited) On 6/17/2024 at 5:54 AM, JonRobertson said: That sounds like much more code than necessary to me. I have a base form with a TDataSet field named StateDataSet and a virtual method called UpdateUIState... Yeah, it often is. What if you're not using DB-aware controls? The OP didn't sound like he was. As I said, the DB-aware controls add a lot of buffering and logic to simplify this. If you just have any random form that's got edits and grids and checkboxes and whatnot on it, and they are NOT DB-aware controls, then what do you do? Like, say, you have a form that displays data returned from an HTTP GET request as a JSON packet that consists of an array of objects, and you want to scroll through the objects as if they're records in a table? I'm genuinely curious. This wide disparity between forms that are used for interacting with DBs that have DB-aware controls on them vs. those that don't -- because you're not using DB -- has always perplexed me with Delphi. I've seen at least a half-dozen different solutions used over the years for the latter case, none of which were particularly elegant. Sometimes I asked the developer why he didn't just use an in-memory table and DB-aware controls, and the excuse was usually that it "added too much overhead to the app". OTOH, there have been numerous situations where I got rid of a bunch of code that did this and replaced it with an in-memory table and DB-aware controls, and the performance actually improved. Edited July 8 by David Schwartz Share this post Link to post
Tom Chamberlain 47 Posted July 8 Depends on the complexity of the data needed to create the record(s). The our app can have many child records for a parent, but requires at least one child, so we use a 'wizard' type screen for creating these initial records. Our app is 25+yrs old, we have a mix of DB-aware for the the oldest forms, client datasets and even TMS string grids have been used to store data, but for 95% of all your 250+ tables we use one form for all create, view and edit of data. If a table contains multiple records we use a grid of some type with a panel that we show/hide depending on the status of the selected record to create/edit the data. We force the users to click an Edit button to change 99% of any data and then an Save or Cancel button. Share this post Link to post
JonRobertson 72 Posted July 8 1 hour ago, David Schwartz said: If you just have any random form that's got edits and grids and checkboxes and whatnot on it, and they are NOT DB-aware controls, then what do you do? The data shown and potentially edited on a form comes from somewhere. So I populate the controls from the source and update if/when the user makes changes. If this is in the app with the base form and UpdateUIState implementation I described, I am still able to override methods like UpdateUIState for that form, that code doesn't care about a dataset. Another option is to use LiveBinding, although I've avoided LiveBinding myself. 1 hour ago, David Schwartz said: This wide disparity between forms that are used for interacting with DBs that have DB-aware controls on them vs. those that don't -- because you're not using DB -- has always perplexed me with Delphi. Just with Delphi? Do you use other languages with frameworks that provide a cleaner solution? 22 minutes ago, Tom Chamberlain said: We force the users to click an Edit button to change 99% of any data and then an Save or Cancel button. The projects that I currently maintain do this as well. Although I am not a fan of requiring extra mouse clicks. It is easy to detect once a user has changed data and require the user to click Save or Cancel before closing the form. Share this post Link to post
corneliusdavid 220 Posted July 8 On 6/17/2024 at 5:54 AM, JonRobertson said: That sounds like much more code than necessary to me. I totally agree. I don't think I have EVER created two forms to work with the same data--seems like tremendous unnecessary duplication of effort plus a maintenance nightmare to remember to have to make changes in two places any time you need to add or change a field or label or some other aspect of the form. And, as mentioned, it's as easy as checking the State property of the associated dataset or a flag variable if you're using non-data-aware controls. 1 hour ago, Tom Chamberlain said: use a 'wizard' type screen for creating these initial records This would be the one exception I can imagine and makes sense to tightly control some required fields and help the user fill out the data properly but that's really a different purpose, sort of like creating a report or grid to view records in addition to the form that created them is also a different purpose. Share this post Link to post
David Schwartz 430 Posted July 9 (edited) 21 hours ago, JonRobertson said: 23 hours ago, David Schwartz said: This wide disparity between forms that are used for interacting with DBs that have DB-aware controls on them vs. those that don't -- because you're not using DB -- has always perplexed me with Delphi. Just with Delphi? Do you use other languages with frameworks that provide a cleaner solution? I've only used Delphi. Prior to that I spent most of 15+ years doing embedded systems programming, and they never used DBs for performance reasons. It was pretty low-level C and C++. Frameworks were fairly primitive back then. And there were lots of Delphi apps I worked on that were essentially embedded systems, like stand-alone kiosks, that had content created externally and manually installed -- no DBs were used here either. Edited July 9 by David Schwartz Share this post Link to post