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

Класс CSkinBaseDialog

Автор: 2Los
Опубликовано: 01.03.2002
Исправлено: 13.03.2005
Версия текста: 1.0

Введение
Краткое описание класса
Подготовка фонового изображения диалога
Cоздание диалога на основе фонового изображения
Добавление контролов в окно диалога
Создание выдвигающихся частей диалога
Заключение

Исходные тексты - 64Kб
Демонстрационное приложение - 45Kб

Введение


Как известно, окна в Windows квадратные. И в принципе этого достаточно для большинства приложений. Если это, например, файл-менеджер, то очень даже неплохо, что он квадратный. Однако иногда хочется, чтобы программа выглядела не как все. Классическим примером могут служить WinAmp или Window Media Player, а также многие autoran'ы для CD дисков. Отличительной особенностью этих программ является то, что все они обладают окнами произвольной формы, с опять же произвольным фоновым изображением, а плэйеры к тому же могут менять свой внешний вид.

У разработчика подобного окна выбор средств не богатый: это функция SetWindowRgn() - устанавливающая регион, занимаемый окном и обработчик события WM_ERASEBKGND, в котором следует поместить код, выводящий фоновую картинку в окно. И если окно не меняет свой размер, этого достаточно, иначе придется потрудиться, чтобы корректно отображать фон и пересчитывать регион окна. Если вам это занятие не по душе, то предлагаю воспользоваться моим новым MFC-классом диалога CSkinBaseDialog, который возьмет всю эту работу на себя.

Краткое описание класса

В исходники входят несколько классов CSkinBaseDialog, CSkinBaseButton и CDib, но обо всем по порядку. Класс CSkinBaseDialog реализует следующие вещи:

Остальные классы будут упоминаться по ходу дела.

Подготовка фонового изображения диалога

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


Части 11, 13, 31, 32 и 34 являются не масштабируемыми, и такие изображения разрешается маскировать, в данном примере цвет маски RGB(255, 0, 255), т.е. розовый. Части 12, 21, 23 и 32 масштабируются по одной из осей, а 22 - по обоим, маскируемые области в таких изображениях отсутствуют. Кроме того, части 23 и 34 позволяют пользователю изменять размер окна: при наведении мыши на одну из этих картинок курсор изменить свой вид на двойную стрелку, а при нажатии кнопки и перемещении мыши будет изменяться размер окна. Возможно, часть 34 следовало сделать и поменьше, но раз это пример, сойдет и такая.

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

Cоздание диалога на основе фонового изображения

Предположим, что в проекте есть диалог MyDialog, соответствующий ему класс диалога CMyDialog, находящийся в файлах MyDialog.h и MyDialog.cpp. Наша задача изменить внешний вид диалога. Для этого нужно:

Добавление контролов в окно диалога

Теперь, когда диалог готов, можно приступить к размещению на нем контролов. Это можно сделать, как и в редакторе форм, так и в runtime, вызовом Create соответствующего элемента управления. Класс CSkinBaseDialog будет отвечать за выравнивание элемента управления, если выполнить вызов следующей функции:

int AddControl(
    CWnd* pControl, 
    int nXAlignment=0, 
    int nYAlignment=0
);

Параметры здесь имеют следующее назначение:

pControl - указатель на объект элемента управления. Класс объекта должен быть производным от CWnd.

nXAlignment и nYAlignmet - аналогичны соответствующим параметрам функции AddSkinDIB

Возвращаемое значение аналогично AddSkinDIB

Например:

CRect rc;
GetClientRect(&rc);
CRect rcButton(rc.Width()-100, rc.Height()-100, rc.Width()-50, rc.Heigth()-50);
m_button.Create("Button 1", WS_CHILD|WS_VISIBLE|WS_TABSTOP, rcButton, this, IDC_BUTTON1);
AddControl(&button, sbdRight, sbdBottom);

В результате получим "квадратную" кнопку со стороной 50 пикселей, расположенную на расстоянии 100 от нижнего правого угла окна диалога, которое будет сохраняться при изменении размеров окна.

Стандартные элементы управления Windows несколько выделяются на фоне диалога, поэтому для полной гармонии, скорее всего, потребуется воспользоваться чем-нибудь иным. В исходники наряду с классами CSkinBaseDialog и CDib входит также класс CSkinBaseButton, представляющий собой MFC-контрол типа кнопка. Рассказывать том, как им пользоваться я не буду, надеюсь, что это можно понять, изучив проект-пример. Остальные элементы управления, как, впрочем, и кнопку, можно найти в сети.

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

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

Создание выдвигающихся частей диалога

Многие наверно заметили, что у Windows Media Player в некоторых "шкурках" есть выдвигающиеся части. Подобная возможность реализована и в CSkinBaseDialog.

Чтобы заставить какую-либо часть фона двигаться используйте вызов функции

typedef CArray<CSkinBkPart*, CSkinBkPart*> ARR_BK_PARTS;
BOOL StartMoveBkParts(
    ARR_BK_PARTS &aMoveBkParts, 
    CSize szLength, 
    int nNumSteps, 
    UINT nIDEvent
);

Параметры здесь имеют следующее назначение:

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

szLength - длина перемещения по осям X и Y.

nNumSteps - число шагов перемещения. Это число должно делить нацело как szLength.cx, так и szLength.cy и в последствии используется для подсчета шага перемещения. Число шагов также влияет на скорость перемещения.

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

Перед началом перемещения вызывается виртуальная функция OnBeginMoveBkParts(CMoveInfo &mi), а по окончании - OnEndMoveBkParts(CMoveInfo &mi). Параметр mi содержит информацию о текущем перемещении, переданную в вызове функции StartMoveBkParts. В реализации этих функций в классе CMyDialog можно поместить код, отвечающий за отображение/скрытие элементов управления.

Заключение

В заключение хотелось бы упомянуть еще об оной функции - Remove, предназначенной для удаления картинок, составляющих фоновое изображение. Кроме того, вызов функций AddSkinDIB, AddControl и AddOwnerDrawControl можно выполнять в любом месте программы, а не только в OnInitDialog. Таким образом, нетрудно реализовать возможность смены фона и формы диалога в runtime, т.е. реализовать поддержку skin'ов в своем приложении, но это тема для отдельного разговора.


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