【问题标题】:Display Multiple Balloons conditionally using Windows API - System Tray Icon in Delphi使用 Windows API 有条件地显示多个气球 - Delphi 中的系统托盘图标
【发布时间】:2016-07-29 13:30:04
【问题描述】:

我编写了一个程序,可以识别 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 标志。所以,提前感谢您的重要帮助。

【问题讨论】:

    标签: delphi delphi-2009 system-tray balloon-tip


    【解决方案1】:

    为什么要手动声明所有内容? Delphi 2009 已经声明了Shell_NotifyIcon() API。它们位于ShellAPI 单元中。它声明了您尝试使用的几乎所有内容,除了 uVersion 字段(在 Delphi 2010 中添加)。你没有使用guidItemhBalloonIcon 字段,所以我们不用担心它们。 uTimeout 字段存在,由于它和uVersion 被包裹在一个联合体中,所以数据大小不会改变,所以当你想使用uVersion 时你可以直接使用uTimeout(或者你可以定义你自己的) union 和 type-cast 字段,但这是矫枉过正)。您当然不需要重新声明整个 API。

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

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

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

    另外,请确保您的消息处理程序将任何未处理的窗口消息传递给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 + $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. 
    

    【讨论】:

    • 我知道,但是我手动声明了它们,因为DUMMYUNIONNAME 没有在ShellAPI 版本的Windows 7(这台PC)中声明,据我所知,它是NotifyIconData 的新结构,你能告诉我如何清除szTip 和其他字段吗?我现在还添加了代码的所有其他部分。
    • DUMMYUNIONNAME 非常有用,我添加了它,因为它可以确定系统托盘图标的气球显示持续时间...... :)
    • 您无需重新声明整个Shell_NotifyIcon() API 即可使用版本控制功能。即使uVersion 字段不存在(它是在Delphi 2010 中添加的),也可以在Delphi 2009 中使用它。我已经更新了我的答案以显示这一点。
    • 谢谢,这似乎适用于我的代码。我会试试这个并很快通知你。
    • 我正确添加了代码,但为什么我收到错误 TMessage does not contain a member named 'Message' ?当我开始创建这个项目时,我将Messages 添加到了使用列表中,而不是今天......为什么会这样?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-10
    • 1970-01-01
    相关资源
    最近更新 更多