Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Bob Swart (aka Drs.Bob) Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
View Bob Swart's profile on LinkedIn Drs.Bob's Delphi Notes
These are the voyages using Delphi Enterprise (and Architect). Its mission: to explore strange, new worlds. To design and build new applications. To boldly go...
Title:

XE2 values of mrAll, mrNoToAll, mrYesToAll and mrClose

Author: Bob Swart
Posted: 11/9/2011 3:28:48 PM (GMT+1)
Content:

Delphi XE2 has introduced a DFM-breaking change in some (4) of the values of mrXXX constants that are used as ModalResult values for buttons and dialogs, among others.
In this blog post I provide a workaround that can prevent some frustration and help you fix this issue in your own projects.

In Delphi XE2 and lower, the definitions for the IDxxx constants can be found in the Windows.pas unit:

const
IDOK = 1; ID_OK = IDOK;
IDCANCEL = 2; ID_CANCEL = IDCANCEL;
IDABORT = 3; ID_ABORT = IDABORT;
IDRETRY = 4; ID_RETRY = IDRETRY;
IDIGNORE = 5; ID_IGNORE = IDIGNORE;
IDYES = 6; ID_YES = IDYES;
IDNO = 7; ID_NO = IDNO;
IDCLOSE = 8; ID_CLOSE = IDCLOSE;
IDHELP = 9; ID_HELP = IDHELP;
IDTRYAGAIN = 10;
IDCONTINUE = 11;

These definitions are the same in Delphi XE and XE2. Note that CLOSE=8 by the way.

The trouble starts when we look at the actual mrXXX values, which can be found in Delphi XE (and lower) in the Controls.pas unit:
const
mrNone = 0;
mrOk = idOk;
mrCancel = idCancel;
mrAbort = idAbort;
mrRetry = idRetry;
mrIgnore = idIgnore;
mrYes = idYes;
mrNo = idNo;
mrAll = mrNo + 1;
mrNoToAll = mrAll + 1;
mrYesToAll = mrNoToAll + 1;
mrClose = mrYesToAll + 1;

 
type
TModalResult = Low(Integer)..High(Integer);

As you can see here, mrAll = 8, mrNoToAll = 9, mrYesToAll = 10, and mrClose = 11 (and not 8 for some reason).

Delphi XE2 introduces FireMonkey, and has extended the set of mrXXX values. The definitions can be found in the System.UITypes.pas unit, and are as follows (note that the idXXX values needed to be copied here, since the cross-platform System.UITYpes.pas unit cannot use the WinApi.Windows unit obviously):
const
idOK = 1;
idCancel = 2;
idAbort = 3;
idRetry = 4;
idIgnore = 5;
idYes = 6;
idNo = 7;
idClose = 8;
idHelp = 9;
idTryAgain = 10;
idContinue = 11;
mrNone = 0;
mrOk = idOk;
mrCancel = idCancel;
mrAbort = idAbort;
mrRetry = idRetry;
mrIgnore = idIgnore;
mrYes = idYes;
mrNo = idNo;
mrClose = idClose;
mrHelp = idHelp;
mrTryAgain = idTryAgain;
mrContinue = idContinue;
mrAll = mrContinue + 1;
mrNoToAll = mrAll + 1;
mrYesToAll = mrNoToAll + 1;

Note that mrClose is now 8 (the same value which was actually defined for IDCLOSE in the Windows.pas unit all along). But also mrAll = 12 now (used to be 8), mrNoToAll = 13 now (used to be 9) and mrYesToAll = 14 now (used to be 10). And there are three new values: mrHelp, mrTryAgain and mrContinue, which were not available in VCL applications before.

Although this change in the mrXXX values for mrClose, mrAll, mrNoToAll and mrYesToAll does not seem like a big deal at first. Until you load a DFM file that has a TButton with the ModalResult property assigned to any of the above four values. If this DFM file was made with Delphi XE or below, then for mrAll, the value inside the DFM file will be a harccoded 8. However, if you then open up the DFM file in Delphi XE, the value remains 8, and Delphi XE2 will interpret it as mrClose. And not mrAll. Same with the mrNoToAll in Delphi XE which will be turned into mrHelp in Delphi XE2, and mrYesToAll in Delphi XE will be turned into mrTryAgain in Delphi XE2, and finally mrClose in Delphi XE will turn into mrContinue in Delphi XE2.

