使用 Windows API - Delphi 中的系统托盘图标有条件地显示多个气球

Display Multiple Balloons conditionally using Windows API - System Tray Icon in Delphi

我编写了一个程序,可以识别 Windows 系统中的过时软件,并通过与用户交互来更新它们。

它有一个软件更新程序,它显示系统托盘图标并显示有关系统中安装的可用/下载更新和软件的气球提示。

问题是当每个任务都在处理时,它不能显示多个气球提示。例如,当软件有可用更新时,它应该记住用户显示像 An update for Software Name is available. 这样的气球,当用户选择下载并再次将其最小化到系统托盘时,气球提示应该再次显示像 [=15] 这样的东西=]

但是我想知道如何只使用一个系统托盘图标来完成此操作?

我可以根据程序的当前状态反复使用 NIM_MODIFY 标志来更改气球提示吗?

我搜索了这个并找到了一些示例,但是对于 Visual Studio 和 C++。

这就是我在程序 运行:

时尝试显示多个提示的方式
unit MainForm-1;

...

const
  NIF_INFO = ;
  NIF_MESSAGE = 1;
  NIF_ICON = 2;
  NOTIFYICON_VERSION = 3;
  NIF_TIP = 4;
  NIM_SETVERSION = [=13=]000004;
  NIM_SETFOCUS = [=13=]000003;
  NIIF_INFO = [=13=]000001;
  NIIF_WARNING = [=13=]000002;
  NIIF_ERROR = [=13=]000003;

  NIN_BALLOONSHOW = WM_USER + 2;
  NIN_BALLOONHIDE = WM_USER + 3;
  NIN_BALLOONTIMEOUT = WM_USER + 4;
  NIN_BALLOONUSERCLICK = WM_USER + 5;
  NIN_SELECT = WM_USER + 0;
  NINF_KEY = ;
  NIN_KEYSELECT = NIN_SELECT or NINF_KEY;

  TRAY_CALLBACK = WM_USER + 58;

  PNewNotifyIconData = ^TNewNotifyIconData;
  TDUMMYUNIONNAME    = record
  case Integer of
       0: (uTimeout: UINT);
       1: (uVersion: UINT);
  end;

  TNewNotifyIconData = record
  cbSize: DWORD;
  Wnd: HWND;
  uID: UINT;
  uFlags: UINT;
  uCallbackMessage: UINT;
  hIcon: HICON;
  szTip: array [0..127] of Char;
  dwState: DWORD; /
  dwStateMask: DWORD; 
  szInfo: array [0..255] of Char; 
  DUMMYUNIONNAME: TDUMMYUNIONNAME;
  szInfoTitle: array [0..63] of Char; 
  dwInfoFlags: DWORD;   
end;

type
  MainForm-1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
private
    IconData: TNewNotifyIconData;
    procedure SysTrayIconMessageHandler(var Msg: TMessage); message TRAY_CALLBACK;
    procedure AddSysTrayIcon;
    procedure ShowBalloonTips;
    procedure DeleteSysTrayIcon;
public
end;

var
  MainForm-1: TForm;

implementation

uses
ShellAPI...,.....,;

procedure MainForm-1.SysTrayIconMessageHandler(var Msg: TMessage);
begin
  case Msg.lParam of
  WM_MOUSEMOVE:;
  WM_LBUTTONDOWN:;
  WM_LBUTTONUP:;
  WM_LBUTTONDBLCLK:;
  WM_RBUTTONDOWN:;
  WM_RBUTTONUP:;
  WM_RBUTTONDBLCLK:;
  NIN_BALLOONSHOW:;
  NIN_BALLOONHIDE:;
  NIN_BALLOONTIMEOUT:
  NIN_BALLOONUSERCLICK:;
 end;
end;

procedure MainForm-1.AddSysTrayIcon;
begin
  IconData.cbSize := SizeOf(IconData);
  IconData.Wnd := AllocateHWnd(SysTrayIconMessageHandler);
  IconData.uID := 0;
  IconData.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
  IconData.uCallbackMessage := TRAY_CALLBACK;
  IconData.hIcon := Application.Icon.Handle;
  IconData.szTip := 'Software Updater is running';
  if not Shell_NotifyIcon(NIM_ADD, @IconData) then
  ShowMessage('System Tray Icon cannot be created.');
end;

procedure MainForm-1.DisplayBalloonTips;
var
  TipInfo, TipTitle: string;
begin
  IconData.cbSize := SizeOf(IconData);
  IconData.uFlags := NIF_INFO;
  if ssHelperState = UpdatesAvailable then TipInfo := 'Updates are available to the programs installed on your Computer' + ' Click to see details.';
  if ssHelperState = UpdatesDownloading then TipInfo := 'Updates are downloading in the background. Click to view the details.';
  strPLCopy(IconData.szInfo, TipInfo, SizeOf(IconData.szInfo) - 1);
  IconData.DUMMYUNIONNAME.uTimeout := 2500;
  if ssHelperState = UpdatesAvailable then TipTitle := 'Updates are Available...';
  if ssHelperState = UpdatesDownloading then TipTitle := 'Downloading the Updates...';
  strPLCopy(IconData.szInfoTitle, TipTitle, SizeOf(IconData.szInfoTitle) - 1);
  IconData.dwInfoFlags := NIIF_INFO; 
  Shell_NotifyIcon(NIM_MODIFY, @IconData);
  {Following code is for testing purpose.}
  IconData.DUMMYUNIONNAME.uVersion := NOTIFYICON_VERSION;
  if not Shell_NotifyIcon(NIM_SETVERSION, @IconData) then
  ShowMessage('Setting the Version is Failed.');
end;

procedure MainForm-1.DeleteSysTrayIcon;
begin
  DeallocateHWnd(IconData.Wnd);
  if not Shell_NotifyIcon(NIM_DELETE, @IconData) then
  ShowMessage('Unable to delete System Tray Icon.');
end;

procedure MainForm-1.FormCreate(Sender: TObject);
begin
  AddSysTrayIcon;
  ShowBalloonTips;
end;

procedure MainForm-1.FormDestroy(Sender: TObject);
begin
  DeleteSysTrayIcon;
end;
...
end. 

但是,这是失败的,当程序 运行 时,我不断收到相同的气球提示(第一个)......

我不知道如何正确使用 NIN_BALLOONSHOWNIN_BALLOONHIDE 标志。因此,在此先感谢您的重要帮助。

为什么要手动声明所有内容? Delphi 2009 已经有 Shell_NotifyIcon() API 的声明。它们在 ShellAPI 单元中。它几乎声明了您尝试使用的所有内容,除了 uVersion 字段(在 Delphi 2010 中添加)。您没有使用 guidItemhBalloonIcon 字段,所以我们不用担心它们。 uTimeout字段是存在的,因为它和uVersion包裹在一个联合中,所以数据大小不会改变,所以当你想使用[=13=时,你可以直接使用uTimeout ] (或者您可以定义自己的联合并对该字段进行类型转换,但那太过分了)。您当然不需要重新声明整个 API.

您每次调用 Shell_NotifyIcon() 时都在重复使用相同的 IconData 变量,这很好,但是您没有清除 szTipszInfoTitle 字段,如果您助手状态不是 UpdatesAvailableUpdatesDownloading,因此托盘图标会一直显示您设置的最后一个 tip/balloon。当您不再需要 tips/balloons 时,您需要清除这些字段。

NIN_BALLOONSHOWNIN_BALLOONHIDE 不是标志。它们是发送到您的托盘图标的已注册 HWND 的通知。要接收通知,您需要填写 WnduCallbackMessage 字段并启用 NIF_MESSAGE 标志。

此外,您需要处理 WM_TASKBARCREATED 消息。如果资源管理器因任何原因(崩溃或被用户终止)重新启动,任务栏将重新创建,因此您必须再次重新添加托盘图标。

此外,请确保您的消息处理程序将任何未处理的 window 消息传递给 DefWindowProc(),或者您可以锁定系统,或者至少锁定您的应用程序。

最后,Delphi 2009 是 Delphi 的 Unicode 版本,但是您的代码的某些部分没有正确处理 Unicode。具体来说,当使用 StrPLCopy() 填充 szTipszInfoTitle 时,您需要使用 Length() 而不是 SizeOf()。副本以字符数而不是字节数表示。

话虽如此,试试这样的东西:

unit MainForm1;

interface

uses
  ..., ShellAPI;

type
  eHelperState = (Idle, UpdatesAvailable, UpdatesDownloading);

  MainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    TaskbarCreatedMsg: UINT;
    IconData: NOTIFYICONDATA;
    IconAdded: Boolean;
    ssHelperState: eHelperState;
    procedure SysTrayIconMessageHandler(var Message: TMessage);
    procedure AddSysTrayIcon;
    procedure ShowBalloonTips;
    procedure DeleteSysTrayIcon;
    procedures SetHelperState(NewState: eHelperState);
    ...
