Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Shell своими руками - System Tray

Автор: Богдан Минич

Иконку в трей помещают с помощью Shell_NotifyIconW. Интересено посмотреть на этот процесс с другой точки зрения.

Цитата с сайта delphi.mastak.ru:

Shell_NotifyIconW просто ищет окно с классом "Shell_TrayWnd" и посылает в него сообщение WM_COPYDATA. в качестве данных выступает простая структура TNIDMessage. возвращаясь к топику: если создать свое окно с классом "Shell_TrayWnd" и обрабатывать входящие сообщения WM_COPYDATA, то можно написать полный аналог system tray! ... (с) paul_shmakov

...чем и займемся.

В первую очередь немаловажное замечание: сообщение посылается только одному окну, то есть наше приложение должно грузится первым. Разные там explorer'ы и другие подобные будут мешать.

Шаг первый:

Создаем окно "Shell_TrayWnd"


procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.WinClassName := 'Shell_TrayWnd';
end;

Все. Окно класса "Shell_TrayWnd" имеем.

Шаг второй:

Ловим WM_COPYDATA


procedure TFORM1.WMCOPYDATA(var Msg: Tmessage);
var
  pcd: PCopyDataStruct;
  NID: PNotifyIconData;
begin
  pcd := PCOPYDATASTRUCT(msg.lParam);
  if pcd^.dwData = 1 then
  begin
    NID := pointer(integer(pcd.lpData) + 8);
    case integer(pointer(integer(pcd.lpData) + 4)^) of
      NIM_ADD: Msg.Result := NewTrayIcon(NID); // добавить иконку
      NIM_DELETE: Msg.Result := DeleteTrayIcon(NID); // удалить иконку
      NIM_MODIFY: Msg.Result := ModifyTrayIcon(NID);
        // изменить иконку (или подсказку)
    end;
    exit;
  end;
end;

Обратите внимание на Msg.Result. Желательно чтобы NewTrayIcon, DeleteTrayIcon, ModifyTrayIcon возвращали Integer(True) или Integer(False) в зависимости от помещения/удаления иконки.
Некоторые приложения не проверяют этот результат, но если начнут проверять - то причины "глючного" поведения иконки того же AVP Monitor можно искать долго и безуспешно.

Шаг третий:

Поймали, и че с ним теперь делать?

А мы имеем очень интересную структуру -
  • NID.cbSize - размер записи, в принципе не интересен;
  • NID.Wnd - хендл окна (владельца иконки);
  • NID.uID - идентификатор иконки (если их в приложении несколько), для данной задачи нужен для отсылки обратного сообщения;
  • NID.uFlags - определяет, какие поля используются в сообщении. Параметр может быть любой комбинацией из флагов (0 - uCallbackMessage, 2 - hIcon, 4 - czTip);
  • NID.uCallbackMessage - номер сообщения, которое посылается окну, определяемому полем NID.Wnd (владельцу). lParam отсылаемого сообщения дожен равняться NID.uID, а wParam сообщение от мыши.
    Пример:
    PostMessage(NID.Wnd, NID.uCallBack, NID.uID, MOUSE_EVENT) где MOUSE_EVENT может принимать значения WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK и подобные для других кнопок мыши.
  • NID.hIcon - хендл иконки, которую собственно и предполагается отображать;
  • NID.szTip - строка, оканчивающаяся нулевым символом, содержит подсказку, которая должна выводится при наведении курсора на иконку.

В случаях ошибки нужно информировать приложения про необходимость поместить иконки обратно. Для этого послужат такие действия:


procedure TForm1.FormCreate(Sender: TObject);
var
  WM_TASKBARCREATED: UINT;
begin
  WM_TASKBARCREATED := RegisterWindowMessage('TaskbarCreated');
  PostMessage(HWND_BROADCAST, WM_TASKBARCREATED, 0, 0);
end;

Не все приложения реагируют на такое сообщение.

Скачать демо проект DemoTray.zip (9.8K)

И несколько слов о демо проекте.

ВНИМАНИЕ!!! Следуйте данным инструкциям только в том случае, если Вы ясно понимаете смысл действий!!!

Повторюсь: Shell_NotifyIconW сообщение посылает только одному окну. Поэтому чтобы увидеть результаты работы демопроекта, загружать его надо без или вместо explorer'а.

Первый вариант (для Win9x): Пример: файл %windir%\system.ini изменить следующим образом:

Найти строчку:


shell=explorer.exe

Заменить на (предполагается что демопроект находится в C:\Demotray\ ) :


;shell=explorer.exe
 shell=c:\demotray\demotray.exe

Перегрузите Windows
Для возврата explorer'a раскомментируйте первую строчку, закомментируйте или удалите вторую.

Второй вариант: Лично я использую Far для выгрузки Explorer.exe

  • 1) Загружаем IDE Delphi и демопроект
  • 2) Загружаем Far, F11->Process list->
    выбираем EXPLORER.EXE->F8->OK
  • 3) Отлаживаем проект
  • 4) Для появления Explorer'a просто запустите его.

Используйте эти инструкции на свой страх и риск. Прочитайте их дважды. Внимательно изучите исходники. Трижды.

Инструкции по закрытию EXPLORER.EXE действительны для Win9x.

Если у Вас NT - разберитесь сами. Если не сможете разобратся - то за такие проекты Вам браться рановато.

Гревные ругательства "А у меня после ... ничего не работает!" не принимаются.

Благодарности:

  • Paul Shmakov - реверсинг Shell_NotifyIcon, моральная поддержка.
  • Стив Тейксейра & Ксавье Пачеко - литература.
  • Особая благодарность обоим использованым алфавитам.
Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay