|
РАССЫЛКА САЙТА
RSDN.RU |
Здравствуйте! Работа с регионами Windows GDI. Создание непрямоугольных окон. Автор: Сапронов Андрей Юрьевич
|
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 m_rgn; // регион // в обработчике события WM_SIZE CRect rc; GetWindowRect(&rc); rc -= rc.TopLeft(); m_rgn.DeleteObject(); // удаление ранее созданного региона // создание региона в виде прямоугольника с закругленными краями m_rgn.CreateRoundRectRgn(rc.left, rc.top, rc.right, rc.bottom, rc.Width() / 2, rc.Height() / 2); // установка созданного региона для окна и немедленная перерисовка окна SetWindowRgn(m_rgn, TRUE);
Помимо перечисленных, существуют и другие функции по созданию регионов. Наиболее универсальны, на мой взгляд, CRgn::CreatePolygonRgn и CRgn::CreatePolyPolygonRgn. Этими функциями можно задать регион (множество регионов), который определяется массивом точек. Применение этих функций будет рассмотрено несколько позже.
После того, как для объекта CRgn задан регион, объект допускает изменение региона при помощи следующих методов:
CombineRgn | Устанавливает регион эквивалентным объединению двух определенных CRgn-объектов |
---|---|
OffsetRgn | Смещает регион на заданное количество точек по вертикали и/или горизонтали |
Рассмотрим параметры функции CombineRgn(HRGN dest, HRGN src1, HRGN src2, int mode). Первый хэндл задает регион-“приемник” для результата объединения следующих двух регионов по правилу определяемому четвертым параметром mode:
Для облегчения комбинирования областей в файле 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 на примере создания окна в виде кольца с эллипсом посередине.
// внешняя граница кольца (создание большого эллипса) 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);
// создание нового региона в виде эллипса m_rgn.DeleteObject(); m_rgn.CreateEllipticRgn(rc.left, rc.top, rc.right, rc.bottom); // создание временного региона в виде эллипса, // который на четверть меньше, чем m_rgn m_HdrRgn.DeleteObject(); m_HdrRgn.CreateEllipticRgn(rc.left + rc.Width() / 4, rc.top + rc.Height() / 4, rc.right - rc.Width() / 4, rc.bottom - rc.Height() / 4); // вызов этого метода с параметром RGN_XOR // удаляет области пересечения регионов m_rgn и m_HdrRgn, // помещая результат в m_rgn. Т. о. в m_rgn у нас останется лишь узкое кольцо m_rgn.CombineRgn(&m_rgn, &m_HdrRgn, RGN_XOR); // в промежуточном буфере создается маленький эллипс m_HdrRgn.DeleteObject(); m_HdrRgn.CreateEllipticRgn(rc.Width() / 2 - rc.Width() / 16, rc.Height() / 2 - rc.Height() / 16,rc.Width() / 2 + rc.Width() / 16, rc.Height() / 2 + rc.Height() / 16 ); // который в простом объединении (параметр RGN_OR) с m_rgn даст искомую фигуру m_rgn.CombineRgn(&m_rgn, &m_HdrRgn, RGN_OR); // установка созданного региона для окна и немедленная перерисовка этого окна SetWindowRgn(m_rgn, TRUE);
Прокомментировать текст программы лучше всего схемами:
Как было обещано, рассмотрим применение функции 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);
// Точки для создания элемента ("доска") региона ("забор"). // Имея одну “доску”, мы затем “размножим” ее в цикле CPoint pnt[5]; pnt[0].x = rc.left; pnt[0].y = rc.bottom; pnt[1].x = rc.left; pnt[1].y = rc.top + rc.Height() * 0.75; pnt[2].x = rc.left + rc.Width() / 8; pnt[2].y = rc.top; pnt[3].x = rc.left + rc.Width() / 4; pnt[3].y = rc.top + rc.Height() * 0.75; pnt[4].x = rc.left + rc.Width() / 4; pnt[4].y = rc.bottom; //создадим первый элемент ("доску") m_rgn.DeleteObject(); m_rgn.CreatePolygonRgn(pnt, 5, ALTERNATE); //добавим еще три "доски"... for(UINT n = 0; n < 3; n++) { // каждый раз смещая все пять точек доски на четверть размера окна for(UINT k=0; k < 5; k + + ) pnt[k].x += rc.Width() / 4; //создавая новую "доску" m_HdrRgn.CreatePolygonRgn(pnt, 5, ALTERNATE); //и добавляя ее к уже существующим m_rgn.CombineRgn(&m_rgn, &m_HdrRgn, RGN_OR); m_HdrRgn.DeleteObject(); } // установка созданного региона для окна и немедленная перерисовка этого окна SetWindowRgn(m_rgn, TRUE);
Результат работы этого фрагмента будет таков:
Использование параметров ALTERNATE = 1 или WINDING = POLYFILL_LAST = 2 в данном случае не имеет значения. Эти определения играют роль в случае сложных пересекающихся регионов. При использовании этой функции следует обязательно учитывать направление обхода. Классическим примером, демонстрирующим различия между этими параметрами, является создание региона в виде звезды. При одном и том же передаваемом массиве вершин мы получаем разные регионы:
Рассмотренных функций уже достаточно для продуктивной работы с формами окна. Но, тем не менее, будет полезно рассмотреть еще пару функций, полезных при создании графических приложений. Речь идет о следующих функциях:
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);
//создание окна в виде восьмиконечной звезды //массив точек для создания первого "луча" CPoint pnt[4]; pnt[0].x = rc.left; pnt[0].y = rc.top + rc.Height() / 2; pnt[1].x = rc.left + rc.Width() * 3 / 4; pnt[1].y = rc.top + rc.Height() * 3 / 4; pnt[2].x = rc.left + rc.Width() / 2; pnt[2].y = rc.top + rc.Height() / 2; pnt[3].x = rc.left + rc.Width() * 3 / 4; pnt[3].y = rc.bottom - rc.Height() * 3 / 4; // создадим первый "луч" m_rgn.DeleteObject(); VERIFY(m_rgn.CreatePolygonRgn(pnt, 4, ALTERNATE)); // Буфер для хранения данных описывающих первый "луч". // Здесь вызов GetRegionData(0, NULL) с параметром NULL возвращает число // байт, необходимых для хранения информации о регионе. Для данной статьи // содержимое структуры LPRGNDATA не имеет значения. Имеет значение то, // что она представляет определенный регион. LPRGNDATA lpRgnData = new RGNDATA[m_rgn.GetRegionData(0, NULL)]; VERIFY(lpRgnData); //получение данных – заполнение буфера VERIFY(m_rgn.GetRegionData(lpRgnData, m_rgn.GetRegionData(0, NULL))); // Структура, определяющая трансформацию массива данных, // описывающего регион. В данном случае на описывает зеркальное // отображение относительно оси ординат. XFORM xf; xf.eM11 = - 1; xf.eM22 = 1; //о xf.eM12 = xf.eM21 = 0; xf.eDx = xf.eDy = 0; //создание второго "луча" с использованием данных первого и структуры xf, //описывающей необходимые изменения m_HdrRgn.DeleteObject(); VERIFY(m_HdrRgn.CreateFromData(&xf, m_rgn.GetRegionData(0, NULL), lpRgnData)); //смещение второго "луча" (т. к. ось ординат проходит через //верхний левый угол) m_HdrRgn.OffsetRgn(rc.Width(), 0); //объединение первого и второго "лучей" VERIFY(m_rgn.CombineRgn(&m_rgn, &m_HdrRgn, RGN_OR)); delete lpRgnData; //удаление промежуточного буфера //еще буфер для хранения данных о двух первых "лучах" lpRgnData = new RGNDATA[m_rgn.GetRegionData(0, NULL)]; VERIFY(lpRgnData); //получение данных VERIFY(m_rgn.GetRegionData(lpRgnData, m_rgn.GetRegionData(0, NULL))); xf.eDx = xf.eDy = 0; //здесь структура XFORM xf.eM11 = xf.eM22 = 0; //определяет поворот xf.eM12 = 1; //относительно центра xf.eM21= - 1; //на 180 градусов XFORM xf2; //еще одна трансформация xf2.eDx = xf2.eDy = 0; // необходимая для масштабирования xf2.eM21 = xf2.eM12 = 0; // повернутых лучей.Так как в общем случае xf2.eM11 = (float)rc.Width() / rc.Height(); // окно не квадратное, xf2.eM22 = (float)rc.Height() / rc.Width(); // а прямоугольное // структура, определяющая трансформацию массива данных, описывающего регион // теперь использование xf будет эквивалентно последовательному // использованию, определенных ранее двух структур XFORM VERIFY(CombineTransform(&xf, &xf, &xf2)); // и создание повернутых и отмасштабированных "вертикальных лучей" m_HdrRgn.DeleteObject(); VERIFY(m_HdrRgn.CreateFromData(&xf, m_rgn.GetRegionData(0, NULL), lpRgnData)); delete lpRgnData; //удаление промежуточного буфера // сместим полученные "вертикальные лучи", это так же связано // с положением центра координат в верхнем левом углу CRect rt; m_HdrRgn.GetRgnBox(&rt); m_HdrRgn.OffsetRgn( - rt.left + (rc.left + rc.Width() / 2 - rt.Width() / 2), - rt.top + (rc.top)); //получение "звезды" VERIFY(m_rgn.CombineRgn(&m_rgn, &m_HdrRgn, RGN_OR)); // установка созданного региона для окна и немедленная // перерисовка этого окна SetWindowRgn(m_rgn, TRUE);
Используемая в листинге функция
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.
которое, наверняка можно было бы получить более легким способом. Это только лишь демонстрация.
Ведущий рассылки: Алекс Jenter jenter@rsdn.ru
Публикуемые в рассылке материалы принадлежат сайту RSDN.