15

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?

Community
  • 1
  • 1
JosephStyons
  • 57,317
  • 63
  • 160
  • 234

4 Answers4

12

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.
JosephStyons
  • 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
  • 1
    The 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
  • 3
    The 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
  • 1
    I 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
4

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.

Community
  • 1
  • 1
yonojoy
  • 5,486
  • 1
  • 31
  • 60
2

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

TDam Message Example

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
1

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.

Rick
  • 1,301
  • 1
  • 16
  • 28