end;

var
  MainForm: TForm;

implementation

const
  TRAY_CALLBACK = WM_USER + 58;
  {$IF RTLVersion < 21}
  NOTIFYICON_VERSION_4 = 4;
  {$IFEND}

procedure MainForm.FormCreate(Sender: TObject);
begin
  TaskbarCreatedMsg := RegisterWindowMessage('TaskbarCreated');
  IconData.cbSize := SizeOf(IconData);
  IconData.Wnd := AllocateHWnd(SysTrayIconMessageHandler);
  IconData.uID := 1;
  AddSysTrayIcon;
end;

procedure MainForm.FormDestroy(Sender: TObject);
begin
  DeleteSysTrayIcon;
  DeallocateHWnd(IconData.Wnd);
end;

procedure MainForm.AddSysTrayIcon;
begin
  IconData.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
  IconData.uCallbackMessage := TRAY_CALLBACK;
  IconData.hIcon := Application.Icon.Handle;
  StrLCopy(IconData.szTip, 'Software Updater is running', Length(IconData.szTip));

  IconAdded := Shell_NotifyIcon(NIM_ADD, @IconData);
  if not IconAdded then
  begin
    ShowMessage('Unable to add System Tray Icon.');
    Exit;
  end;

  if CheckWin32Version(5, 0) then
  begin
    IconData.{$IF RTLVersion >= 21}uVersion{$ELSE}uTimeout{$IFEND} := NOTIFYICON_VERSION_4;
    if not Shell_NotifyIcon(NIM_SETVERSION, @IconData) then
      ShowMessage('Unable to set version for System Tray Icon.');
  end;
end;

procedure MainForm.DisplayBalloonTips;
var
  Tip, InfoText, InfoTitle: string;
begin
  if not IconAdded then Exit;

  case ssHelperState of
    UpdatesAvailable: begin
      Tip := 'Updates are Available. Click to see details.';
      InfoText := 'Updates are available to the programs installed on your Computer. Click to see details.';
      InfoTitle := 'Updates are Available';
    end;
    UpdatesDownloading: begin
      Tip := 'Downloading Updates. Click to see details.';
      InfoText := 'Updates are downloading in the background. Click to see details.';
      InfoTitle := 'Downloading Updates';
    end;
  else
    Tip := 'Software Updater is running';
  end;

  IconData.uFlags := NIF_TIP or NIF_INFO;
  StrPLCopy(IconData.szTip, Tip, Length(IconData.szTip));
  StrPLCopy(IconData.szInfo, InfoText, Length(IconData.szInfo));
  StrPLCopy(IconData.szInfoTitle, InfoTitle, Length(IconData.szInfoTitle));
  IconData.uTimeout := 2500;
  IconData.dwInfoFlags := NIIF_INFO; 

  if not Shell_NotifyIcon(NIM_MODIFY, @IconData) then
    ShowMessage('Unable to update System Tray Icon.')
end;

procedure MainForm.DeleteSysTrayIcon;
begin
  if IconAdded then
  begin
    IconAdded := False;
    if not Shell_NotifyIcon(NIM_DELETE, @IconData) then
      ShowMessage('Unable to delete System Tray Icon.');
  end;
end;

procedures MainForm.SetHelperState(NewState: eHelperState);
begin
  if ssHelperState <> NewState then
  begin
    ssHelperState := NewState;
    DisplayBalloonTips;
  end;
end;

procedure MainForm.SysTrayIconMessageHandler(var Message: TMessage);
begin
  if Message.Msg = TRAY_CALLBACK then
  begin
    case LOWORD(Message.LParam) of
      WM_MOUSEMOVE: begin
        //...
      end;

      WM_LBUTTONDBLCLK,
      NIN_BALLOONUSERCLICK: begin
        // display status window...
      end;

      WM_CONTEXTMENU,
      NIN_KEYSELECT,
      NIN_SELECT: begin
        // display popup menu at coordinates specified by Msg.WParam...
     end;

      NIN_BALLOONSHOW:;
      NIN_BALLOONHIDE:;
      NIN_BALLOONTIMEOUT:;
    end;
  end
  else if (Message.Msg = TaskbarCreatedMsg) and (TaskbarCreatedMsg <> 0) then
  begin
    IconAdded := False;
    AddSysTrayIcon;
    DisplayBalloonTips;
  end
  else begin
    Message.Result := DefWindowProc(IconData.Wnd, Message.Msg, Message.WParam, Message.LParam);
  end;
end;

...

end.