As promised earlier (blog.marcocantu.com/blog/2014-september-building-vcl-apps-xe7.html) here is a first technical blog post on the VCL in Delphi XE7. 

While most existing Windows applications run fine on new versions of the operating system, they often lack a good level of integration with some of the new OS features introduced in Windows 7 and Windows 8. One of these features is the ability to customize the TaskBar button, the graphical element indicating that an application is running and placed in a taskbar that is by default at the bottom of the screen.

TaskBar Buttons in Windows 7 and Windows 8

The operating system allows for several customizations of these taskbar buttons:

  • Adding overlay icons on top of the program icon, to indicate status. An example could be showing you have pending notifications or to do items
  • Showing a progress bar into the taskbar button, so that a user can see the status of a lengthy operation (for example, when you copy or download a file)
  • Customizing the preview displayed as you hoover over the taskbar button, allowing also multiple elements (multiple forms, multiple tabs)
  • Adding action buttons in the frame of this preview
  • Adding custom items to the taskbar button menu, using jump lists

Now the great news is Delphi XE7 directly support all of the features above with specific easy-to-use components. All of the features save for the last one were supported in XE6 using the Taskbar component, while the last one is supported in XE7 using the JumpList component:

 

Taskbar Buttons

There are several operations you can do on the Taskbar component, highlighted by a few demos that ship since XE6. For example, you can set a progress bar in the application task bar button with two lines of code:

Taskbar1.ProgressMaxValue := TrackBar1.Max;
Taskbar1.ProgressValue := TrackBar1.Position;

Another handy feature is adding a overlay icon, given an image:

Taskbar1.OverlayIcon := Image1.Picture.Icon;

This is the taskbar button of a Delphi application with a progress bar and a email icon overlay:

 

There is a lot more you can easily do, like trimming the preview and adding extra border icons. This is the result of these settings (for the code you an refer to the OneFormApp demo under the VCL/Taskbar section):

 

Jump List

Another relevant feature of the taskbar buttons is the ability to customize their local menu. This is a feature Microsoft calls jump list. You can add files or special information to those menu items, like Chrome does:

 

Defining these items using the new TJumpList component is quite simple. You have a main list plus multiple categories with sub-lists. In this case the sub-list is initially empty:

object JumpList1: TJumpList
  AutoRefresh = True
  Enabled = True
  ApplicationID = 'JumpListAppID'
  CustomCategories = <
    item
      CategoryName = 'Dropped Files'
      Items = <>
    end>
  ShowRecent = True
  ShowFrequent = True
  TaskList = <
    item
      FriendlyName = 'Sample'
      Arguments = 'SampleParameter'
    end
    item
      FriendlyName = 'Second'
      Arguments = 'SecondParameter'
    end>
end

If you look at the events of the component, though, it is not obvious how to process the selection of these menus. When a user selects these items, in fact, the application is not directly notified: The system starts a new instance of the application, passing the specific value as a command line parameter.

If all you need to create multiple instances (for example, because each document is managed by a different application instance) the system behavior makes it easy. But if you want to show multiple documents in a single tab, locate a record, open a new form within the same application, than there is some work you have to do. In short, you have to let the new instance notify the existing one that an operation has been requested.

A way to handle this scenario is to use a global mutex to keep track of the execution status, and if an existing instance exists, find its main form and forward the information received by the second instance as a command line parameter to the main form of the existing instance. Clear as mud? Here it is in code. First, change the VCL project startup code:

// check if mutex already exists
HMutex := CreateMutex(nil, False, 'JumpListFilesDemoMutex');
if WaitForSingleObject(hMutex, 0) <> wait_TimeOut then
begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TJumpListForm, JumpListForm);
  Application.Run;
end
else
  ActivateWindow (TJumpListForm);

Now what’s missing is the code for ActivateWindow (which I’ve made generic enough so it can be reused). The procedure sets a couple of global variables needed by the EnumWndProc function (omitted from this code) which scans all of the current running applications for the correct form. If that form is found, the ActivateWindow procedure creates the proper data structure to pass the command line parameter received using Windows wm_CopyData message:

procedure ActivateWindow (aWindowClass: TClass);
var
  cds: CopyDataStruct;
  fFileName: string;
  i: integer;
begin
  MainWindowClass := aWindowClass;

  // get the current module name
  SetLength(ModuleName, 1024);
  GetModuleFileName(HInstance,
    PChar(ModuleName), Length(ModuleName));
  ModuleName := PChar(ModuleName); // adjust length

  // find window of previous instance
  EnumWindows(@EnumWndProc, 0);
  if FoundWnd <> 0 then
  begin
    // show the window, eventually
    if not IsWindowVisible(FoundWnd) then
      PostMessage(FoundWnd, wm_User, 0, 0);
    SetForegroundWindow(FoundWnd);

    // grab the file name(s) from the command line parameters
    for i := 1 to ParamCount do
    begin
      fFileName := ParamStr(i);
      // prepare the data to copy
      cds.dwData := 0;
      cds.cbData := (length(fFileName) + 1) * 2;
      cds.lpData := PChar(fFileName);
      // send the data
      SendMessage(FoundWnd, wm_CopyData, 0, Integer(@cds));
    end;
  end;
end;

So the last bit of code is in the main form, to receive this message, and also handle dropping files to it:

// in TJumpListForm class
procedure DropFiles(var Msg: TWmDropFiles); message wm_DropFiles;
procedure CopyData(var Msg: TWmCopyData); message wm_CopyData;

This is the method for receiving the data from the other instance, and calling OpenFile (which in my implementation just adds the file name to a listbox):

procedure TJumpListForm.CopyData(var Msg: TWmCopyData);
var
  Filename: string;
begin
  // restore the window if minimized
  if IsIconic(Application.Handle) then
    Application.Restore;

  // extract the filename from the data
  SetLength(FileName, Msg.CopyDataStruct.cbData);
  StrCopy(PChar(FileName), Msg.CopyDataStruct.lpData);
  Filename := PChar(FileName);
  // open the file
  OpenFile (FileName);
end;

Finally, if you drag and drop a file to the form from Windows resource explorer, it is added to the display, but also to the jump list:

procedure TJumpListForm.DropFiles(var Msg: TWmDropFiles);
var
  nFiles, I: Integer;
  Filename: string;
  jumpItem: TJumpListItem;
begin
  // get the number of dropped files
  nFiles := DragQueryFile(Msg.Drop, $FFFFFFFF, nil, 0);
  // for each file
  try
    for I := 0 to nFiles - 1 do
    begin
      // allocate memory
      SetLength(Filename, 1024);
      // read the file name
      DragQueryFile(Msg.Drop, I, PChar(Filename), 1024 * 2);
      // normalize file
      Filename := PChar(Filename);
      // open the file
      OpenFile(FileName);
      // add file to Jump List
      jumpItem := JumpList1.CustomCategories[0].Items.Add
        as TJumpListItem;
      jumpItem.FriendlyName := ExtractFilename (Filename);
      jumpItem.Arguments := Filename; // full path
    end;
  finally
    DragFinish(Msg.Drop);
  end;
end;

 


This is a sample output of the program:

 


Now this seems quite a lot of work, but this is not required to handle JumpLists in case of a single document interface with multiple instances of the same application. The extra code is required to handle multiple flies or documents or pages in a single instance but the same code you’ll have to write, for example, to implement associating the file extension to your application, or dragging a file over its executable. Once you have that structure, you’ll be ready to implement jump lists in no time.

Conclusion

While the first demo, the TaskBar one, ships with Delphi, the JumpList is an old demo I adapted. Most of the code is here, but the complete source is avaialble at code.marcocantu.com/trac/marcocantu_delphi_sessions_demos/browser/windows/JumpListFilesDemo. So these two combined are a significant feature to let your VCL applications behave properly and look modern in Windows 7 and Windows 8. Adding styles might be next...