This is an unfortunate and unintended breaking change in Delphi XE2. Unfortunately, it is not easy to fix by Embarcadero (and it would mean that all third-party vendors need to recompile their source code and packages, because such as fundamental change in the System.UITypes.pas unit would mean just about everything needs to be recompiled again).

You could go over all your DFM files, and manually check the ModalResult properties of the TButtons and manually adjust the old mrAll, mrNoToAll, mrYesToAll and mrClose values to their new values (note that the drop-down combobox in the Object Inspector will already show mrClose, mrHelp, mrTryAgain and mrContinue for these four values). But that will mean that you cannot open the DFM file in Delphi XE or prior again, since the ModalResult values will be lost again (and meaningless for ModalResult values higher than 11, which were not used in Delphi XE and below).

The only “solution” to this problem that I could think of, was writing a DFM parser to scan for and identify suspicious ModalResult values of 8 and higher. And for these ModalResult values, a workaround is suggested by adding one line of code to the FormCreate constructor with the assignment of the actual ModalResult value.

For example, if a TButton called btnYesToAll has a ModalResult of mrYesToAll in Delphi XE, then the DFM File will contain the hardcoded value of 10. When loaded in Delphi XE2, this will be turned into mrTryAgain. That doesn’t work as expected in XE2. But if we change it to mrYesToAll in XE2, the new value 14 will be written to the DFM file, which is meaningless when opened in Delphi XE or lower.

However, if you add the following line to the FormCreate event handler:
  btnYesToAll.ModalResult := mrYesToAll; // ModalResult = 10

Then the ModalResult property will be assigned to the correct value if we compile with any value of Delphi. And the FormCreate is executed before we have a chance to click on the button, so the actual ModalResult will work as expected, in all versions of Delphi.

So, my DFM scanner will suggest adding just that line of code in the FormCreate event handler. The DFM Scanner will look for all DFM files in the current directory, as well as all subdirectories and read them (assuming they are stored as TEXT and not as binary DFM files – use convert to convert them into TEXT if you really still have some of these old DFM files on your disk).
  .\AutoIncTesting\Client:
========================
uLogin.dfm
// add to FormCreate of uLogin.pas
btnAll.ModalResult := mrAll; // ModalResult = 8
uMainForm.dfm
// add to FormCreate of uMainForm.pas
btnYesToAll.ModalResult := mrYesToAll; // ModalResult = 10

 
2 suspicious ModalResult values found.

You can download the source code of the simple ScanDFM.dpr console application here. Just compile it with Delphi 2007 or higher, and run it from the directory where it needs to scan the DFM files. It will not change anything, and write to the standard output (which you can redirect to a file, and then use to manually modify the associated .PAS files to implement this workaround).
Use at your own risk, and feel free to send me comments or feedback. Thanks in advance!

Back  


17 Comments

AuthorPostedComments
Bob Swart 11/11/09 16:04:52I've just sent all my subscription customers an e-mail telling them about this issue, with a link to the ScanDFM project for the workaround.
Maarten 11/11/09 16:39:23Hello Bob, I have (fortunately) not run into this yet, but thanks very much for your e-mail and making this tool available. This change could have given us some serious headache bug reports otherwise had we not known this.
Jeremy 11/11/10 00:08:44Had a quick look at the code, you should really check if the dfm is text before attempting to search it.
Eivind Bakkestuen 11/11/10 01:24:19The directory recursion check is flawed; it might not iterate all subdirectories as originally written. The (SRec.Attr = faDirectory) part should be: if ((SRec.Attr and faDirectory) = faDirectory) and (SRec.Name[1] <> '.') then
Bob Swart 11/11/10 07:36:46Eivind: when doing your version of the check, hidden subdirectories are also scanned. Originally I did not want to include those (like the __history directory), but it may be better. Besides, the DFM files in the __history directory have a ~?~ extension to they won't be parsed anyway.
Bob Swart 11/11/10 07:54:59OK, I've added a little check to scan for the $FF $0A $00 first three bytes of the DFM file. ScanDFM v1.02 is now available, same location http://www.bobswart.nl/Weblog/ftp/ScanDFM.zip

Thanks for all feedback and comments (also by e-mail).

Bob Swart 11/11/10 15:38:28Version 1.03 is also available now (follow me on Twitter to see all updates), including a report of the count, scanned and skipped, and better I/O check when opening DFM files (readonly files for example).
Knot Bob 11/11/14 03:53:32This is a prime example of why magic numbers should not be persisted. It would be much better to write the name of the enum value, like how Anchors or Font.Color are currently written to the dfm. Additionally a top-level dfm version number would help and would allow auto conversion of old dfm files on streaming in.
Knot Bob 11/11/14 04:09:13Perhaps a simple non-visual singleton component TFormFix could be created to detect and perform such fixes. It would store the current DFM version (e.g. Delphi build number) in the DFM. On DFM stream read at design time it would check the current build number and apply any fixes since the build number stored in the DFM (e.g. if the component were updated in the future to include additional fixes), optionally informing you of the change, and the DFM change would cause the IDE to indicate and prompt you that changes were made to the form. The component could also do this when first dropped on a form after prompting you for the current DFM version (choose pre-XE2, the current build number, or manually enter a build number) to allow you to drop it on old forms to convert them or new forms to pick up future fixes only.
Pol 11/11/16 17:44:18Very nice idea.
Kris Houser 11/11/30 21:23:37Release Notes for XE2 Update 3 will contain a note about this change. We also want to provide (with your permission) the following link for customers who need more information: http://www.drbob42.com/blog/ Kris Houser, Lead Writer, RAD Studio
Bob Swart 11/11/30 22:27:21@Kris - sure you have my permission. Note that http://www.bobswart.nl/Weblog/Blog.aspx?RootId=5:5029 is a more complete link to the issue at hand... (and might save them from looking for the correct post)...
Concerned Delphi User 11/12/06 18:06:29Isn't a better approach to fix this properly in Update 3, as this is clearly a bug that breaks backward compatibility of code/dfm that was fine from Delphi 1 to Delphi XE. The inconvenience of having to recompile any code is better than persisting this inconsistent behaviour. Otherwise means it will unintentionally break old code/dfm when moved to XE2 for no good reason, and this will be a silent error so many might not even know it exists causing weird problems in their apps. Also causes problems for people that need to maintain codebases/components across various Delphi versions.
Ann Lynnworth 11/12/07 04:37:16Re converting binary DFM files to plain text to make them searchable, etc., here is a utility to do that: http://cc.embarcadero.com/Item/24063 Thank you very much Bob for posting the full explanation and workaround.
Bob Swart 11/12/15 10:08:19Update #3 for Delphi XE2, C++Builder XE2 (and RAD Studio XE2) is now available, and the Release Notes at http://docwiki.embarcadero.com/RADStudio/en/Release_Notes_for_XE2_Update_3 contain a link back to this page ;-)
rgreat 11/12/19 00:09:28Who was that idiot who made this change into Update 3? And where was the frigging QA? Vacation?! Why, for the god sake you needed to change these numbers?!!! I imagine: Programm: "Dear User, before do you need to save your impotant files before formatting? [Yes,YesToAll,Discard and Close]" User: -> YesToAll of cause! Programm: Ok, Close! User: Whaaaat?!... Shoot himself. Embarcadero : You guys are your own worst enemy.
rgreat 11/12/19 00:19:44Even if it was REALLY REALLY needed to make these weird changes to constants, why didn't Embarcadero include automatic failproof DFM and Code fixer into IDE (with user notification!) on project load?


New Comment (max. 2048 characters, no HTML):

Name:
Comment:



This webpage © 2005-2017 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.