Как попасть в System Tray из Visual Basic?

Как попасть в System Tray из Visual Basic?

Эксперимент есть Эксперимент, что здесь еще можно сказать?
(Аркадий и Борис Стругацкие)

О Visual Basic на страницах "Компьютерной газеты" уже было сказано немало. Склоняли эту среду и так, и эдак, критиковали и хвалили, просто рассказывали о возможностях новых версий, сравнивали с конкурирующими разработками.

Однако все сходились на мнении, что она занимает свой сегмент в кругу средств разработки приложений для Windows, обладая достоинствами и недостатками интерпретируемого языка, лежащего в ее основе. И преподносится Visual Basic как самими разработчиками, так и программистами, которые пользуются этим языком, как удобный, дружественный инструмент для создания так называемых Front-end-приложений, то есть программ, позволяющих пользователю получать в консолидированном виде разнородную информацию, подготовленную в других, более специализированных приложениях — например, графическое изображение карты мира с диаграммой и текстовой информацией о демографической обстановке в различных регионах.
В этой статье, как нетрудно догадаться, речь тоже будет идти о Visual Basic. Только в несколько неожиданном для выбранного предмета аспекте. Обсуждаться будет проблема взаимодействия приложения Visual Basic со значком в той области панели задач, которая известна большинству читателей под названием System Tray. В официальной документации эта область называется Task Bar Status Area — область состояния панели задач.

Раньше вы уже могли встретить в "Компьютерной газете" статью "Как попасть в System Tray?". В ней достаточно подробно была рассмотрена API-функция для работы со значками в этой области и даже приводился пример класса на C++, который предоставлял интерфейс для этой цели.
В этой статье будет последовательно показан процесс разработки модуля класса на Visual Basic, который позволит программисту выполнять аналогичные действия в не совсем привычной для этой цели среде.
Чем же так необычна задача, и почему автор так часто об этом упоминает? Чтобы ответить на этот вопрос, нужно вспомнить базовые концепции построения приложения для Windows.
Практически любое приложение, разработанное для выполнения в среде операционной системы Windows, имеет цикл обработки сообщений, главное окно и оконную процедуру, с ним связанную (оговорка "практически" приведена для особенно щепетильных читателей, которые могут заметить, что вырожденное приложение, выводящее на экран диалоговое окно, может и не содержать перечисленных элементов). Взаимосвязь этих компонентов наглядно показана на рис. 1.



Оконная процедура — это блок кода, связанный с окном и принимающий входную информацию от Windows в виде сообщений, которые операционная система генерирует в ответ на события. Примером событий может служить набор текста на клавиатуре, перемещение указателя мыши, изменение размера окна. Приложение тоже может генерировать сообщения, например, для того, чтобы окно развернулось на весь экран или исчезло с рабочего стола.
Сообщение Windows представляет собой структуру данных, состоящую из четырех элементов: манипулятора (дескриптора) окна (window handle), идентификатора сообщения (message Id) и двух 32-битных значений, называемых параметрами сообщения. Манипулятор окна однозначно определяет окно, которому адресовано сообщение.
Как правило, сообщение, прежде чем быть переданным оконной процедуре, сначала попадает в очередь сообщений, которая последовательно обрабатывается циклом обработки сообщений. В этом цикле сообщение может быть подвергнуто предварительной обработке (модифицировано), после чего перенаправляется в оконную процедуру. Оконная процедура должна определить тип поступившего сообщения и решить, нужно ли на него реагировать каким-либо особенным образом, или можно просто вернуть его операционной системе для обработки по умолчанию.
Такова общая схема работы приложения в среде Windows. При чем же тут значки в System Tray? Конечно, ни при чем, если ограничиться их использованием исключительно с целью оповещения пользователя о каких-то особых состояниях программы — например, о том, что идет процесс печати или начат какой-нибудь длительный расчет.

