Сообщений 3    Оценка 160        Оценить  
Система Orphus

Расширение оболочки Windows 95 с помощью панелей инструментов рабочего стола

Автор: Jeffrey Richter
Перевод: Алексей Кирюшкин
Источник: Microsoft Systems Journal, March 1996
Опубликовано: 15.03.2002
Исправлено: 30.01.2007
Версия текста: 1.0

Панели инструментов рабочего стола
Свойства панелей
Реализация панели
Делаем автоматически скрывающуюся панель
Z-порядок
Уведомления панелей инструментов рабочего стола
Класс CAppBar
Класс CSRBar

Демонстрационный проект

Оболочка Windows® 95 – это нечто большее, чем просто приятный интерфейс. Она легко настраивается, в отличии от старого Диспетчера Программ. Руководство программиста Microsoft Windows 95 (Microsoft Press, 1995) даст вам идеи относительно многих путей расширения оболочки, но я нахожу этот документ довольно скудным и местами некорректным. Я собираюсь осветить здесь эту тему, исследуя один вид расширений – панели инструментов рабочего стола.

Но сначала давайте точнее определимся с тем, что собственно является “оболочкой”. После загрузки система создает окно рабочего стола, родительское по отношению ко всем окнам верхнего уровня, которые вы будете создавать. Процедура окна рабочего стола находится внутри USER.EXE и ответственна за отображение выбранного пользователем фонового рисунка.

Одна из последних вещей, которую делает Windows 95 при загрузке – просмотр файла SYSTEM.INI. В этом файле есть секция, подобная этой:

[Windows]
Shell=Explorer.exe

Эта запись говорит Windows 95, что нужно запустить на исполнение приложение Explorer.exe (Вы можете изменить эту запись, если предпочитаете иметь другое приложение в качестве оболочки). Explorer.exe – это и есть оболочка Windows 95. Когда Explorer.exe загружен, он создает окно TaskBar (панель задач), по умолчанию расположенное внизу экрана. Explorer.exe также создает дочернее окно, которое заполняет всю рабочую область экрана, но отображает по прежнему фоновый рисунок рабочего стола (термин “рабочая область” будет рассмотрен ниже в данной статье). Это дочернее окно – фактически окно одного из новых элементов управления, ListView, показывающее его содержимое, всегда используя стиль “Крупные значки” (Large Icon). Этот элемент управления ListView – то, о чем мы обычно думаем, как о новой оболочке Windows 95. Поскольку этот элемент управления занимает всю рабочую область экрана двойные щелчки мышкой по ListView больше не вызывают Диспетчер задач (Task Manager), т.к. окно рабочего стола их больше не получает.

Панели инструментов рабочего стола

Панель инструментов рабочего стола (далее просто панель) – окно, которое присоединяет себя к краю экрана. Приложение может иметь больше чем одну панель – это определяется самой программой. Окно панели задач – самый известный пример панели. Это не более чем окно, содержащее несколько дочерних окон: кнопку “Пуск”, окно уведомлений (содержащее часы) и элемент управления SysTabControl32. Отличает панели от других окон то, что они всегда доступны для пользователя. Это означает, что пользователь всегда имеет возможность запускать приложения, находить файлы, запрашивать справку, завершать работу системы и т.д.

В настоящее время очень не многие продукты используют возможности панелей. Фактически, единственный программный продукт, относительно которого я уверен, что он использует собственную панель – это Microsoft® Office для Windows 95. В его комплект входит приложение MSOFFICE.EXE, которое создает окно панели. На Рисунке 1 показаны панель задач оболочки и панель быстрого доступа Microsoft Office.


Рисунок 1 Панель задач и панель быстрого доступа MS Office

Я ожидаю, что, рано или поздно, многие приложения будут использовать панели инструментов рабочего стола. Представьте себе панель с курсом акций, постоянно отображающую текущую торговую активность. Пользователь может легко закрепить панель на краю экрана, и информация всегда будет на виду.

Вот некоторые другие возможные применения панелей инструментов рабочего стола:

Свойства панелей

Хотя панели обычно стыкуются к краю экрана, они также могут быть и плавающими. Чтобы создать место для стыкуемой панели оболочка Windows 95 сокращает размер рабочей области экрана. Рабочая область экрана – это новая концепция в Windows. Рабочая область – это часть экрана, не занятая никакими панелями. Вы можете запросить у системы размеры прямоугольника рабочей области, вызывая функцию SystemParametersInfo:

 RECT rc;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);
// rc содержит экранные координаты рабочей области 

Всякий раз, когда пользователь разворачивает (максимизирует) окно, система гарантирует, что окно заполнит рабочую область, но не весь экран. Каждый раз, когда панель стыкуют к краю экрана, перестыковывают от одного края экрана к другому, отстыковывают от края экрана, система автоматически изменяет размеры и перемещает все максимизированные окна так, чтобы они снова заполняли всю рабочую область. Кроме того, когда изменяются координаты левого верхнего угла рабочей области, система сдвигает все окна на экране так, чтобы сохранить их расположение относительно этого угла.

Хотя система гарантирует, что максимизированные окна ограничены размерами рабочей области экрана, она позволяет немаксимизированным окнам накладываться на панели. При этом их взаимное расположение определяется обычными правилами Z-порядка. И панель задач оболочки, и панель быстрого запуска Microsoft Office поддерживают свойство “Поверх остальных окон”, это же может обеспечивать и Ваша панель.

Поскольку место на экране всегда в цене, панели также предлагают режим под названием “AutoHide” (автоматически убирать с экрана). Если этот режим включен, и панель не используется, она почти полностью скрывается с экрана. Панель располагается так, что большая часть её окна находится за границей экрана, видима только полоска в несколько пикселов. Когда нужно использовать панель пользователь располагает мышь поверх этой полоски. Панель обнаруживает перемещение мыши и выдвигается на экран. Когда мышь удаляется от неё, панель снова скрывается за краем экрана.

Чтобы закрепить панель на месте и не дать ей скрыться, кликните на ней мышкой, чтобы перевести на неё фокус. Для снятия фиксации достаточно просто активизировать другое окно.

Лично мне нравится это свойство – AutoHide и я всегда включаю его для панели задач оболочки. Обратите внимание на одну важную особенность: оболочка позволяет иметь только одну такую панель на каждом краю экрана (см. Рисунок 2). Оболочка следует этому правилу, т.к. две скрытых панели на одном краю экрана наложились бы друг на друга полностью, что сделало бы невозможным активизацию одной из них. Вы можете провести следующий эксперимент, чтобы увидеть все своими глазами: сначала закрепите панель задач внизу вашего экрана, а панель быстрого запуска Microsoft Office вверху экрана, затем включите свойство AutoHide для обеих этих панелей. Теперь перетащите панель Microsoft Office к нижнему краю Вашего экрана, где расположена панель задач. Появится окно сообщений, показанное на Рисунке 2, и панель быстрого доступа Microsoft Office автоматически отключит у себя свойство AutoHide.


Рисунок 2

Панель можно перемещать, захватывая её за клиентскую область окна. Чтобы определить, к какой стороне экрана будет пристыкована панель, представьте себе крест, нарисованный на рабочей области экрана, как показано на Рисунке 3. Сторона, к которой будет пристыкована панель, зависит от того, где, относительно этого креста, она будет отпущена мышкой.


Рисунок 3 Стыковка AppBar к краю экрана

Размеры панелей могут быть изменены пользователем, если это предусмотрено программистом. Например, размеры панели задач можно изменять. При этом если панель задач закреплена сверху или снизу экрана, то поменять можно только высоту, а если справа или слева, то только ширину. Размеры панели быстрого запуска Microsoft Office можно менять только когда она плавающая.

Реализация панели

Я решил создать класс, названный мною CAppBar, который бы инкапсулировал все особенности панелей инструментов рабочего стола так, чтобы можно было достаточно просто создавать панели любого типа. Мой класс CAppBar унаследован от CDialog, так что Вы можете легко создавать панели инструментов на основе шаблона диалогового окна.

Т.к. документация по панелям очень бедна (на самом деле, сказать что она ужасна – значит сделать ей комплимент), я буду должным образом объяснять весь API панелей инструментов рабочего стола. Как использовать мой класс CAppBar для создания ваших панелей я объясню ниже.

Работа с панелями требует только одной функции:

 UINT SHAppBarMessage(DWORD dwMessage,  
                  PAPPBARDATA pabd);

dwMessage сообщает оболочке, зачем Вы вызываете эту функцию. Вы можете передать любой из 10 идентификаторов ABM_xxx (которые мы скоро обсудим) в качестве этого параметра. Параметр pabd – это указатель на структуру APPBARDATA, содержащую дополнительную информацию:

 typedef struct _AppBarData {
   DWORD  cbSize;
   HWND   hWnd;
   UINT   uCallbackMessage;
   UINT   uEdge;
   RECT   rc;
   LPARAM lParam;
} APPBARDATA, *PAPPBARDATA;

При инициализации этой структуры Вы должны занести в cbSize размер структуры APPBARDATA. Остальные члены структуры могут требовать, а могут и не требовать инициализации, в зависимости от значения dwMessage.

Оболочка поддерживает список структур данных, используемый для управления панелями. (Microsoft ожидает, что возможности панелей будут расширены в новых версиях Windows, поэтому эта информация может измениться.)

 struct INTERNALAPPBARDATA {
   HWND hWnd;
   UINT uCallbackMessage;
   UINT uEdge;
   RECT rc;
};

Одна структура в этом списке соответствует одному из окон панелей. Если Вы хотите, чтобы ваше окно было панелью инструментов, Вы должны зарегистрировать его в оболочке, проинициализировав структуру APPBARDATA и вызвав SHAppBarMessage следующим образом:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowToRegister;
abd.uCallbackMessage = WM_USER + 100;
// uEdge, rc, lParam не используются
SHAppBarMessage(ABM_NEW, &abd);

Сообщение ABM_NEW говорит оболочке, что надо добавить структуру к её внутреннему списку. Члены hWnd и uCallbackMessage внутренней структуры инициализируются значениями, передаваемыми в APPBARDATA, но никакая информация о положении окна в этот момент не сохраняется. Если окно, чей хэндл мы передаем, уже зарегистрировано оболочкой, SHAppBarMessage ничего не делает, а просто возвращает FALSE.

Когда Ваше окно уничтожается, его нужно разрегистрировать в оболочке следующим образом:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowToUnregister;
// uCallbackMessage, uEdge, rc, lParam не используются
SHAppBarMessage(ABM_REMOVE, &abd);

Сообщение ABM_REMOVE говорит оболочке, что нужно удалить соответствующую структуру данных из её внутреннего списка. hWnd указывает на окно, которое нужно удалить. Перед самым выходом из SHAppBarMessage всем существующим панелям посылается уведомление ABN_POSCHANGED, чтобы они могли заново позиционироваться. Например, они могут придвинуться к краю экрана, если панель, которая была на этом месте разрегистрируется. Уведомления панелей будут обсуждаться далее в этой статье.

Теперь, как Вы можете сообщить оболочке, в каком месте экрана нужно расположить Вашу панель? Это не настолько просто и прямо, как Вам наверное нравится, т.к. нужно гарантировать, что Ваша панель не наложится на одну из существующих. Для начала вызовите SHAppBarMessage, указав нужную Вам сторону и прямоугольник с экранными координатами окна панели:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowToPosition;
abd.uEdge = uEdgeToDockOn;
SetRect(&abd.rc, 0, 0, GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN));
// uCallbackMessage, lParam не используются
SHAppBarMessage(ABM_QUERYPOS, &abd);

Сообщение ABM_QUERYPOS заставляет SHAppBarMessage сравнить затребованные Вами координаты прямоугольника с положением всех существующих панелей. Если Ваш запрос конфликтует с любой существующей панелью, SHAppBarMessage изменяет координаты переданного прямоугольника. Обратите внимание, что я всегда передаю прямоугольник, которые описывает полный экран – это многое упрощает. По мере того, как SHAppBarMessage циклически перебирает все зарегистрированные панели, она уменьшает размеры прямоугольника. Когда SHAppBarMessage завершается, abd.rc.left определяет крайний левый пиксел, который может быть использован моим окном, если я захочу пристыковаться к левой стороне, abd.rc.top определяет самый верхний пиксел, который может быть использован моим окном, если я захочу пристыковаться к верху экрана, и т.д.

Как только Вы узнаете, где сможете расположить свою панель, вычислите её размеры:

 switch (abd.uEdge) {
   case ABE_LEFT:
     abd.rc.right = abd.rc.left + uWidthWhenDockedOnLeft;
     break;

    case ABE_TOP:
     abd.rc.bottom = abd.rc.top + uHeightWhenDockedOnTop;
     break;

   case ABE_RIGHT:
     abd.rc.left = abd.rc.right-uWidthWhenDockedOnRight;
     break;

   case ABE_BOTTOM:
     abd.rc.top=abd.rc.bottom-uWidthWhenDockedOnBottom;
     break;
}

Получив координаты своей панели, вызывайте SHAppBarMessage снова:

// Оставляем все члены такими, какими они были при
// посылке предидущего сообщения ABM_QUERYPOS
// uCallbackMessage, lParam не используются
SHAppBarMessage(ABM_SETPOS, &abd);

На сей раз ABM_SETPOS сообщает оболочке, где Вы действительно хотите расположить Вашу панель. Когда Вы посылаете сообщение ABM_SETPOS, SHAppBarMessage рекурсивно вызывает себя (только один раз, к Вашему сведению) с сообщением ABM_QUERYPOS. Это необходимо, т.к. Windows 95 – система с приоритетной многозадачностью. В то время, пока Вы вычисляли размер прямоугольника Вашего окна, другой поток мог зарегистрировать или разрегистрировать в оболочке окно своей панели возможно на том же месте, где Вы хотите расположить Ваше.

После того, как оболочка определилась с месторасположением окна Вашей панели, она записывает данные о крае экрана, к которому стыкуется Ваша панель и координаты прямоугольника ее окна в данные своей внутренней структуры и рассылает уведомление ABN_POSCHANGED всем другим зарегистрированным панелям.

Когда SHAppBarMessage закончит обработку сообщения ABM_SETPOS, Вы должны будете вызвать SetWindowPos, чтобы Ваша панель переместилась на нужное место:

 SetWindowPos(abd.hWnd, NULL, abd.rc.left, abd.rc.top,
     abd.rc.right-abd.rc.left,abd.rc.bottom-abd.rc.top,
     SWP_NOZORDER | SWP_NOACTIVATE);

Это целиком Ваша ответственность – перемещать окно Вашей панели. SHAppBarMessage никогда не перемещает и не меняет размеры окон панелей.

Делаем автоматически скрывающуюся панель

Оболочка дает очень слабую поддержку автоматически скрывающихся панелей. Например, было бы очень хорошо, если б она помогала обнаружить, когда окно панели должно выехать на экран или скрыться с экрана, а также собственно скрывала и показывала окно панели. Но, оболочка ничего этого не делает, так что Вы должны написать весь код для этого самостоятельно. Все, что делает оболочка – поддерживает свой внутренний список автоматически скрывающихся панелей. Список состоит из структур данных, которые связывают HWND и стороны экрана. Каждый HWND идентифицирует хэндл окна автоматически скрывающейся панели, пристыкованной к краю экрана.

Вот как можно сказать оболочке, что окно Вашей панели будет скрываться за определенной стороной экрана:

APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowToMakeAutohide;
abd.uEdge = uEdgeToAutoHideOn;
abd.lParam = TRUE;      // Устанавливаем свойство AutoHide
// uCallbackMessage, rc не используются
SHAppBarMessage(ABM_SETAUTOHIDEBAR, &abd);

Когда Вы вызываете SHAppBarMessage с сообщением ABM_SETAUTOHIDEBAR, оболочка сразу же проверяет, связан ли с заданным в члене uEdge структуры APPBARDAT краем экрана хэндл окна одной из существующих панелей. Если с этим краем не связано хэндла окна, оболочка запоминает Ваше окно как автоматически скрывающееся и SHAppBarMessage возвращает TRUE. Если же уже есть другое окно, запомненное как AutoHide, SHAppBarMessage ничего не делает и возвращает FALSE.

Когда Вы захотите отключить свойство AutoHide для окна Вашей панели, сделайте следующее:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowToMakeNotAutohide;
abd.uEdge = uEdgeToAutohideOn;
abd.lParam = FALSE;    //Отключаем свойство AutoHide
// Ignored members: uCallbackMessage, rc
// uCallbackMessage, rc не используются
SHAppBarMessage(ABM_SETAUTOHIDEBAR, &abd);

Единственное отличие – в том, что lParam устанавливается в FALSE. Это говорит оболочке, что нужно прекратить обрабатывать эту панель как автоматически скрывающуюся за указанным краем экрана. При снятии свойства AutoHide SHAppBarMessage всегда возвращает TRUE.

Знайте, что оболочка не использует два своих внутренних списка совместно, чтобы освободить Вас от дополнительной работы. Скажем, Вы регистрируете панель сообщением ABM_NEW и затем устанавливаете ее как AutoHide сообщением ABM_SETAUTOHIDEBAR. SHAppBarMessage могла бы изменить размеры рабочей области экрана, но она этого не делает. Или, когда другая панель посылает сообщения ABM_QUERYPOS или ABM_SETPOS, SHAppBarMessage должна бы видеть, что Ваша панель запомнена как AutoHide и ее размеры не нужно учитывать при обработке этих сообщений. И снова этого не происходит.

Итак, Вам придется сделать дополнительную работу. Существуют два пути, чтобы сделать панель автоматически скрывающейся с экрана.

Во-первых, можно разрегистрировать панель посылкой сообщения ABM_REMOVE. Т.к. при этом панель будет удалена из внутреннего списка оболочки, она больше не будет учитываться при определении размеров рабочей области экрана и влиять на позицию других панелей.

Другой путь – оставить панель зарегистрированной, но изменить зарегистрированные размеры панели так, чтобы оболочка думала, что окно Вашей панели 0 пикселов в ширину и 0 пикселов в высоту. Этот метод я применил в моём классе CAppBar.

 if (IsBarAutoHidden()) {
   SHAppBarMessage(ABM_SETPOS, ABE_LEFT,
                   FALSE, &CRect(0, 0, 0, 0)); ...

Иногда бывает нужно запросить у оболочки, какое именно окно скрывается за определенным краем экрана:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndofOurAppBar;
abd.uEdge = uEdgeToCheck;
// uCallbackMessage, rc, lParam не используются
HWND hwndAutohide = (HWND)
   SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);

Значение, возвращаемое SHAppBarMessage нужно привести к HWND. Если функция возвращает NULL, за указанным краем экрана не скрыта никакая панель. (В руководстве программиста Microsoft Windows 95 сказано, что нужно инициализировать hWnd перед посылкой сообщения ABM_GETAUTOHIDEBAR. В настоящее время SHAppBarMessage игнорирует hWnd при обработке сообщения ABM_GETAUTOHIDEBAR. Однако мне сказали, что будущие версии оболочки будут использовать hWnd. Так что учтите этот момент, и всегда корректно инициализируйте hWnd при посылке сообщения ABM_GETAUTOHIDEBAR.)

Время от времени Вам нужно будет запросить оболочку об ее панели задач. Например, чтобы выяснить, установлены ли для нее свойства “Поверх всех окон” или “Автоматически убирать с экрана” можно сделать следующее:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
// hwnd, uCallbackMessage, uEdge, rc, lParam не используются
BOOL fTaskBarIsAlwaysOnTop = 
   SHAppBarMessage(ABM_GETSTATE, &abd) & ABS_ALWAYSONTOP;
BOOL fTaskBarIsAutohide = 
   SHAppBarMessage(ABM_GETSTATE, &abd) & ABS_AUTOHIDE;

А так можно узнать позицию панели задач:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
// hwnd, uCallbackMessage, uEdge, rc, lParam не используются
SHAppBarMessage(ABM_GETTASKBARPOS, &abd);
// abd.rc содержит экранные координаты прямоугольника панели задач.

Z-порядок

Настало время рассмотреть Z–упорядочивание панелей инструментов рабочего стола. Каждый раз, когда окно панели получает сообщение WM_ACTIVATE, панель должна уведомить оболочку, выполняя следующий код:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowBeingActivated;
// uCallbackMessage, uEdge, rc, lParam не используются
SHAppBarMessage(ABM_ACTIVATE, &abd);

По получению сообщения ABM_ACTIVATE оболочка выдвигает все скрытые за краями экрана панели на передний план. Зачем? Представьте себе, что есть две панели, пристыкованные к нижнему краю экрана. Обе они находятся на вершине Z–порядка, но только для одной установлено свойство AutoHide. Далее, если пользователь работал с нескрывающейся панелью, система активизирует её окно, и оно закрывает собой окно AutoHide-панели, после чего пользователь больше не может получить к нему доступ. Этой ситуации можно избегнуть, вызывая SHAppBarMessage с сообщением ABM_ACTIVATE.

Чтобы быть уверенным, что AutoHide-панели всегда будут поверх не скрывающихся панелей, Ваша панель должна контролировать также сообщение WM_WINDOWPOSCHANGED. Каждый раз при его получении Вы должны уведомить оболочку:

 APPBARDATA abd;
abd.cbSize = sizeof(abd);
abd.hWnd = hwndOfWindowJustPositioned;
// uCallbackMessage,  uEdge, rc, lParam не используются
SHAppBarMessage(ABM_WINDOWPOSCHANGED,  
                &abd);

Уведомления панелей инструментов рабочего стола

Периодически оболочке требуется посылать уведомления всем панелям инструментов рабочего стола. Когда Вы добавляете панель к внутреннему списку оболочки, посылая сообщение ABM_NEW, Вы указываете идентификатор сообщения для обратного вызова. Если оболочке нужно уведомить о чем-то Вашу панель, она посылает ей сообщение с заданным Вами идентификатором. Когда панель получает это сообщение, в качестве параметра wParam в нем будет код одного из уведомлений: ABN_FULLSCREENAPP, ABN_POSCHANGED, ABN_STATECHANGE, или ABN_WINDOWARRANGE.

Уведомление ABN_FULLSCREENAPP рассылается, когда одно из окон приложений максимизируется или возвращается из максимизированного состояния к исходному размеру. Клиентская область максимизированного окна занимает весь экран. Панели с установленным свойством “Поверх всех окон” должны убрать себя с вершины Z–порядка, чтобы не закрывать максимизированное окно. lParam равен TRUE если окно максимизируется и FALSE, если возвращается к исходному размеру.

К сожалению, есть серьезная ошибка при работе оболочки с уведомлением ABN_FULLSCREENAPP: панели получат это уведомление, только если для панели задач оболочки установлено свойство “Поверх всех окон” и не установлено “Автоматически убирать с экрана”. Если же для панели задач не установлено свойство “Поверх всех окон” или установлено “Автоматически убирать с экрана” Ваша панель не получит надлежащий набор уведомлений ABN_FULLSCREENAPP. Очевидно, что Ваша панель должна получать эти уведомления независимо от установок панели задач. Из Microsoft мне сообщили, что эта ошибка будет исправлена в следующих версиях Windows.

Уведомление ABN_POSCHANGED посылается, когда любая панель изменяет свой размер, вызывая SHAppBarMessage с сообщением ABM_SETPOS. Это уведомление также посылается, когда одна из панелей удаляется, посылая сообщение ABM_REMOVE, а также когда для панели задач включено свойство “Автоматически убирать с экрана” и пользователь заставляет ее скрываться или появляться на экране. Вы должны использовать это уведомление, чтобы Ваша панель переустановила себя, посылая сообщение ABM_SETPOS. Помните, что оболочка не посылает уведомление ABN_POSCHANGED той панели, которая посылала сообщение ABM_SETPOS. Параметр lParam не используется в этом уведомлении.

Уведомление ABN_STATECHANGE указывает, что пользователь изменил состояние панели задач. Используя это уведомление, можно сделать так, чтобы состояние Вашей панели всегда соответствовало состоянию панели задач оболочки, так, если пользователь устанавливает для панели задач свойство “Поверх всех окон” или “Автоматически убирать с экрана”, Ваша панель также устанавливает их для себя.

К сожалению, работа с уведомлением ABN_STATECHANGE выявляет другую ошибку в системе. Когда пользователь изменяет состояние панели задач, Вы должны получить единственное уведомление ABN_STATECHANGE; Фактически же Вы получаете уведомление для каждого свойства, поддерживаемого панелью задач. Так как панель задач поддерживает два свойства (“Поверх всех окон” и “Автоматически убирать с экрана”), Вы получите это уведомление дважды, даже если пользователь изменил только одно свойство панели задач. Microsoft утверждает, что это ошибка будет исправлена после первой беты Windows NT™ 4.0 и в следующих версиях Windows 95.

Такое поведение может заставить Вас столкнуться с проблемами. Представьте, что панель задач и Ваша панель пристыкованы к нижней части экрана. Для панели задач включено свойство “Автоматически убирать с экрана”. Пользователь открывает диалог свойств панели задач (см. Рисунок 4) и отключает оба свойства – “Поверх всех окон” и “Автоматически убирать с экрана”. При этом, внутри себя оболочка сначала отключает свойство “Поверх всех окон” и немедленно рассылает уведомление ABN_STATECHANGE. Т.к. Вы хотите, чтобы Ваша панель во всем подражала панели задач, она отвечает на получение этого уведомления сообщением ABM_GETSTATE. Результаты этого сообщения указывают, что свойство “Поверх всех окон” выключено. Т.к. оболочка еще не дошла до изменения свойства “Автоматически убирать с экрана” Ваша панель будет думать, что оно включено и попробует скрыть себя также. Т.к. не может быть двух скрытых панелей на одном краю экрана, появится окно сообщений, показанное на Рисунке 2. Пользователь будет слегка запутан, когда появится это сообщение в ответ на его попытку отключить свойство “Автоматически убирать с экрана”.


Рисунок 4 Свойства панели задач

Мой класс CAppBar решает эту проблему, запоминая текущее состояние панели задач и сравнивая его с новым состоянием, полученным по сообщению ABM_GETSTATE. В этом случае для моей панели я изменяю только то свойство, которое действительно изменилось у панели задач. Я реализовал все это в моем классе CAppBar так, чтобы он мог нормально работать в будущих версиях Windows.

Последнее уведомление, ABN_WINDOWARRANGE, посылается, когда пользователь выбирает из меню панели задач одно из действий – расположить окна каскадом, сверху вниз или слева направо. Сразу после того, как пользователь выбрал одно из этих действий, оболочка посылает это уведомление с lParam установленным в TRUE, указывая, что она собирается переустанавливать все окна. После того, как все панели обработают это уведомление, оболочка переустанавливает окна и посылает другое уведомление ABN_WINDOWARRANGE. На сей раз, lParam установлен в FALSE, чтобы указать, что все окна были перемещены. Панель задач использует это уведомление, чтобы запомнить положение всех окон перед их перемещением. Это позволяет панели задач предлагать опцию Undo, чтобы окна могли быть возвращены к первоначальным позициям.

Класс CAppBar

Мой класс CAppBar инкапсулирует все особенности новых панелей инструментов рабочего стола. Этот класс был определенно одним из самых трудных и стимулирующих проектов, над которым я работал долгое время. Если Вы посмотрите исходный текст, Вы увидите, что я был чрезвычайно либеральным с комментариями. Полный исходный текст слишком велик, чтобы приводить его здесь, его можно посмотреть в демонстрационном проекте. В Листинге 1 приведен заголовочный файл AppBar.h, содержащий описание класса CAppBar. Сейчас я расскажу, как этот класс реализован.

Листинг 1 AppBar.h

В начале определения класса находится public секция, в которой определены четыре статических вспомогательных функции. Это функции, специфичные для панелей, но Вам не обязательно создавать экземпляр класса CAppBar, чтобы воспользоваться ими. Первые две функции, IsEdgeLeftOrRight и IsEdgeTopOrBottom, удобны для проверки, является ли край, к которому пристыкована панель левым/правым, или верхним/нижним.

ResetSystemKnowledge – интересная функция, облегчающая отладку классов, производных от CAppBar. Эта функция вызывается в конструкторе CAppBar, но поскольку функция статическая, она может вызываться в любое время. ResetSystemKnowledge заставляет оболочку посылать уведомление ABN_POSCHANGED всем зарегистрированным в системе, чтобы они были правильно переустановлены.

 void CAppBar::ResetSystemKnowledge (void) {
   #ifdef _DEBUG
   // Только для отладочных сборок
   APPBARDATA abd;
   abd.cbSize = sizeof(abd);
   abd.hWnd = NULL;
   ::SHAppBarMessage(ABM_REMOVE, &abd);
   #endif
 }

Как Вы видите, эта функция просто посылает сообщение оболочке, чтобы удалить окно, чей хэндл равен NULL. Такого окна в системе никогда не будет, но оболочка все равно разошлет уведомление ABN_POSCHANGED всем зарегистрированным в системе панелям. Так о чем же мы беспокоимся? Предположим, что Вы отлаживаете класс, производный от CappBar, и его окно закреплено на краю экрана. Если Вы прерываете отладку, окно разрушается, но сообщение ABM_REMOVE при этом оболочке не посылается. Оболочка продолжает считать, что Ваше окно все еще занимает часть экрана. На экране появляется пустой прямоугольник, уменьшающий размеры рабочей области. Когда Вы в следующий раз запустите для отладки своё приложение, будет вызвана ResetSystemKnowledge и рабочая область экрана будет сконфигурирована должным образом.

Последняя вспомогательная статическая функция – GetEdgeFromPoint. Она получает в качестве параметров набор флажков ABF_XXX (определеный в AppBar.h) и экранные координаты точки. Функция сравнивает координаты точки с координатами рабочей области экрана, чтобы определить, к какой стороне экрана она ближе всего – к левой, к вершине, к правой или к нижней. Затем GetEdgeFromPoint проверяет переданные флажки для выяснения сторон экрана, к которым может стыковаться панель, а также должна ли она быть плавающей. Функция возвращает ABE_LEFT, ABE_TOP, ABE_RIGHT, ABE_BOTTOM, или ABE_FLOAT.

CAppBar имеет также protected-секцию, в которой определены внутренние переменные. В общем случае, производный класс не должен изменять любую из этих переменных непосредственно, за исключением m_fdwFlags. Эта переменная поможет Вам более тонко настроить поведение CAppBar. Присвойте этой переменной комбинацию из флагов, перечисленных в Таблице 1. Для большинства производных от CAppBar классов можно установить ABF_ALLOWANYWHERE и не использовать другие флаги. Вы должны определить по крайней мере один из флагов ABF_ALLOWLEFTRIGHT, ABF_ALLOWTOPBOTTOM, или ABF_ALLOWFLOAT, иначе Ваша панель не будет правильно работать, т.к. Вы сообщаете классу CAppBar, что Ваша панель не может появиться нигде.

FlagDescription
ABF_ALLOWLEFTRIGHTРазрешает панели пристыковываться к левой или правой стороне экрана.
ABF_ALLOWTOPBOTTOMРазрешает панели пристыковываться к верхней или нижней стороне экрана.
ABF_ALLOWANYEDGEТоже, что и (ABF_ALLOWLEFTRIGHT | ABF_ALLOWTOPBOTTOM).
ABF_ALLOWFLOATРазрешает панели находится в плавающем, непристыкованном состоянии.
ABF_ALLOWANYWHERE(ABF_ALLOWANYEDGE | ABF_ALLOWFLOAT)
ABF_MIMICTASKBARAUTOHIDEПанель должна отслеживать уведомление ABN_STATECHANGE и синхронизировать свое свойство “Автоматически убирать с экрана”, с аналогичным свойством панели задач.
ABF_MIMICTASKBARALWAYSONTOPПанель должна отслеживать уведомление ABN_STATECHANGE и синхронизировать свое свойство “Поверх всех окон”, с аналогичным свойством панели задач.
Таблица 1 Возможные значения переменой m_fdwFlags.

Класс CAppBar также определяет защищенную структуру APPBARSTATE и переменную этого типа:

 typedef struct {
   DWORD m_cbSize;         // Размер этой структуры
   UINT  m_uState;         // ABE_UNKNOWN, ABE_FLOAT, или 
                           // ABE_сторона_экрана
   BOOL  m_fAutoHide;      // Должена ли панель автоматически скрываться с экрана
// в пристыкованном состоянии
BOOL m_fAlwaysOnTop; // Должна ли панель быть “Поверх всех окон” UINT m_auDimsDock[4]; // Ширина/высота панели при стыковке к каждой из // 4-х сторон экрана CRect m_rcFloat; // Прямоугольник панели в плавающем состоянии // (в экранных координатах) } APPBARSTATE, *PAPPBARSTATE; APPBARSTATE m_abs; // Информация о свойствах панели

Эта структура – закулисная движущая сила CAppBar. Она используется производными классами, но производные классы никогда не должны менять ее напрямую – существуют функции-члены CAppBar (описанные в public-секции), которые позволяют производному классу изменять эту переменную. Фактически, почти все функции-члены так или иначе изменяют содержание этой переменной. При инициализации производного класса он должен создать переменную типа APPBARSTATE, инициализировать все ее члены и, после этого вызвать функцию базового класса SetState:

 void SetState (APPBARSTATE& abs);

Эта функция инициализирует внутреннюю переменную m_abs класса CAppBar. SetState также уведомляет оболочку о месторасположении панели, закрепленная она или плавающая, включены ли свойства “Автоматически убирать с экрана” и “Поверх всех окон”. Другими словами эта функция делает много работы.

Производный класс получает текущее состояние переменой m_abs, вызывая GetState:

 void GetState (APPBARSTATE* pabs);

Существуют дополнительные перегруженные варианты функций SetState и GetState, также как и других функций, для удобства использования – все они управляют внутренней переменой m_abs.

Класс CAppBar имеет protected-секцию, озаглавленную “Функции для внутреннего употребления”. Как можно предположить из названия, эти функции существуют для использования внутри класса CAppBar. Вы можете найти эти функции полезными, но вероятнее всего, они не будут Вам нужны.

Функция-член SHAppBarMessage является оберткой над Win32®-функцией SHAppBarMessage. Она введена для удобства, вот почему у неё так много параметров по умолчанию. Эта функция создает переменную типа APPBARDATA, инициализирует её и вызывает Win32 API функцию SHAppBarMessage.

CalcProposedState вызывает GetEdgeFromPoint, передавая ей переменую-член m_fdwFlags и точку. Эта функция также проверяет, не удерживает ли пользователь нажатой клавишу Ctrl, превращая пристыковываемые панели в плавающие. Эта особенность позволяет оставлять панель в плавающем состоянии, даже если она располагается очень близко к краю экрана.

Функция GetRect используется повсюду в реализации класса CAppBar. Эта функция принимает в качестве параметров требуемое состояние панели и месторасположение панели на экране. Функция посылает оболочке сообщение ABM_QUERYPOS и определяет, где на самом деле может быть расположена панель. Прямоугольник, переданный в параметре prcProposed, при этом модифицируется в соответствии с допустимым месторасположением панели на экране.

Функции ShowHiddenAppBar и SlideWindow используются вместе. ShowHiddenAppBar показывает или скрывает панель, если она должна автоматически скрываться с экрана. Если для панели не установлено свойство “Автоматически скрываться с экрана”, функция ничего не делает. Если это свойство установлено, функция определяет новое местоположение панели и затем вызывает SlideWindow, которая выдвигает или задвигает панель. Функция SlideWindow применима не только к панелям. Вы легко можете взять эту функцию и включить в другое приложение, если Вам нужно будет двигать окно от одного положения к другому.

В классе CAppBar также есть несколько защищенных виртуальных функций. В производном от CAppBar классе, возможно, потребуется перегрузить некоторые из этих функций, чтобы получить доступ к уведомлениям, обрабатываемым внутри базового класса.

Первая перегружаемая функция, OnAppBarStateChange, уведомляет производный класс, что панель только что изменила своё состояние. Производный класс может использовать это уведомление, чтобы добавить заголовок к окну панели, когда она в плавающем состоянии, или убрать его, когда панель пристыкована. Реализация этой функции в базовом классе не делает ничего.

Функция OnAppBarForcedToDocked уведомляет производный класс, что автоматически скрывающуюся панель попробовали пристыковать на краю, где уже есть скрытая панель. Скорее всего производному классу не нужно будет реализовывать эту функцию, т.к. функция базового класса отображает окно сообщений, показанное на Рисунке 2. Когда базовый класс отображает это окно, он сначала считывает текст заголовка окна панели и затем использует этот текст в качестве заголовка окна сообщений. Текст заголовка у Вашей панели будет в любом случае, даже если сам заголовок не отображается как часть окна панели.

Последние четыре перегружаемые функции вызываются, когда панель получает одно из четырех ABN_XXX уведомлений. Обычно производный класс не будет реализовывать эти функции, а будет использовать вариант из базового класса.

Когда вызывается OnABNFullScreenApp, реализация базового класса отслеживает, если максимизированные окна, и корректирует соответственно этому Z-порядок панели.

Когда вызывается OnABNPosChanged, реализация базового класса корректирует положение окна панели.

При вызове OnABNStateChanged эта функция принимает битовую маску, показывающую, какое свойство панели задач было изменено, и каковы новые свойства. Реализация базового класса просто вызывает функцию MimicState, передавая ей туже самую информацию, а она устанавливает свойства Вашей панели аналогично свойствам панели задач, в зависимости от того, установлены ли флаги ABF_MIMICTASKBARAUTOHIDE или ABF_MIMICTASKBARALWAYSONTOP. Класс CAppBar рассматривает только те свойства панели задач, которые действительно изменились.

Когда вызывается OnABNWindowArrange, реализация базового класса не делает ничего. Большинству производных классов также не нужно будет заботиться об этом уведомлении и перегружать эту функцию.

Все оставшиеся функции-члены CAppBar – обработчики оконных сообщений. (см. Таблицу 2).

Message HandlerDescription
OnCreateУстанавливает таймер, определяющий, когда нужно задвигать автоматически скрывающееся окно.
Регистрирует панель посылкой сообщения ABM_NEW
Вызывает MimicState
OnDestroyОстанавливает таймер.
Разрегистрирует панель посылкой сообщения ABM_REMOVE
OnWindowPosChangedВыдвигает панель на передний план посылкой сообщения ABM_WINDOWPOSCHANGED
OnActivateПри деактивации панели, задвигает панель, если она должна автоматически скрываться.
Выдвигает автоматически скрывающуюся панель на передний план посылкой сообщения ABM_ACTIVATE
OnTimerЗадвигает панель (если она должна автоматически скрываться) за край экрана, если она не активна и поверх неё нет курсора мыши.
OnNcMouseMoveВыдвигает скрытую панель на экран.
OnNcHitTestЕсли мышь находится над клиентской областью, возвращает HTCAPTION, чтобы панель можно было перетаскивать.
Возвращает значение, индицирующее, находится ли курсор над областью окна, для которой разрешено изменение размеров
OnEnterSizeMoveСохраняет текущее состояние панели.
OnExitSizeMoveСохраняет новое состояние панели и отображает его на экране.
OnMovingДля каждой новой позиции мыши вызывается CalcProposedState, определяющая состояние панели, затем вызывается GetRect, высчитывающая будущее положение панели в этом состоянии, и, затем OnAppBarStateChange, чтобы позволить производному классу узнать новое состояние панели.
OnSizingГарантирует, что ширина или высота окна всегда изменяются дискретно.
Таблица 2 Функции-члены CAppBar

Класс CSRBar

Для проверки моего класса CAppBar я написал простое приложение SHELLRUN.EXE, которое является приложением MFC (версии 4.0) на базе диалога. (Полный исходный код можно посмотреть в демонстрационном проекте). Главное диалоговое окно приложения не произведено непосредственно от CDialog; вместо этого оно является производным от моего класса CAppBar. Когда Вы запускаете это приложение, оно создает панель и закрепляет её по умолчанию на верхней части экрана (см. Рисунок 5).


Рисунок 5

Эта панель содержит два элемента управления, кнопку “Execute” и окно редактирования. В окне редактирования Вы можете ввести любую, какую захотите команду оболочки. Нажмите клавишу Enter или кнопку “Execute”, чтобы заставить панель проанализировать команду и вызвать для её выполнения функцию Win32 API ShellExecute. В качестве команд оболочки Вы можете вводить названия программ, например Notepad или Calc, имена дисков или полные пути к каталогам, чтобы открыть окно Проводника, или Вы можете ввести имя файла документа, и соответствующее приложение будет запущено автоматически. Вы можете ввести даже сетевой адрес, как показано на Рисунке 6, в этом случае будет запущен Internet Explorer.

Поскольку программа ShellRun это панель инструментов рабочего стола, Вы, конечно, можете закрепить её окно на любом краю экрана или оставить в плавающем состоянии, отпуская её в середине Вашего экрана. Чтобы перемещать панель, Вы должны захватить её мышкой за клиентскую часть основного окна; Вы не сможете захватить её за любое из дочерних окон. Когда панель находится в плавающем состоянии, она автоматически добавляет заголовок к окну, как показано на Рисунке 6. При проектировании шаблона диалогового окна для Вашей панели, не забудьте удостовериться, что стиль WS_EX_TOOLWINDOW включен, а WS_EX_APPWINDOW выключен. Окна со стилем WS_EX_TOOLWINDOW не имеют кнопки на панели задач, которая всегда присутствует для окон приложений. Между прочим, когда система изменяет размеры рабочей области экрана, она не перемещает окна, имеющие стиль WS_EX_TOOLWINDOW. Это ещё одна причина для установки панели стиля WS_EX_TOOLWINDOW.


Рисунок 6

Панель ShellRun тестирует также некоторые дополнительные особенности. Когда Вы щелкаете правой кнопкой мыши на клиентской области основного окна, появляется контекстное меню, как показано на Рисунке 7. Это меню позволяет изменять свойства “Поверх всех окон” и “Автоматически скрывать с экрана”. Также можно вызвать окно информации о программе и завершить приложение.


Рисунок 7

Другие особенности приложения ShellRun демонстрируют дискретное изменение размеров панели. Вы можете проверить это когда панель пристыкована, скрытая или плавающая. Базовый класс, CAppBar гарантирует, что панель всегда изменяет свои размеры дискретно и Ваш производный класс получит сообщения WM_SIZE, когда это случится. Конечно Вам захочется при этом поменять расположение элементов управления.

Очевидно, что программисты Microsoft потратили много времени и энергии при разработке новой оболочки системы. Также очевидно, что в оболочке произошли большие изменения (вероятно, в результате usability-тестирования) пока Windows 95 находилась в опытной эксплуатации. Многие из этих изменений привели к появлению плохо документированного API. Но при небольшом (OK, возможно, при большом) терпении и настойчивости, добавление расширений оболочки к Вашим приложениям может придать им профессиональный и отшлифованный вид.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 3    Оценка 160        Оценить