【问题标题】:EOutOfResources exception when trying to restore tray icon尝试恢复托盘图标时出现 EOutOfResources 异常
【发布时间】:2020-05-28 15:53:36
【问题描述】:

在资源管理器崩溃/重新启动后尝试实施代码以恢复托盘图标时,我收到 EOutOfResources 异常“无法删除外壳通知图标”。我的代码基于找到的旧解决方案 here。尝试隐藏托盘图标时发生异常。为什么下面的 Delphi XE 代码不起作用?

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ImgList, ExtCtrls;

type
  TForm1 = class(TForm)
    TrayIcon1: TTrayIcon;
    ImageListTray: TImageList;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  protected
    procedure WndProc(var Message: TMessage); Override;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  msgTaskbarRestart : Cardinal; {custom systemwide message}  

implementation

{$R *.dfm}

//ensure systray icon recreated on explorer crash
procedure TForm1.FormCreate(Sender: TObject);
begin
  msgTaskbarRestart := RegisterWindowMessage('TaskbarCreated');
end;

procedure TForm1.WndProc(var Message: TMessage);
begin
  if (msgTaskbarRestart <> 0) and (Message.Msg = msgTaskbarRestart) then begin 
    TrayIcon1.Visible := False; {Destroy the systray icon here}//EOutOfResources exception here
    TrayIcon1.Visible := True;  {Replace the systray icon}
    Message.Result := 1;
  end;
  inherited WndProc(Message);
end;

end.

【问题讨论】:

    标签: delphi-xe trayicon


    【解决方案1】:

    TTrayIcon.Visible 请求失败时,TTrayIcon.Visible 属性设置器会引发 EOutOfResources

    procedure TCustomTrayIcon.SetVisible(Value: Boolean);
    begin
      if FVisible <> Value then
      begin
        FVisible := Value;
        ...
    
        if not (csDesigning in ComponentState) then
        begin
          if FVisible then
            ...
          else if not (csLoading in ComponentState) then
          begin
            if not Refresh(NIM_DELETE) then
              raise EOutOfResources.Create(STrayIconRemoveError); // <-- HERE
          end;
          ...
        end;
      end;
    end;
    

    其中Refresh() 只是对Win32 Shell_NotifyIcon() 函数的调用:

    function TCustomTrayIcon.Refresh(Message: Integer): Boolean;
      ...
    begin
      Result := Shell_NotifyIcon(Message, FData);
      ...
    end;
    

    当您收到TaskbarCreated 消息时,您之前的图标不再出现在任务栏中,因此Shell_NotifyIcon(NIM_DELETE) 返回 False。 (重新)创建任务栏后,您根本不应该尝试删除旧图标,只需根据需要重新添加带有Shell_NotifyIcon(NIM_ADD) 的新图标。

    TTrayIcon 有一个公共的Refresh() 方法,但它使用NIM_MODIFY 而不是NIM_ADD,因此在这种情况下也不起作用:

    procedure TCustomTrayIcon.Refresh;
    begin
      if not (csDesigning in ComponentState) then
      begin
        ...
        if Visible then
          Refresh(NIM_MODIFY);
      end;
    end;
    

    但是,当使用TTrayIcon 时,您实际上不需要手动处理TaskbarCreated 消息,因为它已经在内部为您处理了该消息,如果Visible=True,它将调用Shell_NotifyIcon(NIM_ADD)

    procedure TCustomTrayIcon.WindowProc(var Message: TMessage);
      ...
    begin
      case Message.Msg of
        ...
      else
        if (Cardinal(Message.Msg) = RM_TaskBarCreated) and Visible then
          Refresh(NIM_ADD); // <-- HERE
      end;
    end;
    
    ...
    
    initialization
      ...
      TCustomTrayIcon.RM_TaskBarCreated := RegisterWindowMessage('TaskbarCreated');
    end.
    

    如果由于某种原因无法正常工作,和/或您需要手动处理TaskbarCreated,那么我建议直接调用受保护的TCustomTrayIcon.Refresh() 方法,例如:

    type
      TTrayIconAccess = class(TTrayIcon)
      end;
    
    procedure TForm1.WndProc(var Message: TMessage);
    begin
      if (msgTaskbarRestart <> 0) and (Message.Msg = msgTaskbarRestart) then begin 
        if TrayIcon1.Visible then begin
          // TrayIcon1.Refresh;
          TTrayIconAccess(TrayIcon1).Refresh(NIM_ADD);
        end;
        Message.Result := 1;
      end;
      inherited WndProc(Message);
    end;
    

    否则,根本不要使用TTrayIcon。众所周知,它是越野车。这些年来,我看到很多人对TTrayIcon 有很多问题。我建议直接使用Shell_NotifyIcon()。我自己使用它从来没有遇到过任何问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-30
      • 2021-09-01
      相关资源
      最近更新 更多