Search icon CANCEL
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 50 hands-on recipes to master the power of Delphi for cross-platform and mobile development on Windows, Mac OS X, Android, and iOS

Arrow left icon
Product type Paperback
Published in Sep 2014
Publisher
ISBN-13 9781783559589
Length 328 pages
Edition 1st 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 (9) Chapters Close

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

Putting your VCL application in the tray

Some applications are designed to be always in the Windows tray bar. For almost all their running time, the user knows where that particular application is in the tray. Think about antivirus, custom audio processors, and video management tools provided by hardware vendors and many other things. Instead, some other applications need to go in the tray only when a long operation is running and the user should otherwise attend in front of a boring please wait animation. In these cases, users will be very happy if our application is not blocked and lets them do some other things. Then, a not intrusive notification will bring up an alert if some thing interesting happens. Think about heavy queries, statistics, heavy report generation, file upload or download, or huge data import or export. Think for a second: what if Google Chrome showed one modal dialog with a message Please wait, while this 2 GB file is downloading… stopping you to navigate to other pages? Crazy! Many applications could potentially behave like this.

In such cases, the users knows that they have to wait, but the application should be so "polite" as to let them do other things. Usually, programmers think that their software is the only reason the user bought a computer. Very often, this is not the case. So, let's find a way to do the right thing at the right moment.

Getting ready

This recipe is about creating a good Windows citizen application. Let's say our application allows us to execute a huge search in a database. When the user starts this long operation, the application UI remains usable. During the request execution, the user can decide to wait in front of the form or minimize it to the taskbar. If the user minimizes the application window, it also goes on the tray bar and when the operation finishes and it will alert the user with a nonintrusive message.

How to do it…

  1. Create a new VCL application and drop on it a TButton, a TLabel, a TTrayIcon, a TApplicationEvents, a TImagelist, a TDataSource, and a TDBGrid component. Connect the TDBGrid to the TDataSource. Leave the default component names (I'll refer to the components using their default names). Use the disposition and the captions to make the form similar to the following screenshot:
    How to do it…

    The form and its components as they should look

  2. In the implementation section of the unit, add the following units:
    • AnonThread: Add this unit to the project (this is located under C:\Users\Public\Documents\Embarcadero\Studio\14.0\Samples\Object Pascal\RTL\CrossPlatform Utils on my machine). You can avoid adding this unit in the project and add the path to the IDE library path by navigating to Tools | Options and then clicking on Delphi Options | Library.
    • RandomUtilsU: Add this unit to the project (this is located under the Commons folder of the recipes).
    • FireDAC.Comp.Client: Add this unit in the implementation uses section of the form.
  3. We'll start with the code that will actually do the heavy work. In the Button1.OnClick method, put this code:
    procedure TMainForm.Button1Click(Sender: TObject);
    var
      I: Integer;
      ds: TDataSet;
    begin
      Button1.Enabled := False;
    
      if Assigned(DataSource1.DataSet) then
      begin
        ds := DataSource1.DataSet;
        DataSource1.DataSet := nil;
        RemoveComponent(ds);
        FreeAndNil(ds);
      end;
    
      Label1.Caption := 'Data retrieving... may take a while';
      TAnonymousThread<TFDMemTable>.Create(
        function: TFDMemTable
        var
          MemTable: TFDMemTable;
          I: Integer;
        begin
          Result := nil;
          MemTable := TFDMemTable.Create(nil);
          try
            MemTable.FieldDefs.Add('EmpNo', ftInteger);
            MemTable.FieldDefs.Add('FirstName', ftString, 30);
            MemTable.FieldDefs.Add('LastName', ftString, 30);
            MemTable.FieldDefs.Add('DOB', ftDate);
            MemTable.CreateDataSet;
            for I := 1 to 400 do
            begin
              MemTable.AppendRecord([
                1000 + Random(9000),
                GetRndFirstName,
                GetRndLastName,
                EncodeDate(1970, 1, 1) + Random(10000)
                ]);
            end;
            MemTable.First;
            //just mimic a slow operation
            TThread.Sleep(2*60*1000);
            Result := MemTable;
          except
            FreeAndNil(MemTable);
            raise;
          end;
        end,
        procedure(MemTable: TFDMemTable)
        begin
          InsertComponent(MemTable);
          DataSource1.DataSet := MemTable;
          Button1.Enabled := True;
          Label1.Caption := 
             Format('Retrieved %d employee',
             [MemTable.RecordCount]);
          ShowSuccessBalloon(Label1.Caption);
        end,
        procedure(Ex: Exception)
        begin
          Button1.Enabled := True;
          Label1.Caption := Format('%s (%s)', 
                    [Ex.Message, Ex.ClassName]);
          ShowErrorBalloon(Label1.Caption);
        end);
    end;
    
  4. Now, create the following event handler for the Tray1.OnBalloonClick method and connect it to the Tra1.OnDoubleClick event handler:
    procedure TMainForm.TrayIcon1BalloonClick(Sender: TObject);
    begin
      TrayIcon1.Visible := False;
      WindowState := wsNormal;
      SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, 
             SWP_NOSIZE or SWP_NOMOVE);
      SetWindowPos(Handle, HWND_NOTOPMOST, 0, 0, 0, 0, 
             SWP_NOSIZE or SWP_NOMOVE);
    end;
    
  5. In the next step, the two raw SetWindowPos calls will be less obscure, believe me.
  6. Now, to keep things clear, we need the following two procedures. Create them as private methods of the form:
    procedure TMainForm.ShowErrorBalloon(const Mess: String);
    begin
      if TrayIcon1.Visible then
      begin
        TrayIcon1.IconIndex := 2;
        TrayIcon1.BalloonFlags := bfError;
        TrayIcon1.BalloonTitle := 'Errors occurred';
        TrayIcon1.BalloonHint := Label1.Caption;
        TrayIcon1.ShowBalloonHint;
      end;
    end;
    
    procedure TMainForm.ShowSuccessBalloon(const Mess: String);
    begin
      if TrayIcon1.Visible then
      begin
        TrayIcon1.IconIndex := 0;
        TrayIcon1.BalloonFlags := bfInfo;
        TrayIcon1.BalloonTitle := 'Request terminated';
        TrayIcon1.BalloonHint := Label1.Caption;
        TrayIcon1.ShowBalloonHint;
      end;
    end;
    
  7. Create one last event handler for the ApplicationEvents1.OnMinimize method:
    procedure TMainForm.ApplicationEvents1Minimize(
       Sender: TObject);
    begin
      TrayIcon1.Visible := True;
      TrayIcon1.BalloonTitle := 'Employee Manager';
      TrayIcon1.BalloonHint :=
        'Employee Manager is still running in the tray.' + 
          sLineBreak + 
          'Reactivate it with a double click on the tray icon';
      TrayIcon1.BalloonFlags := bfInfo;
      TrayIcon1.ShowBalloonHint;
      TrayIcon1.IconIndex := 0;
    end;
    
  8. Run the application by hitting F9 (or navigate to Run | Run).
  9. Click on the Get Employee button and then minimize the application (note that as the GUI is responsive, you can resize, minimize, and maximize the form).
  10. An icon is shown in the tray and shows a message about what the application is doing.
  11. As soon as the data has been retrieved, a Request terminated message will pop up. Click on the balloon. The application will come to the front and you will see the data in the TDBGrid.
  12. Try to repeat the procedure without minimizing the window. All is working as usual (this time without the tray messages) and the GUI is responsive.

How it works…

This recipe is a bit articulated. Let's start from the beginning.

The actual code that executes the request uses a nice helper class provided by Embarcadero in the Samples folder of RADStudio (not officially supported, it is just an official sample). The TAnonymousThread<T> constructor is a class that simplifies the process of starting a thread and when the thread ends, this class updates the UI with data retrieved by the thread.

The TAnonymousThread<T> constructor (there are other overloads, but this the most used) expects three anonymous methods:

  • function: T: This function is executed in the background thread context created internally (so you should avoid accessing the UI). Its Result value will be used after the thread execution.
  • procedure (Value: T): This procedure is called after the thread is executed. Its input parameter is the result value of the first function. This procedure is executed in the context of the main thread, so it can update the UI safely. It is not called in the case of an exception raised by the first function.
  • procedure (E: Exception): This procedure is called in the case of an exception during the thread execution and is executed in the context of the main thread, so it can update the UI safely. It is not called if there isn't an exception during thread execution.

The background thread (the first function passed to the TAnonymousThread<T> constructor) creates a memory table using the TFDMemTable component (we will talk about this component in the FireDAC-related section) and then that object is passed to the second anonymous method that adds it to the form's components using the InsertComponent() method and binds it to the DBGrid causing the data visualization.

When the data is ready in the grid, a call to the ShowSuccessBalloon() function shows a balloon message in the tray area, informing users that their data is finally available. If the user clicks on the balloon (or double-clicks on the tray icon), the application is restored. The balloon message is shown in the following screenshot:

How it works…

The balloon message when the data are ready in DBGrid

If the user clicks on the balloon, the form is restored. However, since Windows XP (with some variation in subsequent versions), the system restricts which processes can set the foreground window. An application cannot force a window to the foreground while the user is working with another window. The calls to SetWindowPos are needed to bring the form to the front.

In the included code, there is also another version of the recipe (20_VCLAppFlashNotification) that uses the most recent flash on the taskbar to alert the user. Consider this approach when you want to implement an application that, when minimized, has to alert the user in some way. The tray area may become rapidly crowded with icons. So consider to flash your icons in the taskbar instead.

The other code is required to correctly handle the memory ownership of the TFDMemTable instance.

There's more...

The use of a tray icon is a well-known pattern in Windows development. However, the concept of I'll go into the background for a while, if you want, and I'll show you the notification as soon something happens is used very often on Android, iOS, and Mac OS X. In fact, some part of this recipe code is reusable also on Mac OS X, iOS, and Android. Obviously, using the right system to alert the user when the background thread finishes (for example, on a mobile platform) execution should use the notification bar. The thread handling of this recipe works on every platform supported by Delphi.

You have been reading a chapter from
Delphi Cookbook
Published in: Sep 2014
Publisher:
ISBN-13: 9781783559589
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 €18.99/month. Cancel anytime