32

How does one use the TTaskDialog class (in Delphi 2009 and later)? The official documentation hasn't helped. In fact, you learn much more by examining the class using CodeInsight or the VCL source code. There are no pedagogical explanations there, but at least there are no errors either (well, just a few).

And just recently, I wondered how you could respond to hyperlink clicks in the dialog. Indeed, setting the tfEnableHyperlinks flag, you can include HTML hyperlinks in the dialog's textual parts. (Well, the doc's said about the flag: "If set, content, footer, and expanded text can include hyperlinks." Naturally, it is "obvious" that the links are implemented using the <A HTML element.) And I managed to figure out myself that you use the OnHyperLinkClick event to respond to clicks on hyperlinks. But this event is a TNotifyEvent, so how do you know what link was clicked? Well, the documentation said nothing about that, so I had to guess. Eventually I found out that the URL public property of the dialog is set, so I could do

procedure TmainFrm.TaskDialogHyperLinkClicked(Sender: TObject);
begin
  if Sender is TTaskDialog then
    with Sender as TTaskDialog do
      ShellExecute(0, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL);
end;

The official documentation says, regarding this property:

URL contains the URL for the Task Dialog.

Now, you have to admit, that's a great explanation! But it is worse than this: not only does the documentation lack in explanations, it also contains errors. For instance,

ExpandButtonCaption: Additional information for this button.

That's not very accurate. What button? If you show the help for this particular property, it says

ExpandButtonCaption contains additional text to be displayed when the caption is expanded.

Not good either. What caption? A proper explanation would be

ExpandButtonCaption is the text shown next to the button that lets the user expand the dialog to make it show more information. For instance, this property might be "More details".

At any rate, currently, I am trying to create a dialog with two command-link buttons. I know that the operating system can display these buttons with both a caption and a longer explanation, but I seem not to be able to make it work using the TTaskButton. The documentation isn't great.

But instead of asking how to achieve this particular thing here at SO, I will ask another question:

Is there any (unofficial) documentation for the TTaskDialog class?

coding Bott
  • 4,287
  • 1
  • 27
  • 44
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384

4 Answers4

81

If you can't find the documentation, then write it:

The Hello World of a Task Dialog

with TTaskDialog.Create(Self) do
  try
    Caption := 'My Application';
    Title := 'Hello World!';
    Text := 'I am a TTaskDialog, that is, a wrapper for the Task Dialog introduced ' +
            'in the Microsoft Windows Vista operating system. Am I not adorable?';
    CommonButtons := [tcbClose];
    Execute;
  finally
    Free;
  end;

Caption is the text shown in the titlebar of the window, Title is the header, and Text is the body matter of the dialog. Needless to say, Execute displays the task dialog, and the result is shown below. (We will return to the CommonButtons property in a section or two.)

Sample of a TTaskDialog

Being a Well-Behaved Citizen

Of course, the task dialog will crash the program if running under Windows XP, where there is not task dialog API. It will also not work if visual themes are disabled. In any such case, we need to stick to the old-fashioned MessageBox. Hence, in a real application, we would need to do

if (Win32MajorVersion >= 6) and ThemeServices.ThemesEnabled then
  with TTaskDialog.Create(Self) do
    try
      Caption := 'My Application';
      Title := 'Hello World!';
      Text := 'I am a TTaskDialog, that is, a wrapper for the Task Dialog introduced ' +
              'in the Microsoft Windows Vista operating system. Am I not adorable?';
      CommonButtons := [tcbClose];
      Execute;
    finally
      Free;
    end
else
  MessageBox(Handle,
             'I am an ordinary MessageBox conveying the same message in order to support' +
             'older versions of the Microsoft Windows operating system (XP and below).',
             'My Application',
             MB_ICONINFORMATION or MB_OK);

In the rest of this article, we will assume that the tax of backwards compatibility is being payed, and instead concentrate on the task dialog alone.

Types of Dialogs. Modal Results

The CommonButtons property is of type TTaskDialogCommonButtons, defined as

TTaskDialogCommonButton = (tcbOk, tcbYes, tcbNo, tcbCancel, tcbRetry, tcbClose);
TTaskDialogCommonButtons = set of TTaskDialogCommonButton;

This property determines the buttons shown in the dialog (if no buttons are added manually, as we will do later on). If the user clicks any of these buttons, the corresponding TModalResult value will be stored in the ModalResult property as soon as Execute has returned. The MainIcon property determines the icon shown in the dialog, and should -- of course -- reflect the nature of the dialog, as should the set of buttons. Formally an integer, MainIcon can be set to any of the values tdiNone, tdiWarning, tdiError, tdiInformation, and tdiShield.

with TTaskDialog.Create(Self) do
  try
    Caption := 'My Application';
    Title := 'The Process';
    Text := 'Do you want to continue even though [...]?';
    CommonButtons := [tcbYes, tcbNo];
    MainIcon := tdiNone; // There is no tdiQuestion
    if Execute then
      if ModalResult = mrYes then
        beep;
  finally
    Free;
  end;

Sample of a TTaskDialog

Below are samples of the remaining icon types (shield, warning, and error, respectively):

Sample of a TTaskDialog

Sample of a TTaskDialog

Sample of a TTaskDialog

Finally, you should know that you can use the DefaultButton property to set the default button in the dialog box.

with TTaskDialog.Create(Self) do
  try
    Caption := 'My Application';
    Title := 'The Process';
    Text := 'Do you want to continue even though [...]?';
    CommonButtons := [tcbYes, tcbNo];
    DefaultButton := tcbNo;
    MainIcon := tdiNone;
    if Execute then
      if ModalResult = mrYes then
        beep;
  finally
    Free;
  end;

Sample of a TTaskDialog

Custom Buttons

You can add custom buttons to a task dialog. In fact, you can set the CommonButtons property to the empty set, and rely entirely on custom buttons (and un unlimited number of such buttons, too). The following real-world example shows such a dialog box:

with TTaskDialog.Create(self) do
  try
    Title := 'Confirm Removal';
    Caption := 'Rejbrand BookBase';
    Text := Format('Are you sure that you want to remove the book file named "%s"?', [FNameOfBook]);
    CommonButtons := [];
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := 'Remove';
      ModalResult := mrYes;
    end;
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := 'Keep';
      ModalResult := mrNo;
    end;
    MainIcon := tdiNone;
    if Execute then
      if ModalResult = mrYes then
        DoDelete;
  finally
    Free;
  end

Sample of a TTaskDialog

Command Links

Instead of classical pushbuttons, the task dialog buttons can be command links. This is achieved by setting the tfUseCommandLinks flag (in Flags). Now you can also set the CommandLinkHint (per-button) property:

with TTaskDialog.Create(self) do
  try
    Title := 'Confirm Removal';
    Caption := 'Rejbrand BookBase';
    Text := Format('Are you sure that you want to remove the book file named "%s"?', [FNameOfBook]);
    CommonButtons := [];
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := 'Remove';
      CommandLinkHint := 'Remove the book from the catalogue.';
      ModalResult := mrYes;
    end;
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := 'Keep';
      CommandLinkHint := 'Keep the book in the catalogue.';
      ModalResult := mrNo;
    end;
    Flags := [tfUseCommandLinks];
    MainIcon := tdiNone;
    if Execute then
      if ModalResult = mrYes then
        DoDelete;
  finally
    Free;
  end

Sample of a TTaskDialog

The tfAllowDialogCancellation flag will restore the close system menu item (and titlebar button -- in fact, it will restore the entire system menu).

Sample of a TTaskDialog

Don't Throw Technical Details at the End User

You can use the properties ExpandedText and ExpandedButtonCaption to add a piece of text (the former) that is only displayed after the user clicks a button (to the left of the text in the latter property) to request it.

with TTaskDialog.Create(self) do
  try
    Title := 'Confirm Removal';
    Caption := 'Rejbrand BookBase';
    Text := Format('Are you sure that you want to remove the book file named "%s"?', [FNameOfBook]);
    CommonButtons := [];
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := 'Remove';
      CommandLinkHint := 'Remove the book from the catalogue.';
      ModalResult := mrYes;
    end;
    with TTaskDialogButtonItem(Buttons.Add) do
    begin
      Caption := 'Keep';
      CommandLinkHint := 'Keep the book in the catalogue.';
      ModalResult := mrNo;
    end;
    Flags := [tfUseCommandLinks, tfAllowDialogCancellation];
    ExpandButtonCaption := 'Technical information';
    ExpandedText := 'If you remove the book item from the catalogue, the corresponding *.book file will be removed from the file system.';
    MainIcon := tdiNone;
    if Execute then
      if ModalResult = mrYes then
        DoDelete;
  finally
    Free;
  end

The image below shows the dialog after the user has clicked the button to reveal the additional details.

Sample of a TTaskDialog

If you add the tfExpandFooterArea flag, the additional text will instead be shown in the footer:

Sample of a TTaskDialog

In any case, you can let the dialog open with the details already expanded by adding the tfExpandedByDefault flag.

Custom Icons

You can use any custom icon in a task dialog, by using the tfUseHiconMain flag and specifying the TIcon to use in the CustomMainIcon property.

with TTaskDialog.Create(self) do
  try
    Caption := 'About Rejbrand BookBase';
    Title := 'Rejbrand BookBase';
    CommonButtons := [tcbClose];
    Text := 'File Version: ' + GetFileVer(Application.ExeName) + #13#10#13#10'Copyright © 2011 Andreas Rejbrand'#13#10#13#10'http://english.rejbrand.se';
    Flags := [tfUseHiconMain, tfAllowDialogCancellation];
    CustomMainIcon := Application.Icon;
    Execute;
  finally
    Free;
  end

Sample of a TTaskDialog

Hyperlinks

You can even use HTML-like hyperlinks in the dialog (in Text, Footer, and ExpandedText), if you only add the tfEnableHyperlinks flag:

with TTaskDialog.Create(self) do
  try
    Caption := 'About Rejbrand BookBase';
    Title := 'Rejbrand BookBase';
    CommonButtons := [tcbClose];
    Text := 'File Version: ' + GetFileVer(Application.ExeName) + #13#10#13#10'Copyright © 2011 Andreas Rejbrand'#13#10#13#10'<a href="http://english.rejbrand.se">http://english.rejbrand.se</a>';
    Flags := [tfUseHiconMain, tfAllowDialogCancellation, tfEnableHyperlinks];
    CustomMainIcon := Application.Icon;
    Execute;
  finally
    Free;
  end

Sample of a TTaskDialog

Notice, however, that nothing happens when you click the link. The action of the link must be implemented manually, which -- of course -- is a good thing. To do this, respond to the OnHyperlinkClicked event, which is a TNotifyEvent. The URL of the link (the href of the a element, that is) is stored in the URL public property of the TTaskDialog:

procedure TForm1.TaskDialogHyperLinkClicked(Sender: TObject);
begin
  if Sender is TTaskDialog then
    with Sender as TTaskDialog do
      ShellExecute(0, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with TTaskDialog.Create(self) do
    try
      Caption := 'About Rejbrand BookBase';
      Title := 'Rejbrand BookBase';
      CommonButtons := [tcbClose];
      Text := 'File Version: ' + GetFileVer(Application.ExeName) + #13#10#13#10'Copyright © 2011 Andreas Rejbrand'#13#10#13#10'<a href="http://english.rejbrand.se">http://english.rejbrand.se</a>';
      Flags := [tfUseHiconMain, tfAllowDialogCancellation, tfEnableHyperlinks];
      OnHyperlinkClicked := TaskDialogHyperlinkClicked;
      CustomMainIcon := Application.Icon;
      Execute;
    finally
      Free;
    end
end;

The Footer

You can use the Footer and FooterIcon properties to create a footer. The icon property accepts the same values as the MainIcon property.

with TTaskDialog.Create(self) do
  try
    Caption := 'My Application';
    Title := 'A Question';
    Text := 'This is a really tough one...';
    CommonButtons := [tcbYes, tcbNo];
    MainIcon := tdiNone;
    FooterText := 'If you do this, then ...';
    FooterIcon := tdiWarning;
    Execute;
  finally
    Free;
  end

Sample of a TTaskDialog

Using the tfUseHiconFooter flag and the CustomFooterIcon property, you can use any custom icon in the footer, in the same way as you can choose your own main icon.

A Checkbox

Using the VerificationText string property, you can add a checkbox to the footer of the task dialog. The caption of the checkbox is the property.

with TTaskDialog.Create(self) do
  try
    Caption := 'My Application';
    Title := 'A Question';
    Text := 'This is a really tough one...';
    CommonButtons := [tcbYes, tcbNo];
    MainIcon := tdiNone;
    VerificationText := 'Remember my choice';
    Execute;
  finally
    Free;
  end

Sample of a TTaskDialog

You can make the checkbox initially checked by specifying the tfVerificationFlagChecked flag. Unfortunately, due to a bug (?) in the VCL implementation of the TTaskDialog, the inclusion of this flag when Execute has returned doesn't reflect the final state of the checkbox. To keep track of the checkbox, the application thus needs to remember the initial state and toggle an internal flag as a response to each OnVerificationClicked event, which is triggered every time the state of the checkbox is changed during the modality of the dialog.

Radio Buttons

Radio buttons can be implemented in a way resembling how you add custom push buttons (or command link buttons):

with TTaskDialog.Create(self) do
  try
    Caption := 'My Application';
    Title := 'A Question';
    Text := 'This is a really tough one...';
    CommonButtons := [tcbOk, tcbCancel];
    MainIcon := tdiNone;
    with RadioButtons.Add do
      Caption := 'This is one option';
    with RadioButtons.Add do
      Caption := 'This is another option';
    with RadioButtons.Add do
      Caption := 'This is a third option';
    if Execute then
      if ModalResult = mrOk then
        ShowMessage(Format('You chose %d.', [RadioButton.Index]));
  finally
    Free;
  end

Sample of a TTaskDialog

JensG
  • 13,148
  • 4
  • 45
  • 55
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • 5
    Nice writeup. +1 for the extra effort. – Chris Thornton Feb 14 '11 at 15:42
  • +1 to @Chris. You always post a nice comment when someone gets a lot of criticism (c.f. http://stackoverflow.com/questions/3017743/delphi-components-you-cant-live-without/3017810#3017810). There should be a badge for that, e.g. "comradeship", or "supportive". – Andreas Rejbrand Feb 14 '11 at 17:53
  • Selfish idea: would you consider creating a component that degrades gracefully under Windows XP? I like the idea of the TMS component, but I already have a third-party package and don't want to add another... I can provide some help if you create a small google code project... :-) – Leonardo Herrera Feb 15 '11 at 17:10
  • @Leonardo: That wouldn't be difficult at all. (Not saying I will do it, though...) – Andreas Rejbrand Feb 15 '11 at 22:12
  • +1 for Q&A; I've found TTaskDialog now and your article about it helped me a lot. Good job! –  Aug 16 '11 at 21:06
  • 1
    @Leonardo: Maybe you've discovered it for yourself by now, but there is [SynTaskDialog](http://blog.synopse.info/post/2011/03/05/Open-Source-SynTaskDialog-unit-for-XP,Vista,Seven). Its emulation doesn't cover everything Microsoft's task dialog can do and it looks a bit rough but it works. – Uli Gerhardt Oct 17 '11 at 19:45
  • @UlrichGerhardt - no, I haven't revisited this thought I meant to. Thanks! – Leonardo Herrera Oct 18 '11 at 16:07
  • hey 'Service Unavailable' for the link, – PresleyDias Apr 19 '12 at 07:55
  • @PresleyDias: Must be my web hosting provider that experienced problems. I never remove stuff from my sites. – Andreas Rejbrand Apr 19 '12 at 10:50
  • @AndreasRejbrand : oh ok got it, it opens now – PresleyDias Apr 19 '12 at 12:30
  • Hi, Andreas, could you take your excellent example over here? This answer as it stands is endangered to be closed as VLQ due to being `Link Only`. – bummi Nov 02 '14 at 07:50
  • Meanwhile the problem with the checkbox flag 'tfVerificationFlagChecked' seems to be fixed. I'm working with Delphi XE3 and it works just fine. – Tupel Nov 26 '14 at 10:57
  • 6
    Embarcadero should use your code and reward you with a free license. – Gabriel Oct 20 '16 at 18:26
  • + 100 - this solves almost every of case of fancy message dialogues!! – Reversed Engineer Jul 28 '17 at 18:23
  • "_There is no tdiQuestion_" -> Workaround: `MainIcon := 32514;` – dipold Nov 30 '18 at 12:12
  • 2
    This is the best answer on SO ever. I want to declare your honorific citizen of StackOverflow. – Gabriel Nov 29 '19 at 20:32
  • I don't think that the `ThemeServices.ThemesEnabled` check is required. Even after disabling `Themes` service in `services.msc` and disabling all the visual effects, it still shows up, not as nice, but still fully functional. So is the `ThememServices.ThememsEnabled` check really needed here? Here is a screenshot: https://i.imgur.com/MeLEoAK.png – Coder12345 Feb 06 '20 at 00:43
  • @Coder12345: I think it was at the time I wrote this A (Delphi 2009 on Windows 7). It might not be needed today (Delphi 10.3 on Windows 10). – Andreas Rejbrand Feb 06 '20 at 06:55
  • @AndreasRejbrand I tested this in details. On newer versions of Delphi `ThemeServices.ThemesEnabled` is not required, it works without it even if themes are disabled, then it goes into "ugly mode" automatically. If compiled using older version of Delphi then it won't work not even in the "ugly mode". So it is better to leave it in the code to make the code compatible with all versions of Delphi, with this perhaps as a sidenote. – Coder12345 Feb 09 '20 at 20:59
7

This is old stuff, but I'm adding this here for completeness:

Open Source SynTaskDialog unit for XP,Vista,Seven

TTaskDialog unit that works under XP (with VCL), but uses the system TaskDialog under Vista+.

Leonardo Herrera
  • 8,388
  • 5
  • 36
  • 66
5

TMS Has a nice wrapper, and it also emulates the new behavior when run on XP. It's a snap to drop-in. It's not free though, and doesn't really answer your "how to" question.

http://www.tmssoftware.com/site/vtd.asp

They also have some articles where they discuss the dialog, and there's some source code that may be useful to you if you want to make your own wrapper.

http://www.tmssoftware.com/site/atbdev5.asp

http://www.tmssoftware.com/site/atbdev7.asp

Chris Thornton
  • 15,620
  • 5
  • 37
  • 62
  • Yes, I know of this wrapper, but I prefer to do things without 3rd-party components. – Andreas Rejbrand Feb 12 '11 at 18:56
  • To be honest, I don't think that task dialogs look good on XP. They look misplaced. The way to do it, IMHO, is to use task dialogs on Vista/7, and old-fashioned `MessageBox`es on XP, as I do at http://specials.rejbrand.se/TTaskDialog. – Andreas Rejbrand Feb 13 '11 at 10:27
  • However, it's not breaking your app on XP. Even if Andreas doesn't think it's a bad idea. The rest of the professional software community doesn't look too kindly on such a limitation. If your software is at toy, then fine. is looking good more important to you, than complete functional breakage? – Warren P Feb 14 '11 at 15:00
  • @Warren P: All my applications work perfectly on Windows XP. I always use `TTaskDialog` on Vista/7 and the `MessageBox` on XP. If you call that "breaking" the app on XP, then that is your (and Embarcadero's?) opinion. – Andreas Rejbrand Feb 14 '11 at 17:02
  • TMS Task dialogs have some ugly issues when VCL Styles are used. – user1580348 Jun 29 '16 at 11:34
1

Here's my short documentation:

  1. Don't use it, unless you don't want your application to work on XP.

  2. Suggest improvements to the delphi doc wiki content, as others did, but note the phrase "Note: Task Dialogs require Vista or Windows 7". that's code for "Don't use it!". Basically, someone got the idea to fully support new Windows Vista dialogs, and the way it was done, was to write wrapper code that only calls the dialog APIs. Since no fallback functionality is provided to you, you are out of luck on XP.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • 2
    I always test the OS version and the availability of visual themes (yes, `TTaskDialog` doesn't work on Vista/7 without Aero enabled), and use a `TTaskDialog` if possible, and an ordinary `MessageBox` if not, as in http://specials.rejbrand.se/TTaskDialog. The wonderful visual style of the `TTaskDialog` makes it all worth this extra work. (But still, to be honest, I really do feel like "Who does use XP today?") – Andreas Rejbrand Feb 13 '11 at 10:13
  • I don't think it is wise to blame Embarcadero for not making the `TTaskDialog` work on XP, because `TTaskDialog` is only a wrapper for an operating system API, and this is new to Vista. Yes, maybe it would have been good if the `TTaskDialog` did some custom processing on XP (or without visual themes), to simplify (marginally) the life of the developers (a simplification that wouldn't have been there if they used nothing but the native OS API), but I don't think you should require a wrapper to do so. – Andreas Rejbrand Feb 13 '11 at 10:22
  • Disclosure: I work for Embarcadero. But I still personally don't like components that don't work on all common windows versions. If you want to write your applications, to not work on XP fine. But remember stackoverflow is not just about you and your problem, it's also about people who will come along later and read this information. So downvote away. But others may come along and need to know this. – Warren P Feb 14 '11 at 14:59
  • 1
    @Warren, see Andreas's writeup. His example shows how to make it fall-back nicely for XP. – Chris Thornton Feb 14 '11 at 15:41
  • @Warren P, I think the downvote is because of the tone of the post. Looks ranty :-) – Leonardo Herrera Feb 15 '11 at 17:12
  • Fortunately, XP is now history – mjn Nov 02 '14 at 11:34
  • @AndreasRejbrand is the check for `ThemeServices.ThemesEnabled` really necessary? Please see my comment on your post - Screenshot again: https://i.imgur.com/MeLEoAK.png – Coder12345 Feb 06 '20 at 00:50
  • @Coder12345: I think it was at the time of this post (Delphi 2009 on Windows 7). It might not be needed today (Delphi 10.3 on Windows 10). – Andreas Rejbrand Feb 06 '20 at 06:56