Однако в этой статье автор хочет расширить спектр применения этих значков. Всем известны приложения, работающие в фоновом режиме, единственным напоминанием о которых служит картинка в области состояния панели задач. Но как только пользователь щелкает мышью по этой картинке, приложение "оживает": на экране появляется его главное окно, позволяющее выполнить необходимые настройки.
Каким же образом происходит такое магическое превращение? Снова обратимся к схеме, изображенной на рис.1. Вспомним, что главное окно приложения может существовать в четырех состояниях: максимизированном (развернуто на весь экран), нормальном (занимает часть экрана), минимизированном (отображается кнопка на панели задач) и скрытом (ничто внешне не напоминает о выполнении приложения). Первые три состояния главного окна являются "видимыми", четвертое — "невидимое". Но независимо от того, наблюдаем ли мы окно на рабочем столе, связка "цикл обработки сообщений — оконная процедура" продолжает безостановочно работать до завершения выполнения приложения.
А что же значок в System Tray? Оказывается, при размещении значка в области состояния панели задач можно так настроить его параметры, чтобы он посылал сообщения главному окну, как только указатель мыши переместится на занимаемую значком область. Если оконная процедура сумеет распознать такое сообщение, она может дать команду на отображение главного окна. Такова схема решения поставленной задачи.
Как же претворить эту схему в работающий код, да еще реализовать все это средствами Visual Basic? С размещением значка все выглядит и работает достаточно просто. Visual Basic позволяет вызывать функции Windows API, поэтому нужно с помощью входящей в комплект поставки программы Text API Viewer найти объявление функции Shell_NotifyIcon и включить его в текст разрабатываемой программы.
Для работы со значками лучше всего создать модуль класса. Тогда его можно будет использовать без изменений в самых разных программах. На Visual Basic 4.0 файл SysTrayIcon.cls выглядит следующим образом:

VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "SysTrayIcon"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
'==============================
' Объект — значок в области состояния панели задач (System Tray).
' Свойства: IconData — информация о значке, IsVisible — признак наличия в System Tray.
' Методы: Add — добавить в область состояния, Remove — убрать из области состояния
'=============================
' Константы для работы со значками в области System Tray.
'=============================
Private Const NIM_ADD = &H0
' размещение значка
Private Const NIM_DELETE = &H2
' удаление значка
Private Const NIM_MODIFY = &H1
' изменение значка
Private Const NIF_ICON = &H2
' задан значок
Private Const NIF_MESSAGE = &H1
' задано сообщение
Private Const NIF_TIP = &H4
' задана всплывающая подсказка
Private Const WM_MOUSEMOVE = &H200
' идентификатор сообщения о перемещении мыши
'=============================
'Функция работы со значками в области System Tray.
'Вызов: dwMessage — тип операции над значком, lpData — информация о значке.
' Возврат: 0 — ошибка выполнения операции, <> 0 — операция выполнена успешно.
'=============================
Private Declare Function Shell_No- tifyIcon Lib "shell32.dll" Alias "Shell_NotifyIconA" (ByVal dwMes-sage As Long, lpData As NOTIFYICONDATA) As Long
'=============================
'Информация о значке в области System Tray.
'=============================
Private Type NOTIFYICONDATA
cbSize As Long
' размер структуры
hWnd As Long
' манипулятор окна-хозяина
uID As Long
' идентификатор значка
uFlags As Long
' признаки заполнения полей
uCallbackMessage As Long
' вызываемое сообщение
hIcon As Long
' манипулятор значка
szTip As String * 64
' всплывающая подсказка
End Type
Private IconData As NOTIFYICONDATA

Private IsVisible As Boolean
'признак установки значка в область состояния
'============================
' Установка значка в область состояния панели задач.
' Вызов: Wnd — манипулятор окна, которое будет принимать сообщения, Icon — идентификатор размещаемого значка, Tip — всплывающая подсказка. Message — сообщение, которое будет посылаться окну
'============================
Public Sub Place(Wnd As Long, Icon As Long, Optional Tip As Variant, Optional Message As Variant)
If IsVisible Then Exit Sub
With IconData
.hWnd = Wnd
.hIcon = Icon
If Not IsMissing(Tip) Then .szTip = Tip + Chr$(0)
If Not IsMissing(Message) Then .uCallbackMessage = Message
End With
IsVisible = Shell_NotifyIcon (NIM_ADD, IconData) <> 0
End Sub
'============================
' Удаление значка из области состояния.
'============================
Public Sub Remove()
If Not IsVisible Then Exit Sub
IsVisible = Shell_NotifyIcon (NIM_DELETE, IconData) <> 0
End Sub
'============================
' Установка параметров значка по умолчанию.
'============================
Private Sub Class_Initialize()
IsVisible = False
With IconData
.cbSize = 88
.uID = 1
.uFlags = NIF_ICON Or NIF_MESSA-GE Or NIF_TIP
.uCallbackMessage = WM_MOUSEMOVE
.szTip = Chr$(0)
End With
End Sub
'============================
' Завершение работы со значком в области состояния.
'============================
Private Sub Class_Terminate()
Remove
End Sub

Если не вдаваться в детали описания функции Shell_NotifyIcon, которое можно найти, например, в файле справки по функциям Win32 API, распространяемом с продуктами Microsoft, Borland и других фирм, о реализации класса можно сказать следующее.
В секции описаний (declarations) определены константы, предназначенные для использования в функции Shell_NotifyIcon для указания конкретного действия над значком в System Tray (добавление/удаление/изменение). Там же описан тип структуры данных NOTIFYICONDATA, которая предназначена для хранения информации о значке и передачи ее в функцию Shell_NotifyIcon. Вся эта информация может быть получена с помощью программы Text API Viewer, которая уже упоминалась выше.
Разработанный класс имеет два свойства: IconData и IsVisible. Все операции над значками выполняются посредством вызова методов Place (разместить значок) и Remove (убрать значок), которые предоставляют интерфейс к функции Shell_NotifyIcon. Из особенностей реализации нужно отметить специфику работы со строками при вызове API-функций. Они должны завершаться нулевым символом в соответствии с требованиями языка C.
Использовать этот класс в программе на Visual Basic очень просто. Пусть, например, главная форма приложения названа MainForm. Тогда в нужном месте программы, например, в секции Declarations формы, создается переменная разработанного класса:

Public STI As New SysTrayIcon

а при необходимости размещения значка вызывается метод Place следующим образом:

MainForm.STI.Place MainForm.hWnd, MainForm.Icon, "Hello, World!!!"

где hWnd и Icon — предопределенные стандартные свойства формы, содержащие информацию о манипуляторе окна и значка соответственно.
Читатели могут самостоятельно разработать метод Change, который позволит изменять отображаемый значок, например, для создания эффекта анимации.
Итак, разместить значок в области состояния большого труда не составляет. Более того: если взглянуть на реализацию метода Place, в качестве параметра можно передать идентификатор сообщения, которое будет посылаться окну программы при появлении в области значка курсора мыши. Какое же выбрать сообщение и какому окну его адресовать?
Ответ на этот вопрос и составляет соль статьи. Традиционно поступают следующим образом. В качестве идентификатора сообщения выбирают число из диапазона, зарезервированного в Windows для так называемых пользовательских сообщений. Начинается этот диапазон с идентификатора WM_USER, который вполне подходит для такой цели. Но чтобы посылаемое значком сообщение как-то влияло на процесс выполнения программы, его должна распознать и обработать оконная процедура.
Обратимся теперь к среде Visual Basic. Увы, если синонимом окна в терминах этой среды выступает форма, то среди прочих ее конструкций нет ничего, что напоминало бы классический цикл обработки сообщений или оконную процедуру. Но как-то же программы, разработанные в среде Visual Basic, все же выполняются, и выполняются именно под Windows!

Оказывается, чтобы упростить и ускорить разработку приложений, создатели Visual Basic решили скрыть некоторые элементы образцового Windows-приложения. Конечно же, каждая программа, написанная в этой среде, имеет цикл обработки сообщений, а каждое окно — оконную процедуру. Просто разработчик не может их изменять непосредственно. Если говорить по существу, то методы — обработчики событий формы являются "заглушками", вставленными в код оконной процедуры, связанной с формой. Прописывая вместо этих "заглушек" полезную подпрограмму, разработчик постепенно наращивает функциональность приложения, увеличивает его способность реагировать на внешние события.
К сожалению, такие "заглушки" ограничивают разнообразие типов сообщений, которые приложение может обработать. Посмотрим, что же предложили разработчики Visual Basic. Это обработка событий формы (Load/ Unload, Activate/Deactivate, Paint, Resize и др.), обработка DDE-событий (Link Open/LinkClose, LinkError, LinkExecu-te), обработка событий мыши (Mouse Down, MouseMove, MouseUp) и клавиатуры (KeyDown, KeyPress, KeyUp), а также некоторых других. Но среди всех процедур-обработчиков невозможно выделить такую, которая позволила бы обработать пользовательские сообщения, которые посылает значок, размещенный в System Tray.
Какой же выход возможен в сложившейся почти тупиковой ситуации? Здесь нужно снова обратиться к вопросу о том, какого рода сообщения посылает значок. В документации сказано, что идентификатор сообщения может задаваться при размещении значка с помощью функции Shell_NotifyIcon. При этом первым параметром сообщения является идентификатор значка, назначенный программистом (что позволяет различать сообщения от различных значков, если программа разместила их несколько штук), а вторым — идентификатор события мыши, которое произошло в области значка: перемещение, одинарный или двойной щелчок (см. табл.1).

Таблица 1. Идентификаторы событий мыши, передаваемые в сообщении от значка, размещенного в System Tray.
Событие\Кнопка Левая Правая
Нажата 020116 020416
Отпущена 020216 020516
Двойной щелчок 020316 020616

Что следует из вышесказанного? Для того, чтобы программа, написанная на Visual Basic, могла обработать сообщение от значка, это сообщение должна уметь получать форма, которой оно адресовано. Но форма способна получать только те сообщения, обработка которых заложена разработчиками Visual Basic, и с этим ничего нельзя поделать.
Значит, значок должен посылать такие сообщения, для обработки которых существует подпрограмма-"заглушка"! И это вполне реально, так как идентификатор посылаемого значком сообщения программист может выбрать достаточно произвольно.
Осталось только определить, на место какой "заглушки" лучше всего установить подпрограмму-обработчик. Если просмотреть описания методов обработки событий формы, то можно заметить, что одни из них не имеют параметров вовсе, другие имеют всего один параметр, третьи — принимают два, три или даже четыре параметра.
Логично предположить, что разработчики Visual Basic не изобретали велосипед, и параметры, передаваемые методу-обработчику, как-то связаны с параметрами сообщения Windows. Так как для выполнения поставленной задачи, а именно обработки сообщений от значка в System Tray, необходимо проанализировать параметры сообщения, переданные подпрограмме, нужно выбрать обработчик, принимающий как минимум два 32-битных параметра. Многочисленные эксперименты, проведенные автором статьи, показали, что годными оказались только методы обработки событий мыши, да и то "ограниченно годными" (но об этом — чуть позже).
Итак, для обработки сообщений от значка, размещенного в области состояния панели задач, будем использовать метод MouseMove (перемещение указателя мыши). Он декларируется следующим образом:

Private Sub Form_MouseMove(But- ton As Integer, Shift As Integer, X As Single, Y As Single)

из чего можно предположить, что он способен принять два 32-битных параметра Button и Shift и два 16-битных параметра X и Y. Вроде бы, более чем достаточно для поставленной задачи.
Снова обратимся к справочнику по Windows API. Что представляет собой сообщение о перемещении указателя мыши? В соответствии с соглашениями об идентификаторах, принятых в Windows, это сообщение обозначается WM_MOUSEMOVE и несет с собой следующие параметры: 32-битное значение, информирующее о нажатых в момент возникновения события сдвиговых клавишах (Shift, Ctrl, Alt), и два 16-битных числа, задающих положение курсора на экране.
Учитывая вышесказанное, попробуем проиграть следующую ситуацию. Назначим значку в области SystemTray событие WM_MOUSE, а метод MouseMove напишем таким образом, чтобы можно было трассировать значения принимаемых им параметров. Для этого, например, на главной форме можно разместить элемент управления ListBox, в который обработчик события будет записывать значения параметров.
Практика показала следующее. При появлении указателя мыши в области значка действительно вызывается процедура Form_MouseMove, причем фактические параметры имеют следующие значения. Button принимает значение 1, если приложение выполняется автономно, и 0 — если оно запущено из среды разработки. Shift действительно говорит о том, какая из сдвиговых клавиш была нажата. Параметр X характеризует тип события мыши в соответствии с таблицей 2. Y все время равен нулю.

Таблица 2. Фактические коды событий мыши, принимаемые методом MouseMove при обработке сообщения от значка в System Tray.
Событие\Кнопка Левая Правая
Нажата 1E0F16 1E3C16
Отпущена 1E1E16 1E4B16
Двойной щелчок 1D2D16 1E5A16

К сожалению, прямого соответствия между параметрами сообщения Windows и фактическими значениями параметров процедуры-обработчика не наблюдается. Часть информации, содержащейся в сообщении (например, идентификатор значка), бесследно теряется. Однако даже существующее положение дел позволяет достаточно полно использовать функциональность значка в System Tray.
Еще одно замечание. Продолжая эксперименты с размещением значков в области состояния панели задач и последующей обработкой поступающих от них сообщений, автор обратил внимание на очень неприятную особенность. Если горизонтальные размеры окна, получающего сообщения, превосходят некоторую критическую величину, процедура-обработчик попросту перестает вызываться! Это обстоятельство способно перечеркнуть все достижения, о которых было рассказано выше.
Существует ли какое-нибудь решение проблемы, связанной с обнаруженным явлением? Вспомним о том, что задачей этой статьи был не только выбор подходящего сообщения для использования со значком в System Tray, но и подбор окна, способного эти сообщения принимать и обрабатывать.
Ранее негласно подразумевалось, что сообщения адресуются главному окну приложения, но так как его размеры обычно могут изменяться пользователем в широких пределах, в связи с обнаруженной особенностью приложения от его использования придется отказаться.
Лучше всего создать специальную форму, главной и, возможно, единственной задачей которой будет обработка сообщений от значка. Естественно, раз она нужна только ради своего метода Form_MouseMove, то ее окно лучше всего сделать невидимым на весь период выполнения программы. Однако это не помешает ей получать и обрабатывать сообщения WM_MOUSEMOVE.
В заключение рассмотрим пример, который демонстрирует следующую функциональность. Пусть после запуска приложение, оставаясь невидимым, должно разместить свой значок в System Tray. Если пользователь дважды щелкнет мышью по этому значку, приложение должно отобразить свое главное окно. Если пользователь свернет главное окно, приложение должно сделать его невидимым и снова ждать сообщения от значка.
Приложение должно иметь две формы: главную форму MainForm и вспомогательную (невидимую) форму IconForm.
Главная форма должна уметь делаться невидимой при ее минимизации. Для этого ее метод Form_Resize нужно написать следующим образом:

Private Sub Form_Resize()
If WindowState = vbMinimized Then Hide: Exit Sub
'Далее могут идти операторы обработки остальных ситуаций, 'связанных с изменением размера главной формы.
... ... ...
End Sub

Задачей вспомогательной формы является обработка событий мыши в области значка и ответ на двойной щелчок отображением главной формы. Для этого ее метод Form_MouseMove должен быть реализован так:

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
With MainForm
If Not .Visible And X = 7725 Then
.WindowState = vbNormal
.Show
End If
End With
End Sub

Ну, а для того, чтобы при старте приложения значок попадал в System Tray и функционировал должным образом, нужно создать модуль с процедурой Main, которая должна выполняться сразу после запуска приложения:

Public Sub Main()
MainForm.STI.Place IconForm. hWnd, MainForm.Icon, "Hello, World!!!"
End Sub

Нужно обратить внимание на то, что в качестве окна — получателя сообщений указывается вспомогательная форма, хотя в область состояния панели задач устанавливается значок главной формы. С использованием разработанного класса все выглядит весьма лаконично.
А сейчас пришло время подвести итоги. Итак, в статье был рассмотрен вопрос организации взаимодействия приложения на Visual Basic с размещенным им значком в System Tray. Для управления значками был разработан класс SysTrayIcon. По ходу повествования были рассмотрены основные преграды, встающие на пути разработчика к решению поставленной задачи, и детально описаны методы их преодоления. В результате приведены основные элементы приложения, демонстрирующего размещение и обработку сообщений значка в System Tray.

Игорь Орещенков, 2003 г.


Компьютерная газета. Статья была опубликована в номере 19 за 2003 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета