Техническая поддержка :

Современные решения

для защиты Windows программ

и восстановления исходного кода
Автор: Vander Nunes. Дата публикации: 20.08.2004

Работа с регионами



В Win32 API есть набор функций для работы с регионами. При помощи регионов Вы можете создавать различные поверхности, используя только стандартные геометрические фигуры. Ну а дальше всё зависит от Вашей выдумки.

Итак, приступим к определениям:

HRNG:

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

Пример использования:

CODE NOW!

HRGN hRegion = CreateRectRgn(x,y,x+128,y+128);




После завершения работы с регионом, необходимо удалить объект, связанный с регионом при помощи функции DeleteObject().

CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn, EqualRgn, ExtCreateRegion, FillRgn, FrameRgn, GetPolyFillMode, GetRegionData, GetRgnBox, InvertRgn, OffsetRgn, PaintRgn, PtInRegion, RectInRegion, SetPolyFillMode.

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

SetWindowRgn

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


Чтобы проделать такое с окном, понадобятся следующие функции:

CODE NOW!

// эта функция создаёт круглый регион.
HRGN CreateEllipticRgn (
int nLeftRect, // x-координата верхнего-левого угла
int nTopRect, // y-координата верхнего-левого угла
int nRightRect, // x-координата нижнего-правого угла
int nBottomRect // y-координата нижнего-правого угла
);


// эта функция совмещает два региона
int CombineRgn (
HRGN hrgnDest, // хэндл конечного региона
HRGN hrgnSrc1, // хэндл исходного региона
HRGN hrgnSrc2, // хэндл исходного региона
int fnCombineMode // режим совмещения регионов
);

// эта функция прикрепляет регион к окну.
// чтобы убрать регион с окна, надо вместо хэндла региона поставить NULL.
int SetWindowRgn (
HWND hWnd, // хэндл окна, на которое будет установлен регион
HRGN hRgn, // хэндл региона
BOOL bRedraw // флаг перерисовки окна
);




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

CODE NOW!

// --------------------------------------------------
// Создаём круглый регион.
// Используем отрицательную начальную координату, чтобы наш элипс
// захватил заголовок окна.
// --------------------------------------------------
HRGN hRegion1 = CreateEllipticRgn(20,-20,190,150);

// --------------------------------------------------
// создаём ещё один круглый регион в другом месте.
// --------------------------------------------------
HRGN hRegion2 = CreateEllipticRgn(140,100,300,240);

// --------------------------------------------------
// склеиваем два региона, чтобы сделать новый регион.
// итоговый регион будет помещён в region1,
// подобно операции:
//
// hRegion1 = hRegion1 + hRegion2.
//
// в функции CombineRgn() можно использовать набор операций RGN_.
// --------------------------------------------------
CombineRgn(hRegion1, hRegion1, hRegion2, RGN_OR);

// --------------------------------------------------
// прикрепляем регион к окну
// --------------------------------------------------
SetWindowRgn(hWnd, hRegion1, true);

// --------------------------------------------------
// удаляем объекты регионов
// --------------------------------------------------
DeleteObject(hRegion1);
DeleteObject(hRegion2);

Чтобы вернуть окно в нормальное состояние (без региона), воспользуйтесь следующей функцией:

SetWindowRgn(hWnd, NULL, true);




СКИНЫ
Тема скинов довольно популярна в программировании. При помощи скинов мы можем придать стандартному окну привлекательный вид:


Для этого необходим битмап, который заполнит всё окно. На приведённой картинке используется окно размером 320х240 и такого же размера битмап.

Давайте создадим небольшое демонстрационное приложение. Ниже приведены шаги, которые потребуется проделать:

1 - Загружаем битмап;
2 - Создаём контекст устройства для скина и выбираем в нём битмап;
3 - Создаём переключатель между нормальным режимом и скином;
4 - Скрываем заголовок окна и блокируем изменение его размеров в режиме скина;
5 - Показываем заголовок окна и разблокируем его при выходе из режима скина;
6 - Обрабатываем перерисовку скина в сообщении WM_PAINT;
7 - Обрабатываем сообщение WM_LBUTTON, чтобы пользователь мог перетаскивать окно за любую часть в режиме скина;
А теперь каждый шаг подробнее:

1- Загружаем битмап:

CODE NOW!

// -----------------------------------------------------------------------
// загружаем битмап скина из ресурса
// -----------------------------------------------------------------------
hSkinBmp = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_SKIN));
if (!hSkinBmp) return -1;




Как видно из кода, ничего сложного. Конечно можно загружать картинки других форматов, но это тема другой статьи.

2 - Создаём контекст устройства для скина и выбираем в нём битмап:

CODE NOW!

// -----------------------------------------------------------------------
// создаём контекст устройства для скина
// -----------------------------------------------------------------------
dcSkin = CreateCompatibleDC(0);

// -----------------------------------------------------------------------
// выбираем битмап для скина
// -----------------------------------------------------------------------
hOldBmp = (HBITMAP)SelectObject(dcSkin, hSkinBmp);




Не забудьте освободить эти объекты перед тем как Ваше приложение завершит свою работу.

3 - Создаём переключатель между нормальным режимом и скином:

CODE NOW!

case VK_SPACE:
{
if (!bRegioned)
RegionMe();
else
UnRegionMe();

break;
}




Этот фрагмент кода необходмо поместить в главную оконную процедуру в обработчик сообщения WM_KEYDOWN. Здесь используются две небольшие собственные функции RegionMe() и UnregionMe() для переключения режима.

4 - Скрываем заголовок окна и блокируем изменение его размеров в режиме скина:

CODE NOW!

// ------------------------------------------------------------------------
// Создаём основной регион и устанавливаем его на окно.
// ------------------------------------------------------------------------
void RegionMe()
{
// --------------------------------------------------
// создаём круглый регион и используем отрицательную
// координату, чтобы регион захватил заголовок окна.
// --------------------------------------------------
HRGN hRegion1 = CreateEllipticRgn(20,-20,190,150);
OffsetRgn(hRegion1, GetSystemMetrics(SM_CXBORDER)*4, GetSystemMetrics(SM_CYCAPTION));

// --------------------------------------------------
// создаём второй регион в другом месте.
// --------------------------------------------------
HRGN hRegion2 = CreateEllipticRgn(140,100,300,240);
OffsetRgn(hRegion2, GetSystemMetrics(SM_CXBORDER)*4, GetSystemMetrics(SM_CYCAPTION));

// --------------------------------------------------
// совмещаем два региона по принципу:
// hRegion1 = hRegion1 + hRegion2.
// --------------------------------------------------
CombineRgn(hRegion1, hRegion1, hRegion2, RGN_OR);

// --------------------------------------------------
// прикрепляем итоговый регион к окну
// --------------------------------------------------
SetWindowRgn(hWnd, hRegion1, true);

// --------------------------------------------------
// удаляем объекты регионов
// --------------------------------------------------
DeleteObject(hRegion1);
DeleteObject(hRegion2);

// --------------------------------------------------
// изменяем стиль окна (избавляемся от заголовка)
// --------------------------------------------------
DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
dwStyle &= ~(WS_CAPTION|WS_SIZEBOX);
SetWindowLong(hWnd, GWL_STYLE, dwStyle);

// --------------------------------------------------
// перерисовываем окно, а так же убираем рамку окна, чтобы
// чтобы нельзя было менять его размер
// --------------------------------------------------
InvalidateRect(hWnd, NULL, TRUE);
SetWindowPos(hWnd, NULL, 0,0,320,242, SWP_NOMOVE|SWP_NOZORDER);

// --------------------------------------------------
// устанавливаем флажок, только для того, чтобы приложение знало об изменении.
// --------------------------------------------------
bRegioned = true;
}




5 - Показываем заголовок окна и разблокируем его при выходе из режима скина:

CODE NOW!

// ------------------------------------------------------------------------
// Убираем регион с окна
// ------------------------------------------------------------------------
void UnRegionMe()
{
// --------------------------------------------------
// Убираем регион с окна
// --------------------------------------------------
SetWindowRgn(hWnd, NULL, true);

// --------------------------------------------------
// меняем стиль окна (снова показываем заголовок)
// --------------------------------------------------
DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
dwStyle |= WS_CAPTION|WS_SIZEBOX;
SetWindowLong(hWnd, GWL_STYLE, dwStyle);

// --------------------------------------------------
// перерисовываем окно и возвращаем на место рамку окна, чтобы можно было
// менять его размер
// --------------------------------------------------
InvalidateRect(hWnd, NULL, TRUE);
SetWindowPos(hWnd, NULL, 0,0,320,240, SWP_NOMOVE|SWP_NOZORDER);

// --------------------------------------------------
// устанавливаем флажок.
// --------------------------------------------------
bRegioned = false;
}




6 - Обрабатываем перерисовку скина в сообщении WM_PAINT:

CODE NOW!

case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd,&ps);

// рисуем скин на окне
if (bRegioned) SkinMe(ps.hdc);

// рисуем текст
SetBkMode(ps.hdc,TRANSPARENT);
SetTextColor(ps.hdc,RGB(255,0,0));
TextOut(ps.hdc, 115,90, "Press SPACE", 11);

EndPaint(hWnd,&ps);

break;
}




Функция SkinMe() вызывается только в том случае, если приложение находится в режиме скина (bRegioned).

Функция SkinMe() выглядит следующим образом:

CODE NOW!

void SkinMe(HDC dc)
{
BitBlt(dc, 0,0,320,240, dcSkin, 0,0, SRCCOPY);
}




7 - Обрабатываем сообщение WM_LBUTTON, чтобы пользователь мог перетаскивать окно за любую часть в режиме скина:

CODE NOW!

case WM_LBUTTONDOWN:
{
// ---------------------------------------------------------
// Посылаем сообщение окну, чтобы оно думало, что кликнули по его заголовку.
// ---------------------------------------------------------
if (bRegioned) SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION,NULL);
break;
}




Естевственно, что это сообщение посылается только когда окно в режиме скина.

Сложные регионы

Простые геометрические фигурки это конечно хорошо. А что делать, если надо создать регион сложной формы, например в виде руки:


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

CODE NOW!

// ----------------------------------------------------------------------
// Функция сканирует битмап и возвращает необходимый нам регион.
// Освободить объект региона нужно будет самостоятельно...
// ----------------------------------------------------------------------
HRGN ScanRegion(HBITMAP pBitmap, BYTE jTranspR, BYTE jTranspG, BYTE jTranspB)
{
// ширина и высота битмапа
WORD wBmpWidth,wBmpHeight;

// конечный и временный регионы
HRGN hRgn, hTmpRgn;

// 24-битные пиксели из битмапа
BYTE *pPixels = Get24BitPixels(pBitmap, &wBmpWidth, &wBmpHeight);
if (!pPixels) return NULL;

// создаём рабочий регион
hRgn = CreateRectRgn(0,0,wBmpWidth,wBmpHeight);
if (!hRgn) { delete pPixels; return NULL; }

// ---------------------------------------------------------
// сканируем битмап
// ---------------------------------------------------------
DWORD p=0;
for (WORD y=0; y {
for (WORD x=0; x {
BYTE jRed = pPixels[p+2];
BYTE jGreen = pPixels[p+1];
BYTE jBlue = pPixels[p+0];

if (jRed == jTranspR && jGreen == jTranspG && jBlue == jTranspB)
{
// удаляем прозрачный цвет из региона
hTmpRgn = CreateRectRgn(x,y,x+1,y+1);
CombineRgn(hRgn, hRgn, hTmpRgn, RGN_XOR);
DeleteObject(hTmpRgn);
}

// следующий пиксель
p+=3;
}
}

// освобождаем пиксели
delete pPixels;

// возвращаем регион
return hRgn;
}




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



Комментарии

отсутствуют

Добавление комментария


Ваше имя (на форуме):

Ваш пароль (на форуме):

Комментарии могут добавлять только пользователи,
зарегистрированные на форуме данного сайта. Если Вы не
зарегистрированы, то сначала зарегистрируйтесь тут

Комментарий:





Главная     Программы     Статьи     Разное     Форум     Контакты