Исключение EOutOfResources при попытке восстановить значок в трее - PullRequest
0 голосов
/ 28 мая 2020

Я получаю исключение EOutOfResources «Невозможно удалить значок уведомления оболочки» при попытке реализовать код для восстановления значка в трее после сбоя / перезапуска проводника. Мой код основан на старом решении, найденном здесь . Исключение возникает при попытке скрыть иконку в трее. Почему приведенный ниже код 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.

1 Ответ

2 голосов
/ 28 мая 2020

Установщик свойств TTrayIcon.Visible вызывает EOutOfResources, когда запрос NIM_DELETE завершается неудачно:

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 имеет publi c Refresh(), но в нем используется NIM_MODIFY вместо NIM_ADD, так что в этой ситуации он тоже не сработает:

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

Однако на самом деле вам не нужно обрабатывать TaskbarCreated сообщение вручную при использовании TTrayIcon, потому что он уже обрабатывает это сообщение внутри для вас, и он вызовет Shell_NotifyIcon(NIM_ADD), если Visible=True:

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(). У меня никогда не было проблем с его использованием.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...