I am using Delphi, and I want to show custom text in the buttons of a MessageDlg, as described here. What is the best way to do that?
4 Answers
Answering my own question.... I wrote the below unit which works well for me.
Delphi provides CreateMessageDialog() to give you a dialog template, which you can modify before displaying. I used that to create a function I called MessageDlgCustom, which takes the same parameters as a standard MessageDlg, but adds one more for replacement button titles.
It correctly handles custom fonts and automatically adjusts buttons to be wide enough for their message. If the buttons overflow the dialog, then that gets adjusted too.
After using that unit, the below sample works:
case MessageDlgCustom('Save your changes?',mtConfirmation,
[mbYes,mbNo,mbCancel],
['&Yes, I would like to save them with this absurdly long button',
'&No, I do not care about my stupid changes',
'&Arg! What are you talking about? Do not close the form!'],
nil) //nil = no custom font
of
mrYes:
begin
SaveChanges;
CloseTheForm;
end; //mrYes (save & close)
mrNo:
begin
CloseForm;
end; //mrNo (close w/o saving)
mrCancel:
begin
//do nothing
end; //mrCancel (neither save nor close)
end; //case
If someone else knows a better way, please share it.
unit CustomDialog;
interface
uses
Dialogs, Forms, Graphics, StdCtrls;
function MessageDlgCustom(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; ToCaptions: array of string;
customFont: TFont) : integer;
procedure ModifyDialog(var frm: TForm; ToCaptions : array of string;
customFont : TFont = nil);
implementation
uses
Windows, SysUtils;
function GetTextWidth(s: string; fnt: TFont; HWND: THandle): integer;
var
canvas: TCanvas;
begin
canvas := TCanvas.Create;
try
canvas.Handle := GetWindowDC(HWND);
canvas.Font := fnt;
Result := canvas.TextWidth(s);
finally
ReleaseDC(HWND,canvas.Handle);
FreeAndNil(canvas);
end; //try-finally
end;
function MessageDlgCustom(const Msg: string;
DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; ToCaptions: array of string;
customFont: TFont): integer;
var
dialog : TForm;
begin
try
dialog := CreateMessageDialog(Msg, DlgType, Buttons);
dialog.Position := poScreenCenter;
ModifyDialog(dialog,ToCaptions,customFont);
Result := dialog.ShowModal;
finally
dialog.Release;
end; //try-finally
end;
procedure ModifyDialog(var frm: TForm; ToCaptions: array of string;
customFont: TFont);
const
c_BtnMargin = 10; //margin of button around caption text
var
i,oldButtonWidth,newButtonWidth,btnCnt : integer;
begin
oldButtonWidth := 0;
newButtonWidth := 0;
btnCnt := 0;
for i := 0 to frm.ComponentCount - 1 do begin
//if they asked for a custom font, assign it here
if customFont <> nil then begin
if frm.Components[i] is TLabel then begin
TLabel(frm.Components[i]).Font := customFont;
end;
if frm.Components[i] is TButton then begin
TButton(frm.Components[i]).Font := customFont;
end;
end;
if frm.Components[i] is TButton then begin
//check buttons for a match with a "from" (default) string
//if found, replace with a "to" (custom) string
Inc(btnCnt);
//record the button width *before* we changed the caption
oldButtonWidth := oldButtonWidth + TButton(frm.Components[i]).Width;
//if a custom caption has been provided use that instead,
//or just leave the default caption if the custom caption is empty
if ToCaptions[btnCnt - 1]<>'' then
TButton(frm.Components[i]).Caption := ToCaptions[btnCnt - 1];
//auto-size the button for the new caption
TButton(frm.Components[i]).Width :=
GetTextWidth(TButton(frm.Components[i]).Caption,
TButton(frm.Components[i]).Font,frm.Handle) + c_BtnMargin;
//the first button can stay where it is.
//all other buttons need to slide over to the right of the one b4.
if (1 < btnCnt) and (0 < i) then begin
TButton(frm.Components[i]).Left :=
TButton(frm.Components[i-1]).Left +
TButton(frm.Components[i-1]).Width + c_BtnMargin;
end;
//record the button width *after* changing the caption
newButtonWidth := newButtonWidth + TButton(frm.Components[i]).Width;
end; //if TButton
end; //for i
//whatever we changed the buttons by, widen / shrink the form accordingly
frm.Width := Round(frm.Width + (newButtonWidth - oldButtonWidth) +
(c_BtnMargin * btnCnt));
end;
end.

- 57,317
- 63
- 160
- 234
-
Well, if you are using at least Delphi 2007, then I would create a completely new MessageDlg() function, checking the Windows version first, using the new dialog classes on Vista, and use a modified version of the original MessageDlg() function otherwise. That would allow you to easily add "Don't show again" check boxes as well. – mghie Jul 08 '09 at 19:47
-
1The code as it currently stands doesn't compile. You need to reorganize a couple of the methods. GetTextWidth needs to be move up to the top of the implementation and if you move ModifiyDialog above the MessageDlgCustom method in the implementation then you can remove the declaration from the interface section. On WinXP the modified dialogs last button, using your example call sits almost on the edge of the window border. For some reason the method is not recalculating the width of the dialog properly. – Vivian Mills Jul 08 '09 at 19:48
-
@Ryan - thanks, I reorganized it to put the most important thing at the top, forgetting that it would break the compile. I've restored the original order. It should compile now. I'll have to try it on an XP machine - I'm using Vista. Hopefully the problem you describe only occurs in extreme cases, anyway... – JosephStyons Jul 08 '09 at 20:00
-
3The key to this answer is the VCL's CreateMessageDialog function, which provides a stock dialog object for you to modify before you show it. Please mention that at the start, instead of making readers dig through the example to find the zero-fanfare usage of it. – Rob Kennedy Jul 08 '09 at 21:56
-
@Rob - thanks for the suggestion, that does make it clearer. Added. – JosephStyons Jul 09 '09 at 12:33
-
@Ryan: The current version should correctly handle the form size all the time - notice the new (c_BtnMargin * btnCnt) at the end – JosephStyons Jul 09 '09 at 12:54
-
1I suggested a edit, to handle situations when you don't want to override all the buttons. Like `MessageDlgCustom('Confirm?',mtCustom, mbYesNoCancel, ['Yes, sir!','',''], nil);` and the default will be used instead of the empty strings. Please review it. – Vitim.us Apr 24 '14 at 19:16
As an alternative you can use the Open Source SynTaskDialog unit. SynTaskDialog uses the Windows TaskDialog API natively on newer Windows versions and emulates it on older versions. You even can use it with FireMonkey.
For an example of a customizable MessageDlg function have a look at this answer.
You may have a look at the TDam component available on GitHub (https://github.com/digao-dalpiaz/Dam).
This component allows you to create customized Message Dialogs with pre-defined buttons, using formatted text (HTML Text), and allowing to customize a lot of aspects of dialogs.
Besides that, you can manage all your app dialogs into a "container", which stores all dialogs as objects (TDamMsg).
TDamMsg properties allows to customize message dialog, like:
- Button1 - button 1 caption
- Button2 - button 2 caption
- Button3 - button 3 caption
Buttons: TDamMsgButtons = Defines the buttons in the message dialog:
- dbOK: Defines one button OK
- dbYesNo: Defines two buttons Yes/No
- dbOne: Defines one button by Button1 defined caption
- dbTwo: Defines two buttons by Button1 and Button2 defined captions
- dbThree: Defines three buttons by Button1, Button2 and Button3 defined captions

- 41
- 3
Also, make sure that your 3rd party controls also call your custom message dlg and not standard MessageDlg function. That is if they're actually using it. It is possible that 3rd party controls do not use the Delphi messagedlg and call the MessageBox API directly. If that's case, you might end up with inconsistencies in showing message boxes.

- 1,301
- 1
- 16
- 28