I have a love/hate relationship with the Developer Express Quantum Grid: I love the features and our users think they’re great. I’m even willing to spend the time required to make them work right — I understand that with such a huge number of features they’re going to be complicated (and boy are they ever!). But every once in a while I come across something that is just hair-pullingly more difficult than it needs to be. The upgrade from v3 to v4 was bound to be difficult, due to the vastly different architecture, but some parts were senselessly so — properties which did the same things had new, but synonymous, names, for example.
Last week we ran into another one.
We have a field which is sometimes ReadOnly and sometimes not ReadOnly. When it is ReadOnly it is set in code based on the value of other fields. Because TCustomClientDataset does not allow code to change a ReadOnly column without temporarily setting ReadOnly FALSE, we have code like this, which is called several times as the user changes the values the ReadOnly column depends upon during editing:
MyField.ReadOnly := FALSE;
try
MyField.AsString := GetMyFieldValue;
finally
MyField.ReadOnly := TRUE;
end;
The VCL’s TDataSet allows this.
However, the code fails if we do nothing more than connect a Developer Express TcxGrid with a TcxGridDBTableView to the same dataset. The reason is that changing the ReadOnly property of the TField causes the datasource to send a deLayoutChanged data event. That shouldn’t be a problem, but when it trickles through the cxGrid it eventually hits TcxDBDataProvider.Freeze, which calls TDataSet.Cancel, tossing out the rest of the user’s changes.
In fairness to Developer Express, the VCL’s TDataSet architecture doesn’t make this easy makes this very painful to implement right. It could be that you got deLayoutChanged because a field became ReadOnly, it could be an entirely different dataset was connected. They only way you, as a component author, have to find out is to exhaustively run through the possibilities and see if it affects you. I know that this is laborious and error-prone as I do it in some of my own controls. The TcxGrid’s data-aware provider doesn’t do this, however. It just reloads unconditionally, whether or not there is any difference in the data.
I’ve emailed Developer Express support about this. They replied promptly, as always. But they don’t have a way to stop this misbehavior. So the only way I have to implement the feature is to either toss the grid — which I don’t want to do — or set the ReadOnly property of the editor differently than the TField, which is non-trivial in our case since we’re creating all of the screens dynamically at runtime. Yuck!
So here’s a plea to component authors: Please, please avoid calling TDataSet.Cancel at all costs. Even the (Borland-provided) TDBGrid’s dgCancelOnExit option (true by default!) seems very wrong to me. The user’s data is the lifeblood of our applications; we take great pains to protect it. And if you absolutely must call Cancel in some situation, then you must also provide a way to prevent that from happening when your users know more about what is going on in their applications than you do.
{ 19 } Comments
We have the same feelings for the QuantumGrid. We have stayed on version 3 because we didn’t want to make the changes required for version 4. Our products have a bunch of base class objects that use the QuantumGrid and I shudder to think what will break by going to V4. Their support has been great and our customers love how the grid works.
Hi Craig,
I share you love/hate feelings for this complicated control. One lesson I’ve learned, when dealing with a "paid contract" situation, always build time in up front to thoroughly test a particular scenario with the Quantum Grid before commiting to a client that wants to use a new feature not yet tried….
-d
i agree with all of you . i spent a lot of my time to use it, damn it’s makes me frustated too, if you in a cantract based project i think you think twice to use this component. may if they provide us with more sample for many type of problem could be better for us, do you think so, huh…?
it’s a very good component (many features and an xp look), but less explanation. so it made me so confused just to use it ….
If you compare to standard DBGrid then you should use GridMode in DevEx Grid - it will behave in the same way - no Cancel during editing. Otherwise DevEx Grid has to reload records on Layout Change and cancel editing.
I’m not comparing them to TDBGrid. I’ve never used that control in an application. I’m comparing them to every other data-aware control I have ever used. I do understand that GridMode — which disables most of the interesting features of the grid — will work around this issue, in much the same manner that decapitation cures acne.
But the point is that the grid doesn’t *have to* reload the records when ReadOnly changes. It just *does.* It does it because the VCL doesn’t make it easy to know why you got a deLayoutChanged, and in *some* cases the grid really does have to reload the records when it gets deLayoutChanged. Just not this one.
I know how it works and tried to make it clear for you, but you do understand this already and seem to answer your own question
So is it frustration about Grid or VCL?
Both, but the only actual bug is with the grid. The VCL makes this difficult to implement when you’re designing a component, but it is possible to do.
Actually, in this particular case it isn’t even that difficult — the grid just shouldn’t call Dataset.Cancel, ever, when the user is in edit mode unless the user has specifically requested to cancel. As an application designer you just don’t toss out user input without being confident that this is what the user wants. But in general handling deLayoutChange correctly is a non-trivial task for component implementors.
I think all other grids you mentioned before worked in "Grid" mode - none of the loaded all records.
When grid receives layout change notification it has to reload data (there is no correct way to check what actually happened). Reloading data is DataSet.First + DataSet.Next until DataSet.EOF, so editing should be canceled anyway because focused record will be changed.
No, I don’t agree with this at all. I am openly appalled at the statement "editing should be cancelled anyway because focused record will be changed" as IMHO it demonstrates a casual disregard for the user’s data. Even if it were necessary to reload unconditionally — and I remain to be convinced that this is the case, why Cancel instead of CheckBrowseMode?
The only way I’ve found in my on controls to correctly respond to deLayoutChanged is to iterate through the list of possible reasons for the message that actually affect me. Has the dataset changed? Has column visibility changed? Etc. Then respond accordingly if so. It’s a big pain, it is true, and that is why I have indicated from the outset that the VCL makes this difficult. But it just isn’t OK to throw out user data because the alternative is difficult. That makes me distrust the control, although lately "RecordIndex out of range" errors are doing a better job of that….
Editing should be cancelled because posting of the data can cause problems. For example data will be invalid - in this case message box should appear and all process should be stoped. Do you want this happen in the middle of you code with locked grid?
Layout change can happen in situation which cannot be controlled from inside the control. Dataset can be used by several controls together and one of them can call this change or operation with dataset can fire it. You can only check the stuff your own control does (there is a SmartRefresh mode in DevEx Grid demonstrating this). You cannot check dataset’s modifications in most cases and your example is one of such cases. Also TDataSet descendants can call this change and you cannot check them too.
Do you really think that assigning Field.ReadOnly just to assign data in code is a good technic? I think the problem here is not with any grid or TDataSet - it is with the implementation of the TClientDataSet.
It is *never* OK to arbitrarily toss out user data. *Never.*
What would you think of a word processor which threw away the last three paragraphs you wrote when you changed your regional settings because the implementor found it inconvenient to refresh the display? Would that be OK? And if not, why is it any better to lose data in a dataset when TField.ReadOnly changes? Yes, posts can fail. So can saving to a file. But an application which can’t be trusted to retain the work you as the user do is useless.
As for TClientDataset, I wish it didn’t work the way it did, but I can see the argument for doing it that way. TClientDataset has a dual purpose. It is both a client-side buffer for middle-tier data and a standalone flat file database. As a client buffer, it doesn’t make a lot of sense to forbid in-code editing of read-only columns. But as a single-tier flat file database it does make sense, because TField.ReadOnly is really the only way you have to make your metadata read only in this case.
So as for changing ReadOnly to assign data, I would prefer that it wasn’t necessary to do this, but it is, and it’s also legal within TDataSet’s contract. I don’t consider it a kludge, because TDataSet doesn’t have any rule against it.
I’m talking exclusively about dataset here - not about word processor or any other example (look at the name of the discussion here: it is "Developer Express QuantumGrid Frustration"). Yes - loosing of data is bad, but there is no other way to avoid it in this scenario. You can only "workaround" it. This is a real world and it has its own limitations - TDataSet change notification system is a limitation in this case for grid. So if you have solution for this problem on the grid’s level, I’d like to hear it.
Sorry, I just don’t accept that it is somehow more acceptable to lose data in a dataset than in a word processor. Making that distinction is disingenuous. Nor do I believe that it is impossible to avoid losing data in a dataset when ReadOnly changes (as can be easily demonstrated by disconnecting the Developer Express grid from the dataset). Finally, I don’t believe that it is necessary to reload data when the TField.ReadOnly changes — when ReadOnly changes the data *can’t* change, because there is no way to assign values and ReadOnly at the same time.
The only reason I was able to work around the problem here is because I happened to notice it. I would rather see an exception and have the whole process fail than to silently lose data and possibly not notice until our product ships. Things like that scare me.
The *only* thing which is difficult is determining that the reason you got deLayoutChanged is because ReadOnly changed, not for some other reason (such as the DataSource.DataSet being replaced). And the only way I know of to do this effectively is to exhaustively test the reasons for the event that affect the component you’re writing.
Here’s what I would do if I were the author of this component: My first choice would be to try and limit reloading to the times it is genuinely needed. As I said, that’s a great deal of work, but is doable with sufficient VCL source code analysis.
But failing that, I would change the Cancel to raising an exception and then provide the component user with a way of disabling the reload when necessary — something akin to a BeginUpdate/EndUpdate pair. Doing it this way means:
1) The developer cannot fail to notice that the grid is tossing out user changes.
2) The developer can take control when the developer knows more about the specific situation [s]he is working with than the grid author.
I would strongly prefer the first solution, because I don’t believe the reload is necessary *in this particular situation.* But the second solution, at least, does not risk losing user changes.
My messages are short but you do not read them.
1) Word processor is bad example because there are no edit and browse modes for it, just edit mode. Once you entered something it is already there.
2) There is no way to check ALL possible situations which cause layout change notification. Remember there are a lot of third-party datasets available. Also, how you as component developer can see that ReadOnly changed? Save previous ReadOnly values for all fields somewhere? And if you are sure ReadOnly changed, how can you be sure nothing else changed? Have you really tried to do something like this?
3) Do you developer DB-aware components of such level as grid?
4) I can only propose to have special option in the grid to ignore layout changes from the dataset when it is in the editing mode and hope that nothing will break because of this.
You write, "My messages are short but you do not read them." Funny, I was thinking the same thing. Let me write something shorter, then:
[-- Begin important part --]
It is not acceptable to throw away user data, period. Even if it’s difficult to implement.
[-- End important part --]
Now then…
Your suggestion 4 is OK with me. Or the second suggestion I made in my last message (i.e., something like a BeginUpdate/EndUpdate pair to ignore layout changes), which is equally simple to implement.
I really do think the call to Cancel should be replaced with raising an exception, but I understand that would break existing code and you don’t want to do that, either.
I would respond to your other points, but I think they’re tangential to the real issue here, and I think the solutions proposed (your #4 and my #2) will solve the problem in a way you and I can both live with.
Craig: I agree with you that abritrarily tossing out user entered data seems like a bad design decision.
> when ReadOnly changes the data *can’t* change, because there is no way to assign values and ReadOnly at the same time.
I haven’t tried it, but wouldn’t DisableControls/EnableControls have this affect?
Yes, Brad, I think calling DisableControls/EnableControls has the same effect.
However, I can live with the (not significant for me, anyway) limitation that I can’t call DisableControls/EnableControls while the dataset is in edit mode now that I know about it. I guess it will be a surprise for other folks.
This problem has been fixed as of QuantumGrid version 5. I have seen no more issues with all of these problems you’re speaking of
Post a Comment