Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Delphi Cookbook

You're reading from   Delphi Cookbook Over 60 hands-on recipes to help you master the power of Delphi for cross-platform and mobile development on multiple platforms

Arrow left icon
Product type Paperback
Published in Jun 2016
Publisher
ISBN-13 9781785287428
Length 470 pages
Edition 2nd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Daniele Teti Daniele Teti
Author Profile Icon Daniele Teti
Daniele Teti
Arrow right icon
View More author details
Toc

Table of Contents (10) Chapters Close

Preface 1. Delphi Basics FREE CHAPTER 2. Becoming a Delphi Language Ninja 3. Knowing Your Friends – the Delphi RTL 4. Going Cross-Platform with FireMonkey 5. The Thousand Faces of Multithreading 6. Putting Delphi on the Server 7. Riding the Mobile Revolution with FireMonkey 8. Using Specific Platform Features Index

Be coherent with the Windows look and feel using TTaskDialog

Version after version, the Windows OS changed its look and feel a lot from the mid 2009 when the first Windows 95 came out. Also the UX guidelines from Microsoft changed a lot. Do you remember the Multiple Document Interface (MDI) paradigm? It was very popular in the 90s, but now is deprecated and an application seems old also if it has been just released. Indeed, many Windows applications seem stuck in the past in terms of UI and UX. What about dialogs? Our beloved ShowMessage and MessageDlg are there since Delphi 1, but now, the modern Windows versions use different dialogs to communicate to the users. Many of these standard dialogs contain more than a question and a simple Yes and No. Some dialogs ask something and provide a list of choices using radio buttons; some others have a nice progress bar inside; others have a nice button with an extended explanation of each choice just inside the button. How can our Delphi application can benefit from these new dialogs offered by the OS? In other words, how we can give a coherent look and feel to our dialog windows so that our application does not look old? This recipe shows how to use the TTaskDialog component.

Getting started

TTaskdialog is a dialog box somewhat like the standard call to Application.MessageBox in the VCL but much more powerful. Task Dialog API has been available since Windows Vista and Windows Server 2008, and your application must be theme enabled to use it (go to Project | Options | Application | Runtime Themes | Enable Runtime Themes).

Besides the usual default set of buttons (OK, Cancel, Yes, No, Retry, and Close), you can define extra buttons and many other customizations. The following Windows API provides task dialogs:

API Name

Description

TaskDialog

This creates, displays, and operates a task dialog. The task dialog contains application-defined message text and title, icons, and any combination of predefined push buttons. This function does not support the registration of a callback function to receive notifications.

TaskDialogCallbackProc

This is an application-defined function used with the TaskDialogIndirect function. It receives messages from the task dialog when various events occur. TaskDialogCallbackProc is a placeholder for the application-defined function name.

TaskDialogIndirect

This creates, displays, and operates a task dialog. The task dialog contains application-defined icons, messages, title, verification checkbox, command links, push buttons, and radio buttons. This function can register a callback function to receive notification messages.

More information about API utilization can be obtained from https://msdn.microsoft.com/en-us/library/windows/desktop/bb787471(v=vs.85).aspx.

While the API can be useful in some border cases, the VCL comes with a very nice component that does all the low-level stuff for us. Let's see the sample program that shows how it is simple to create a modern look and feel application.

How it works…

Open the TaskDialogs.dproj project and understand how it works.

There are six buttons on the form. The first one shows a simple utilization of the Task Dialog API, while the other five show a different utilization of the TTaskDialog component, which wraps that API.

The first button uses the Windows API directly with the following code:

procedure TMainForm.btnAPIClick(Sender: TObject);
var
  LTDResult: Integer;
begin
  TaskDialog(0, HInstance,
    PChar('The Title'),
    PChar('These are the main instructions'),
    PChar('This is another content'),
    TDCBF_OK_BUTTON or TDCBF_CANCEL_BUTTON,
    TD_INFORMATION_ICON, @LTDResult);
  case LTDResult of
    IDOK:
      begin
        ShowMessage('Clicked OK');
      end;
    IDCANCEL:
      begin
        ShowMessage('Clicked Cancel');
      end;
  end;
end;

