0

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

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

问题是当每个任务被它处理时,它不能显示多个气球提示。例如,当软件有可用更新时,它应该记住用户显示气球状An update for Software Name is available.,当用户选择再次下载并将其最小化到系统托盘时,气球提示应该再次显示类似Updates are downloading...Click to view the Progress of Downloads.

但是我想知道如何只使用一个系统托盘图标来做到这一点?

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

我对此进行了搜索,发现了一些示例,但适用于 Visual Studio 和 C++。

这就是我在程序运行时尝试显示多个提示的方式:

unit MainForm-1;

...

const
  NIF_INFO = $10;
  NIF_MESSAGE = 1;
  NIF_ICON = 2;
  NOTIFYICON_VERSION = 3;
  NIF_TIP = 4;
  NIM_SETVERSION = $00000004;
  NIM_SETFOCUS = $00000003;
  NIIF_INFO = $00000001;
  NIIF_WARNING = $00000002;
  NIIF_ERROR = $00000003;

  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 = $1;
  NIN_KEYSELECT = NIN_SELECT or NINF_KEY;

  TRAY_CALLBACK = WM_USER + $7258;

  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标志。所以,提前感谢您的重要帮助。

4

1 回答 1

1

为什么要手动声明所有内容?Delphi 2009 已经有Shell_NotifyIcon()API 声明。他们在ShellAPI单位里。它声明了您尝试使用的几乎所有内容,除了uVersion字段(在 Delphi 2010 中添加)。你没有使用guidItemandhBalloonIcon字段,所以我们不用担心它们。该uTimeout字段存在,并且由于它包含在与 的联合中uVersion,因此数据大小不会改变,因此您可以在uTimeout需要时使用uVersion(或者您可以定义自己的联合并对该字段进行类型转换,但这有点过头了)。您当然不需要重新声明整个 API。

IconData您每次调用时都在重用相同的变量Shell_NotifyIcon(),这很好,但如果您的助手状态不是or ,您不会清除szTipand字段,因此托盘图标会继续显示您设置的最后一个提示/气球。当您不再需要提示/气球时,您需要清除这些字段。szInfoTitleUpdatesAvailableUpdatesDownloading

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

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

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

最后,Delphi 2009 是 Delphi 的 Unicode 版本,但是您的代码的某些部分没有正确处理 Unicode。具体来说,在填充szTipszInfoTitle使用时StrPLCopy(),需要使用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 + $7258;
  {$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. 
于 2016-07-29T15:46:24.483 回答