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).
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!
|