Окна нестандартной формы в VC++

Наверное, еще не каждый знает, как создать окно нестандартной (непрямоугольной) формы, как, например, у "Винампа" или других поддерживающих скины программ. Поэтому еще раз затрону эту тему и добавлю кое-что новое.

Для задания региона используются нижеприведенные WinAPI-функции, которые создают регион, определяемый координатами левого верхнего (x1, y1) и правого нижнего (x2, y2) углов.
HRGN CreateEllipticRgn(int x1,int y1,int x2,int y2). Эллиптический регион.
HRGN CreateRectRgn(int x1,int y1,int x2,int y2). Прямоугольный регион.
HRGN CreateRoundRectRgn(int x1,int y1,int x2,int y2, int nWidthEllipse, int nHeightEllipse). Прямоугольный регион cо скругленными краями. Величины nWidthEllipse и nHeightEllipse определяют величину скругления.
int GetRgnBox(HRGN hrgn, LPRECT lprc). Получает минимальный прямоугольник, содержащий в себе заданный регион.
int OffsetRgn(HRGN hrgn, int nXOffset, int nYOffset). Сдвигает регион на указанные величины по обеим осям.
BOOL PtInRegion(HRGN hrgn, int X, int Y). Определяет, лежит ли точка внутри региона.
BOOL RectInRegion(HRGN hrgn, CONST RECT *lprc). Определяет, пересекается ли данный прямоугольник с регионом.
BOOL SetRectRgn(HRGN hrgn, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect). Конвертирует произвольный регион в прямоугольный.
BOOL EqualRgn(HRGN hSrcRgn1, HRGN hSrcRgn2). Проверяет идентичность двух регионов.
HRGN CreatePolygonRgn(CONST POINT *lppt, int cPoints, int fnPolyFillMode). Создает регион, определяемый координатами многоугольника. Первый параметр — массив точек, второй — их количество, третий — режим заполнения (принимает значения ALTERNATE и WINDING).
Int CombineRgn(HRGN hDest, HRGN hScr1, HRGN hScrc2, int fnCombineMode). В зависимости от последнего параметра эта функция некоторым образом комбинирует регионы hSrc1 и hSrc2 и записывает результат в hDest. Значения параметра могут быть следующими:
RGN_AND (результат — пересечение регионов);
RGN_COPY (результат — копия региона hSrc1);
RGN_DIFF (результат — части hSrc1, которые не являются частями hScr2);
RGN_OR (объединение);
RGN_XOR (объединение с исключением пересекающихся частей).
HRGN ExtCreateRegion(CONST XFORM *lpXform, DWORD nCount, CONST RGNDATA *lpRgnData). Создает регион из другого путем трансформации, заданной первым параметром.

Первый параметр — lpXform — это указатель на структуру XFORM, которая определяет трансформацию. Структура XFORM описывается следующим образом:
typedef struct _XFORM {
FLOAT eM11;
FLOAT eM12;
FLOAT eM21;
FLOAT eM22;
FLOAT eDx; //Сдвиг по горизонтали
FLOAT eDy; //Сдвиг по вертикали
} XFORM, *PXFORM;

В зависимости от того, какую трансформацию вы выполняете, переменные имеют следующие значения:
Операция   eM11   eM12   eM21   eM22
Вращение   косинус   синус   минус синус   косинус
Маштабирование   горизонтальное увеличение   не используется   вертикальное увеличение
Отражение   горизонтальный компонент      вертикальный компонент

Вторым и третьим параметрами функции ExtCreateRegion являются размер, занимаемый данными региона, и сами данные соответственно. Для получения данных о регионе по его указателю можно использовать следующую функцию:
DWORD GetRegionData(HRGN hRgn, DWORD dwCount, LPRGNDATA lpRgnData );
dwCount. Размер буфера, содержащего RGNDATA, в байтах.
lpRgnData. Указатель на RGNDATA.
Теории, я считаю, достаточно. Переходим к практике.

Задача 1. Создать форму, похожую на Чебурашку.
Решение. Для решения задачи можно использовать следующий код:
HWND hwndDlg;
//некоторый код, инициализирующий hwndDlg и создающий окно обычной формы
HRGN LevoeUho=CreateEllipticRgn(0,0,100,100);
HRGN Cheburashka=CreateEllipticRgn(60,30,140,70);
HRGN PravoeUho=CrateEllipticRgn(100,0,200,100);
CombineRgn(Cheburashka,Cheburashka,LevoeUho, RGN_OR);//приклеили левое ухо
CombineRgn(Cheburashka,Cheburashka,PravoeUho, RGN_OR);//приклеили правое ухо
iRet = SetWindowRgn(hwndDlg, tTemp, TRUE);//присвоили окну регион

Задача 2. Создать форму с регионом, определяемым некоторым изображением/маской (фактически нужно нарисовать на экране некоторое изображение с непрямоугольными краями)
Решение. Для решения задачи будем строить регион следующим образом:
HGDIOBJ hGdiObj;
   char imageFile[]="mask.bmp";
   HBITMAP hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), imageFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); //Загрузили картинку.
   if(!hBitmap) return;   

   HDC hdcMem = CreateCompatibleDC(NULL);
   GetObject(hBitmap, sizeof(bmpInfo), &(bmpInfo));   //Получаем информацию о изображении
   hGdiObj = SelectObject(hdcMem, hBitmap);

   //Создаем пустой регион
HRGN hRgn = CreateRectRgn(0,0,0,0);
   
   //Цветом прозрачности может быть любой цвет изображения. Т.е. на месте областей, закрашенный этим цветом, будет дыра
   //Указываем цветовые компоненты прозрачного пикселя.
   COLORREF crTransparent = RGB(255, 0, 0);
   int iX = 0;
   int iRet = 0;
   for (int iY = 0; iY    {
      do
      {
         //Пропускаем прозрачные пиксели
         while (iX             iX++;
         //Запоминаем позицию
         int iLeftX = iX;
         //Ищем последний непрозрачный пиксель
         while (iX             ++iX;
         //и создаем регион толщиной в 1 пиксель на основе полученных данных
         HRGN hRgnTemp = CreateRectRgn(iLeftX, iY, iX, iY+1);
         //Добавляем его к основному региону
         iRet = CombineRgn(hRgn, hRgn, hRgnTemp, RGN_OR);
         if(iRet == ERROR)
         {
            return;
         }
         //Удаляем временный регион
         DeleteObject(hRgnTemp);
         
      }while(iX       //Пока на линии еще остались пиксели, анализируем их.
      iX = 0;
   }
В результате в hRgn будет содержаться нужный нам регион.

Задача 3. Сделать динамически изменяющийся регион.
Ну, это совсем просто. Т.к. нерационально строить регионы каждый раз (особенно если они строяться по изображению), то заведем некоторый массив указателей на регионы, который мы заполним заранее.
HRGN hRng[20];
//инициализация регионов
….
//какая-та процедура, назначающая окну регион с номером i
int iRet;   
HRGN tTemp=CreateRectRgn(0,0,0,0);//если мы назначим регион, непосредственно взяв его из массива, то не сможем использовать его повторно
iRet = CombineRgn(tTemp, tTemp, hRgn[i], RGN_OR);
         if(iRet == ERROR)
         {
            return;
         }
iRet = SetWindowRgn(hwndDlg, tTemp, TRUE);

Результат использования этого кода можно увидеть на скриншоте.



Вот и все. Надеюсь, эта статья вам поможет.

Андрей Кирковский ака Риега


Компьютерная газета. Статья была опубликована в номере 01 за 2005 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета