- •Часть II. Проектирование пользовательских интерфейсов средствами win32 api п 1. Основы приложений для Windows. П 1.1 Типы данных Windows.
- •П. 1.2 Главная функция приложения WinMain().
- •П. 1.3 Оконная функция.
- •П. 1.4 Сообщения от окна
- •П. 1.5 Сообщения от мыши
- •1.5.1 Сообщения мыши, связанные с рабочей областью окна
- •1.5.3 Двойные щелчки мыши
- •1.5.4 Сообщения мыши, связанные с нерабочей областью окна
- •1.5.5 Захват мыши
- •П. 1.6 Сообщения от клавиатуры
- •1.6.1 Синхронизация событий клавиатуры
- •1.6.2 Игнорирование событий клавиатуры
- •1.6.3 Понятие фокуса ввода
- •1.6.4 Категории клавиатурных сообщений
- •1.6.5 Аппаратные сообщения
- •1.6.6 Символьные сообщения
- •П. 1.7 Таймеры Windows и служба времени.
- •П. 1.8 Классы окон
- •1.8.1. Описание используемых классом окон ресурсов
- •1.8.2. Регистрация класса окон
- •1.8.3. Создание окон
- •1.8.4. Стили окна
- •П. 1.9 Цикл обработки сообщений
- •П. 1.10 Структура текста приложения
- •П. 1.11 Вспомогательные функции создания окон
- •1.11.1 Функции отображения и перерисовки окон
- •1.11.2 Функции поиска и определения состояния окон
- •1.11.3 Функции перемещения окон
- •1.11.4 Сообщения приложения для пользователя
- •П. 1.12 Примеры создания окон
- •П 2. Органы управления
- •П 2.1. Кнопки
- •2.1.1. Создание кнопок
- •2.1.2. Кнопки и сообщения
- •2.1.3. Флажки и переключатели
- •П 2.2. Статический орган управления
- •П 2.3. Полоса прокрутки
- •2.3.1. Общие сведения
- •2.3.2. Создание полосы прокрутки
- •2.3.3. Простейшие полосы прокрутки
- •2.3.4. Сообщения от полосы прокрутки
- •2.3.5. Управление полосой прокрутки
- •П 2.4 Редактор текста
- •2.4.1. Создание редактора
- •2.4.2. Сообщения для редактора текста
- •2.4.3. Сообщения от редактора текста
- •П 2.5. Списки строк
- •2.5.1. Создание списка
- •2.5.2. Сообщения от списка
- •2.5.3. Сообщения для списка
- •П. 2.6. Комбинированный список
- •2.6.1. Создание комбинированного списка
- •2.6.2. Коды извещения
- •2.6.3. Сообщения для комбинированного списка
- •П 3. Вывод в окно
- •П. 3.1. Виды контекста отображения
- •П 3.2. Сообщение wm_paint
- •П 3.3. Установка атрибутов контекста отображения для текста
- •П 3.4. Вывод текста
- •3.4.1. Настройка параметров шрифта
- •3.4.2. Выбор шрифта в контекст отображения
- •3.4.3. Функции вывода текста
- •3.4.4. Определение метрик шрифта
- •П 3.5. Рисование геометрических фигур
- •3.5.1 Установка атрибутов контекста отображения для рисования.
- •3.5.1. Функции рисования точки
- •3.5.2. Функции рисования линий
- •3.5.3. Функции рисования замкнутых фигур
- •3.5.4 Функции создания и рисования регионов
- •П. 4 Ресурсы приложения и их использование. П. 4.1 Файл ресурсов.
- •П 4.2 Главное меню
- •П. 4.2.1 Элементы меню
- •П. 4.2.2 Создание меню
- •П. 4.2.3 Сообщения от меню
- •П. 4.3. Плавающее меню
- •П. 4.4. Акселераторы
- •П. 4.5. Панель инструментов
- •4.5.1. Создание панели инструментов
- •4.5.2. Управление состоянием кнопок панели
- •4.5.3. Вывод подсказок в панели инструментов
- •П. 4.6. Строка состояния
- •4.6.1. Создание строки состояния
- •4.6.2. Сообщения о меню в строке состояний
- •П 5. Диалоговые панели п. 5.1. Характеристики диалоговых панелей
- •5.1.1. Единицы диалоговой панели '
- •5.1.2. Стили диалоговой панели
- •5.1.3. Функция окна диалоговой панели
- •П. 5.2. Создание диалоговой панели
- •5.2.1 Создание окна модальной диалоговой панели
- •5.2.2 Создание окна немодальной диалоговой панели
- •П. 5.3 Разработка и компоновка диалоговых панелей.
- •П. 5.4 Сообщения и диалоговые панели
- •П. 5.5. Стандартные диалоговые панели
- •5.5.1. Панели для открытия или сохранения файлов
- •5.5.2. Панель для выбора цветов
- •5.5.3. Панель для выбора шрифта
3.5.4 Функции создания и рисования регионов
Графический объект "регион" (в некоторых источниках "region" переводят как "область") представляет (определяет) собой плоскую произвольную область. Предопределен также ряд простых форм региона: эллиптические, прямоугольные, прямоугольные с закругленными краями, полигональные. Однако геометрическая простота не подразумевает простоты внутренней реализации региона. Непосредственно при программировании можно идти двумя путями: использовать функции WinAPI или библиотеку MFC. При работе на WinAPI для манипулирования регионами используется дескриптор HRGN и соответствующая группа функций.
Применение регионов достаточно разнообразно:
-
создание непрямоугольных окон;
-
определение принадлежности произвольной точки региону (можно узнать, кликнул ли пользователь по региону);
-
как графические объекты (регионы можно закрашивать, делать из них рамку…);
-
создание маски для ограничения отрисовки (clipping).
Для начала рассмотрим наиболее простые и понятные функции для работы с регионами, а также базовые функции для работы с окнами. В принципе, последних всего две: SetWindowRgn и GetWindowRgn. Уже из названия понятно назначение этих функций – установка региона окна и получение параметров региона окна. Никакая часть окна не будет отображаться вне заданного региона. Первый параметр функции SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw) – дескриптор hWnd указывает, к какому окну применяется эта функция, второй, hRgn, определяет новую форму окна, а параметр bRedraw в случае ненулевого значения обеспечивает перерисовку окна (система посылает сообщения WM_WINDOWPOSCHANGING и WM_WINDOWPOSCHANGED).
У функции SetWindowRgn есть ряд особенностей:
-
координаты региона должны задаваться относительно верхнего левого угла окна, а не относительно клиентской области или верхнего левого угла экрана;
-
функция SetWindowRgn передает системе управление регионом посредством дескриптора. При этом система не делает копии объекта, а это значит, что мы не должны модифицировать объект и уж тем более удалять его;
-
SetWindowRgn не работает для дочерних окон – окон, созданных со стилем WS_CHILD.
Рассмотрим пример. Код, приведенный ниже, взят из обработчика события WM_SIZE. Он показывает создание окна в виде эллипса.
Создание окна в виде эллипса
// где-то в программе
HRGN hRgn; // регион
// в обработчике события WM_SIZE
RECT rc;
// получает прямоугольник окна
GetWindowRect(hWnd, &rc);
// так как необходимо задавать регион относительно левого верхнего угла окна,
// нужно его сместить так, чтобы верхний левый угол прямоугольника был (0, 0)
OffsetRect(&rc, - rc.left, - rc.top);
DeleteObject(hRgn); // удаление ранее созданного региона
// создание региона в виде эллипса, ограниченного прямоугольником rc
hRgn = CreateEllipticRgnIndirect(&rc);
// установка созданного региона для окна и немедленная перерисовка этого окна
SetWindowRgn(hWnd, hRgn, TRUE);
В этом фрагменте регион создается функцией CreateEllipticRgnIndirect. Существует еще несколько аналогичных функций.
CreateRectRgn |
Создание региона в виде прямоугольника |
CreateRectRgnIndirect |
Создание региона в виде прямоугольника, определенного структурой RECT |
CreateEllipticRgn |
Создание региона в виде эллипса |
CreateEllipticRgnIndirect |
Создание региона в виде эллипса, определенного структурой RECT |
CreateRoundRectRgn |
Создание региона в виде прямоугольника с закругленными краями |
Все эти функции возвращают дескриптор созданного объекта. Если hRgn уже владел каким-либо объектом, перед вызовом функции его необходимо удалить. В противном случае вы потеряете этот объект, и он будет без толку занимать ресурсы системы. Вот еще один простой пример.
Создание прямоугольного окна с закругленными краями
// где то в программе
HRGN hRgn; // регион
// в обработчике события WM_SIZE
RECT rc;
// прямоугольник окна
GetWindowRect(hWnd, &rc);
OffsetRect(&rc, - rc.left, - rc.top);
DeleteObject(hRgn);
// создание региона в виде прямоугольника с закругленными краями
hRgn = CreateRoundRectRgn(
rc.left, rc.top, rc.right, rc.bottom,
(rc.right - rc.left) / 2, (rc.bottom - rc.top) / 2);
// установка созданного региона для окна и немедленная перерисовка этого окна
SetWindowRgn(hWnd, hRgn, TRUE);
После того, как для объекта CRgn задан регион, объект допускает изменение региона при помощи следующих методов:
CombineRgn |
Устанавливает регион эквивалентным объединению двух определенных CRgn-объектов |
OffsetRgn |
Смещает регион на заданное количество точек по вертикали и/или горизонтали |
Рассмотрим параметры функции CombineRgn(HRGN dest, HRGN src1, HRGN src2, int mode). Первый дескриптор задает регион-“приемник” для результата объединения следующих двух регионов по правилу определяемому четвертым параметром mode:
-
RGN_AND – пересечение двух регионов;
-
RGN_COPY – копирование src1;
-
RGN_DIFF – объединение частей src1, не являющихся частями src2;
-
RGN_OR – простое объединение двух регионов src1 и src2;
-
RGN_XOR – объединение src1 и src2 (за исключением перекрывающихся частей).
Для облегчения комбинирования областей в файле windowsx.h определены макрокоманды, предназначенные для копирования, пересечения, объединения и вычитания областей. Все они созданы на основе CombineRegion:
#define CopyRgn (hrgnDst, hrgnSrc) \
CombineRgn(hrgnDst, hrgnSrc, 0, RGN_COPY)
#define IntersectRgn (hrgnResult, hrgnA, hrgnB) \
CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_AND)
#define SubtractRgn (hrgnResult, hrgnA, hrgnB) \
CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_DIFF)
#define UnionRgn (hrgnResult, hrgnA, hrgnB) \
CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_OR)
#define XorRgn (hrgnResult, hrgnA, hrgnB) \
CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_XOR)
Применение функции OffsetRgn уже было показано. Рассмотрим применение функции CombineRgn на примере создания окна в виде кольца с эллипсом посередине.
Демонстрация CombineRgn (WinAPI)
// внешняя граница кольца (создание большого эллипса)
DeleteObject(hRgn);
hRgn = CreateEllipticRgn(rc.left, rc.top, rc.right, rc.bottom);
// внутренняя граница кольца (создание эллипса немного меньшего размера –
// во временном буффере)
hHdrRgn = CreateEllipticRgn(
rc.left + (rc.right - rc.left) / 4,
rc.top + (rc.bottom - rc.top) / 4,
rc.right - (rc.right - rc.left) / 4,
rc.bottom - (rc.bottom - rc.top) / 4);
// создание кольцо путем “вырезания” в большом эллипсе отверстия
CombineRgn(hRgn, hRgn, hHdrRgn, RGN_XOR);
// создание маленького эллипса в центре (во временном буфере)
DeleteObject(hHdrRgn);
hHdrRgn = CreateEllipticRgn(
(rc.right - rc.left) / 2 - (rc.right - rc.left) / 16,
(rc.bottom - rc.top) / 2 - (rc.bottom - rc.top) / 16,
(rc.right - rc.left) / 2 + (rc.right - rc.left) / 16,
(rc.bottom - rc.top) / 2 + (rc.bottom - rc.top) / 16 );
// объединение полученных регионов (кольца и маленького эллипса)
CombineRgn(hRgn, hRgn, hHdrRgn, RGN_OR);
Прокомментировать текст программы лучше всего схемами:
Рисунок 1.
Рисунок 2. Вызов функции CombineRgn с параметром RGN_OR.
Как было обещано, рассмотрим применение функции CreatePolygonRgn.
Создание окна в виде забора
// Точки для создания элемента ("доска") региона ("забор").
// Имея одну “доску”, мы затем “размножим” ее в цикле
POINT pnt[5];
pnt[0].x = rc.left;
pnt[0].y = rc.bottom;
pnt[1].x = rc.left;
pnt[1].y = rc.top + (rc.bottom - rc.top) * 0.75;
pnt[2].x = rc.left + (rc.right - rc.left) / 8;
pnt[2].y = rc.top;
pnt[3].x = rc.left + (rc.right - rc.left) / 4;
pnt[3].y = rc.top + (rc.bottom - rc.top) * 0.75;
pnt[4].x = rc.left + (rc.right - rc.left) / 4;
pnt[4].y = rc.bottom;
//создадим первый элемент ("доску")
DeleteObject(hRgn);
hRgn = CreatePolygonRgn(pnt, 5, ALTERNATE);
//добавим еще три "доски"...
for(n = 0; n < 3; n ++)
{
//каждый раз смещая все пять точек доски на четверть размера окна
for(k=0; k < 5; k ++)
pnt[k].x += (rc.right - rc.left) / 4;
//создавая новую "доску"
hHdrRgn = CreatePolygonRgn(pnt, 5, ALTERNATE);
//и добавляя ее к уже сщуствующим
CombineRgn(hRgn, hRgn, hHdrRgn, RGN_OR);
DeleteObject(hHdrRgn); //удаление промежуточного объекта
}
// установка созданного региона для окна и немедленная перерисовка этого окна
SetWindowRgn(m_rgn, TRUE);
Результат работы этого фрагмента будет таков:
Рисунок 3.
Использование параметров ALTERNATE = 1 или WINDING = POLYFILL_LAST = 2 в данном случае не имеет значения. Эти определения играют роль в случае сложных пересекающихся регионов. При использовании этой функции следует обязательно учитывать направление обхода. Классическим примером, демонстрирующим различия между этими параметрами, является создание региона в виде звезды. При одном и том же передаваемом массиве вершин мы получаем разные регионы:
Рисунок 4
Рассмотренных функций уже достаточно для продуктивной работы с формами окна. Но, тем не менее, будет полезно рассмотреть еще пару функций, полезных при создании графических приложений. Речь идет о следующих функциях:
ExtCreateRegion (CreateFromData – MFC) |
Создает новый регион из передаваемого региона и данных о преобразовании, определяемых структурой XFORM |
GetRegionData |
Заполняет буфер данными, описывающими регион |
Смысл этих функций в том, что, имея массив данных о регионе и располагая структурой XFORM, над регионом можно произвести следующие действия, определяемые полями этой структуры:
|
Поля структуры XFORM |
|||||
Действие |
eM11 |
eM12 |
eM21 |
eM22 |
|
|
Поворот |
Косинус угла поворота |
Синус угла поворота |
Отрицательный синус угла поворота |
Косинус угла поворота |
|
|
Масштабирование |
Горизонтальный коэффициент |
|
|
Вертикальный коэффициент |
|
|
Смещение |
|
Горизонтальный коэффициент |
Вертикальный коэффициент |
|
|
|
Отображение |
Горизонтальный коэффициент |
|
|
Вертикальный коэффициент |
|
|
Кроме указанных, в нижеследующем фрагменте будет использована дополнительная функция GetRgnBox, вычисляющая координаты прямоугольника, описанного вокруг региона
Трансформации регионов
// трансформации
XFORM xf, xf2;
// буфер для хранения данных описывающих первый "луч"
// здесь вызов GetRegionData(HRGN, 0, NULL) с “нулями”
// возвращает число байт, необходимых для хранения информации
// о регионе. В данной статье содержимое структуры LPRGNDATA не имеет
// значения. Имеет значение то, что она представляет определенный регион.
LPRGNDATA lpRgnData;
// прямоугольник описанный вокруг региона
RECT rt;
//массив точек для создания первого "луча"
POINT pnt[4];
pnt[0].x = rc.left;
pnt[0].y = rc.top + (rc.bottom - rc.top) / 2;
pnt[1].x = rc.left + (rc.right - rc.left) * 3 / 4;
pnt[1].y = rc.top + (rc.bottom - rc.top) * 3 / 4;
pnt[2].x = rc.left + (rc.right - rc.left) / 2;
pnt[2].y = rc.top + (rc.bottom - rc.top) / 2;
pnt[3].x = rc.left + (rc.right - rc.left) * 3 / 4;
pnt[3].y = rc.bottom - (rc.bottom - rc.top) * 3 / 4;
// создадим первый "луч"
DeleteObject(hRgn);
hRgn = CreatePolygonRgn(pnt, 4, ALTERNATE);
_ASSERT(hRgn);
//буфер для хранения данных описывающих первый "луч"
lpRgnData = GlobalAlloc(GMEM_FIXED,
sizeof(RGNDATA) * GetRegionData(hRgn, 0, NULL));
_ASSERT(lpRgnData);
//получение данных
GetRegionData(hRgn, GetRegionData(hRgn, 0, NULL), lpRgnData);
// в данном случае XFORM описывает зеркальное
// отображение относительно оси ординат
xf.eM11 = - 1; //
xf.eM22 = 1; //
xf.eM12 = xf.eM21 = 0; //
xf.eDx = xf.eDy = 0; //
// создание второго "луча" с использованием данные первого и структуры,
// описывающей необходимые изменения
hHdrRgn = ExtCreateRegion(&xf, GetRegionData(hRgn, 0, NULL), lpRgnData);
_ASSERT(hHdrRgn);
// смещение второго "луча" (т. к. ось ординат проходит через
// верхний левый угол)
OffsetRgn(hHdrRgn, rc.right - rc.left, 0);
// объединение первого и второго "лучей"
CombineRgn(hRgn, hRgn, hHdrRgn, RGN_OR);
// удаление промежуточного буфера
GlobalFree(lpRgnData);
// еще буфер для хранения данных о двух первых "лучах"
lpRgnData = GlobalAlloc(GMEM_FIXED,
sizeof(RGNDATA) * GetRegionData(hRgn, 0, NULL));
// получение данных
GetRegionData(hRgn, GetRegionData(hRgn, 0, NULL), lpRgnData);
// здесь структура XFORM определяет поворот относительно
// центра на 180 градусов
xf.eDx = xf.eDy = 0;
xf.eM11 = xf.eM22 = 0; //
xf.eM12 = 1; //
xf.eM21 = -1;
// необходимо для масштабирования повернутых лучей.
xf2.eDx = xf2.eDy = 0;
xf2.eM21 = xf2.eM12 = 0;
// Так как в общем случае окно не квадратное, а прямоугольное
xf2.eM11 = (float)(rc.right - rc.left) / (rc.bottom - rc.top);
xf2.eM22 = (float)(rc.bottom - rc.top) / (rc.right - rc.left);
// объединение двух трансформаций, создание повернутых и
// масштабированных "вертикальных лучей"
CombineTransform(&xf, &xf, &xf2);
DeleteObject(hHdrRgn);
hHdrRgn = ExtCreateRegion(&xf, GetRegionData(hRgn, 0, NULL), lpRgnData);
//удаление промежуточного буфера
GlobalFree(lpRgnData);
// сместим полученные "вертикальные лучи", это также связано
// с положением центра координат в верхнем левом углу
GetRgnBox(hHdrRgn, &rt);
OffsetRgn(hHdrRgn, - rt.left + (rc.left + (rc.right - rc.left) / 2
- (rt.right - rt.left) / 2), - rt.top + (rc.top));
//получение "звезды"
CombineRgn(hRgn, hRgn, hHdrRgn, RGN_OR);
//очистим дополнительный регион
DeleteObject(hHdrRgn);
Используемая в листинге функция
GetRegionData(HRGN hRgn, DWORD dwCount, LPRGNDATA lpRgnData);
заполняет буфер lpRgnData данными о регионе hRgn. Параметр dwCount передает количество байтов, необходимых для заполнения. Это количество можно узнать, если вызвать эту функцию с параметром dwCount, равным нулю. В этой статье не рассматривается содержимое структуры RGNDATA, нужное в очень редких случаях.
Считается, что использование структуры XFORM сопряжено с координатными преобразованиями, которые работают только на платформе NT 3.1 и выше. К использованию регионов для создания окон координатные преобразования практически никакого отношения не имеют. Исключение составляет функция CombineTransform, которая осуществляет объединение двух трансформаций и действительно работает только на NT 3.1 и выше. Для того, чтобы данный код исполнялся на платформе Win 9x, можно обойтись применением двух последовательных трансформаций без объединения их в одной.
СОВЕТ
Если используется структура XFORM, то никакие макросы и отладчики не заменят лист бумаги и карандаш.
Кроме того, необходимо помнить о немного нетрадиционной системе координат для окна (центр – левый верхний угол, направление вертикальной оси - вниз). Однако это можно изменить, для чего в Win API существует целый класс функций для координатных преобразований.
В результате выполнения этой части программы на экране появится окно изображенное на рисунке 5.
Рисунок 5
которое, наверняка можно было бы получить более легким способом. Это только лишь демонстрация.