The TaskDialog function is declared inside the Winapi.CommCtrl.pas unit. So far, you could ask, "Why should I use a component for TaskDialogs? Seems quite simple." Yes, it is, if you only want to mimic MessageDlg, but things get complicated very fast if you want to use all the features of the Task Dialog API. So, the second button uses the TTaskDialog component. Let's see the relevant properties configured at design time for the tdSimple component:

object tdSimple: TTaskDialog
  Caption = 'The question'
  CommonButtons = [tcbYes, tcbNo]
  DefaultButton = tcbYes
  ExpandButtonCaption = 'More information'
  ExpandedText = 'Yes, you have to decide something about this question...' + ' but I cannot help you a lot'
  Flags = [tfUseHiconMain, tfUseHiconFooter, tfVerificationFlagChecked]
  FooterIcon = 4
  FooterText = 'This is an important question...'
  Text = 'To be or not to be, this is the question. To be?'
  Title = 'William ask:'
end

Note

You can check the runtime appearance also at design time by double-clicking on the component over your form, or by selecting Test Dialog from the menu over the component. You can access the menu by right-clicking on the component.

As you can see, only the minimum properties have been set, just to show the power of the component. This configuration shows up a dialog with two buttons labelled Yes and No. The TTaskDialog component can be configured at design time using the Object Inspector, or can be configured at runtime by code. In this first example, the configuration is defined at design time so that at runtime we only have to call the Execute method and read the user response. Here's the code that actually uses the tdSimple instance:

procedure TMainForm.btnSimpleClick(Sender: TObject);
begin
  tdSimple.Execute; //show the taskdialog
  if tdSimple.ModalResult = mrYes then
    ShowMessage('yes')
  else
    ShowMessage('no')
end;

Even in this case, it is quite simple, but let's go deeper with the configuration. Let's say that we need a TaskDialog similar to the following screenshot:

How it works…

Fig. 12.1 The TTaskDialog component is configured to show three radio buttons

Using the plain API is not so simple to do this. So, let's see how to configure the component:

object tdRadioButtons: TTaskDialog
  Caption = 'The question'
  DefaultButton = tcbYes
  ExpandButtonCaption = 'More information'
  ExpandedText = 
    'Yes, you have to decide something about this question... ' + 'but I cannot help you a lot'
  Flags = [tfUseHiconMain, tfUseHiconFooter, tfVerificationFlagChecked]
  FooterIcon = 4
  FooterText = 'This is an important question...'
  RadioButtons = <
    item
      Caption = 'Yes, I want to buy this book'
    end
    item
      Caption = 'No, this book is awful'
    end
    item
      Caption = 'Maybe in the future'
    end>
  Text = 'Do you wanna buy "The Tragedy of Hamlet"?'
  Title = 'William ask:'
end

The preceding block of code contains the definition for the three radio buttons. The following code shows the dialog and the retrieval of the result:

procedure TMainForm.btnRadioClick(Sender: TObject);
begin
  tdRadioButtons.Execute;
  if tdRadioButtons.ModalResult = mrOk then
    ShowMessage('Selected radio button ' + tdRadioButtons.RadioButton.ID.ToString);
end;

Even in this case, we have defined the properties at design time so that the runtime code is quite simple. Just note that the user choice is stored in the RadioButton.ID property.

The TTaskDialog.Flags property can greatly change the behavior of the dialog. Here's the meaning of each element of its set:

Flag set element name

If set…

tfEnableHyperlinks

Content, footer, and expanded text can include hyperlinks

tfUseHiconMain

Uses the custom main icon

tfUseHiconFooter

Uses the custom footer icon

tfAllowDialogCancellation

Permits Task Dialog to be closed in the absence of a Cancel button

tfUseCommandLinks

Buttons are displayed as command links using a standard dialog glyph

tfUseCommandLinksNoIcon

Buttons are displayed as command links without a glyph

tfExpandFooterArea

Displays expanded text in the footer

tfExpandedByDefault

Expanded text is displayed when the Task Dialog opens

tfVerificationFlagChecked

The verification checkbox is initially checked

tfShowProgressBar

Displays the progress bar

tfShowMarqueeProgressBar

Displays the marquee progress bar

tfCallbackTimer

Callback Dialogs will be called every 200 milliseconds

tfPositionRelativeToWindow

Task Dialog is centered with respect to the parent window

tfRtlLayout

Text reads right to left

tfNoDefaultRadioButton

There is no default radio button

tfCanBeMinimized

The Task Dialog can be minimized

The real power of TaskDialogs comes when you build your dialog at runtime. Let's check what the fourth button does under the hood:

procedure TMainForm.btnConfirmClick(Sender: TObject);
var
  LFileName: string;
  LGSearch: String;
const
  GOOGLE_SEARCH = 99;
begin
  LFileName := 'MyCoolProgram.exe';
  tdConfirm.Buttons.Clear;
  tdConfirm.Title := 'Confirm Removal';
  tdConfirm.Caption := 'My fantastic folder';
  tdConfirm.Text :=
    Format('Are you sure that you want to remove ' + 'the file named "%s"?', [LFileName]);
  tdConfirm.CommonButtons := [];
  with TTaskDialogButtonItem(tdConfirm.Buttons.Add) do
  begin
    Caption := 'Remove';
    CommandLinkHint := Format('Delete file %s from the folder.', [LFileName]);
    ModalResult := mrYes;
  end;
  with TTaskDialogButtonItem(tdConfirm.Buttons.Add) do
  begin
    Caption := 'Keep';
    CommandLinkHint := 'Keep the file in the folder.';
    ModalResult := mrNo;
  end;

  if TPath.GetExtension(LFileName).ToLower.Equals('.exe') then
  begin
    with TTaskDialogButtonItem(tdConfirm.Buttons.Add) do
    begin
      Caption := 'Google search';
      CommandLinkHint := 'Let''s Google tell us what ' + 'this program is.';
      ModalResult := GOOGLE_SEARCH;
    end;
  end;

  tdConfirm.Flags := [tfUseCommandLinks];
  tdConfirm.MainIcon := tdiInformation;

  if tdConfirm.Execute then
  begin
    case tdConfirm.ModalResult of
      mrYes:
        ShowMessage('Deleted');
      mrNo:
        ShowMessage(LFileName + 'has been preserved');
      GOOGLE_SEARCH:
        begin
          LGSearch := Format('https://www.google.it/#q=%s', [LFileName]);
          ShellExecute(0, 'open', PChar(LGSearch), nil, nil, SW_SHOWNORMAL);
        end;
    end; //case
  end; //if
end;

It seems like a lot of code, but it is simple and can be easily parameterized and reused inside your program. The resultant dialog is as shown:

How it works…

Fig. 12.3 The dialog customized by code

The third choice allows the user to search on Google about the program executable name. This is not a common choice in the MessageDlg dialog where buttons are predefined, but using the Task Dialog you can even ask something "strange" to the user (such as "do you want to ask Google about it?")

To achieve a better apparent speed, progress bars are great! The Task Dialog API provides a simple way to use progress bars inside dialogs. The classic Delphi solution relays a custom form with a progress bar and some labels (just like the "Compiling" dialog that you see when you compile a program within the Delphi IDE). However, in some cases, you need some simple stuff done and a Task Dialog is enough. If TTaskDialog has the tfCallbackTimer flag and tfShowProgressBar, the OnTimer event will be called every 200 milliseconds (five times a second), and the dialog will show a progress dialog that you can update within the OnTimer event handler. However, the OnTimer event handler runs in the main thread so that all the related advice applies (if the UI becomes unresponsive, consider a proper background thread and a queue to send information to the main thread).

This is the design time configuration of TTaskDialog tdProgress:

object tdProgress: TTaskDialog
  Caption = 'Please wait'
  CommonButtons = [tcbCancel]
  ExpandButtonCaption = 'More'
  ExpandedText = 
    'A prime number (or a prime) is a natural number greater'+' than 1 that has no positive divisors other than 1 ' + 'and itself.'
  Flags = [tfAllowDialogCancellation, tfShowProgressBar, 
tfCallbackTimer]
  FooterIcon = 3
  FooterText = 'Please wait while we are calculate prime numbers'
  Text = 'Let'#39's calculate prime numbers up to 1000'
  Title = 'Calculating prime numbers...'
  VerificationText = 'Remember my choice'
  OnButtonClicked = tdProgressButtonClicked
  OnTimer = tdProgressTimer
end

There are two event handlers, one to handle click on the Cancel button inside the dialog and one to handle the callback:

const
  MAX_NUMBERS = 1000;
  NUMBERS_IN_A_SINGLE_STEP = 50;

procedure TMainForm.tdProgressButtonClicked(Sender: TObject;
  ModalResult: TModalResult; var CanClose: Boolean);
begin
  if not FFinished then
  begin
    tdProgress.OnTimer := nil;
    ShowMessage('Calculation aborted by user');
    CanClose := True;
  end;
end;

procedure TMainForm.tdProgressTimer(Sender: TObject; TickCount: Cardinal;
  var Reset: Boolean);
var
  I: Integer;
begin
  for I := 1 to NUMBERS_IN_A_SINGLE_STEP do
  begin
    if IsPrimeNumber(FCurrNumber) then
      Inc(FPrimeNumbersCount);
    tdProgress.ProgressBar.Position := FCurrNumber * 100 div MAX_NUMBERS;
    Inc(FCurrNumber);
  end;

  FFinished := FCurrNumber >= MAX_NUMBERS;
  if FFinished then
  begin
    tdProgress.OnTimer := nil;
    tdProgress.ProgressBar.Position := 100;
    ShowMessage('There are ' + FPrimeNumbersCount.ToString + ' prime numbers up to ' + MAX_NUMBERS.ToString);
  end;
end;

To not block the main thread, the prime numbers are calculated a few at a time. When the calculation is ended, the callback is disabled by setting the OnTimer event handler to nil.

In other words, the real calculation is done in the main thread, so you should slice your process in to smaller parts so that it can be executed one (small) piece at time.

The following code fires the progress Task Dialog:

procedure TMainForm.btnProgressClick(Sender: TObject);
begin
  FCurrNumber := 1;
  FFinished := False;
  FPrimeNumbersCount := 0;
  tdProgress.ProgressBar.Position := 0;
  tdProgress.OnTimer := tdProgressTimer;
  tdProgress.Execute;
end;

Here's the resultant dialog:

How it works…

Fig. 12.4 The Task Dialog with an embedded Progress Bar

There's more…

The new Task Dialog API can give your application a fresh breath, but that comes with cost because it works only on Vista or better, with enabled themes. So, how to work around the problem if you need to run the application also in Windows XP or in machine without themes enabled? For button 6, there's a simple code to check whether you can safely use the TTaskDialog component or whether you have to come back to normal ShowMessage or MessageDlg. Here's the event handler for the button 6:

procedure TMainForm.btnCheckWinVerClick(Sender: TObject);
var
  LTaskDialog: TTaskDialog;
begin
  if (Win32MajorVersion >= 6) and ThemeServices.ThemesEnabled then
  begin
    LTaskDialog := TTaskDialog.Create(Self);
    try
      LTaskDialog.Caption := 'MY Fantastic Application';
      LTaskDialog.Title := 'The Cook Task Dialog!';
      LTaskDialog.Text :=
        'This is a Task Dialog, so I''m on Vista ' + 'or better with themes enabled';
      LTaskDialog.CommonButtons := [tcbOk];
      LTaskDialog.Execute;
    finally
      LTaskDialog.Free;
    end
  end
  else
  begin
    ShowMessage('This is an old and boring ShowMEssage, ' + 'here only to support old Microsoft Windows OS ' + '(XP and below)');
  end;
end;

Try to disable the themes for your application and click on button 6.

Obviously, it is strongly suggested that you wrap this code in a function so that you do not have to write the same check code repeatedly.

Tip

Downloading the example code

You can download the example code files for this book from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

You can download the code files by following these steps:

  • Log in or register to our website using your e-mail address and password.
  • Hover the mouse pointer on the SUPPORT tab at the top.
  • Click on Code Downloads & Errata.
  • Enter the name of the book in the Search box.
  • Select the book for which you're looking to download the code files.
  • Choose from the drop-down menu where you purchased this book from.
  • Click on Code Download.

You can also download the code files by clicking on the Code Files button on the book's webpage at the Packt Publishing website. This page can be accessed by entering the book's name in the Search box. Please note that you need to be logged in to your Packt account.

Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:

  • WinRAR / 7-Zip for Windows
  • Zipeg / iZip / UnRarX for Mac
  • 7-Zip / PeaZip for Linux

The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Delphi-Cookbook-Second-Edition. We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!

You have been reading a chapter from
Delphi Cookbook - Second Edition
Published in: Jun 2016
Publisher:
ISBN-13: 9781785287428
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime