Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций Часть II.doc
Скачиваний:
14
Добавлен:
20.11.2018
Размер:
1.48 Mб
Скачать

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

которое, наверняка можно было бы получить более легким способом. Это только лишь демонстрация.