所有的 Windows 窗体都位于一个称为“region”中,窗体的大小如果超出“region”的范围,windows 会自动裁剪超出"region"范围那部分的窗体,使其不可见。所以,要创建不规则窗体有两个步骤:第一步就是创建不规则"region".第二步就是将窗体放到创建的“region”中。
其中第二步很简单就调用一条语句即可。在SDK中调用API函数SetWindowRgn,该函数原型如下:相对与第二步,创建不规则窗体的第一步要复杂许多,并且不规则窗体越复杂,创建其"region"的过程也越复杂。接下去我们将由浅入深地介绍各种创建”region”的方法。
在windows.h头文件中,提供了如下的函数,来创建一些region,通过这些函数,可以直接创建出region区域供使用:
CreateEllipticRgn(int x1,int y1,int x2,int y2)//创建椭圆区域
其中(x1,y1)是椭圆的外接矩形的左上角的坐标,(x2,y2)是外接矩形的右下角的坐标。注意,坐标是相对于原来窗体(也即要变成不规则窗体的原来的规则的窗体)的左上角的,而不是相对于桌面
CreateEllipticRgnIndirect(RECT lpRECT)//创建椭圆区域
其中lpRECT是一个矩形区域,该矩形是椭圆的外接矩形
CreatePolygonRgn( POINTAPI lpPoint, long nCount, long nPolyFillMode)//建立多边形区域
注,windows将会自动将最后一个点与第一个点相连。
其中,lpPoint是nCount个POINTAPI结构中的第一个POINTAPI结构,nCount是多边形的点数,nPolyFillMode描述多边形填充模式。默认情况下是ALTERNATE。
模式ALTERNATE:其从封闭区域中的一个点向无穷远处水平画一条射线,只有当该射线穿越奇数条边框线时,封闭区域才被填充,如为偶数,则不填充该区域;
模式WINDING:方法一样,如为奇数,填充该区域;如为偶数则要根据边框线的方向来判断:如果穿过的边框线在不同方向的边框线数目相等,则不填充,如不等,则填充。
CreatePolyPolygonRgn(POINTAPI lpPoint,long lpPolyCounts,long nCount, long nPolyFillMode)//建立由多边形组成的区域
lpPoint,nCount个POINTAPI结构中的第一个POINTAPI结构
lpPolyCounts,长整数阵列的第一个入口。每个入口包含构成一个封闭多边形的点数。lpPoint阵列组成了一系列多边形,每个多边形在lpPolyCounts中有一个入口
nCount ,多边形的点数
nPolyFillMode,描述多边形填充模式。可为ALTERNATE 或 WINDING常数
CreateRoundRectRgn(int x1,int y1,int x2,int y2,int nWidthEllipse,int nHeightEllipse)//建立圆角矩形
其中,(x1,y1)为圆角矩形的外接矩形的左上角坐标,(x2,y2)为右下角坐标,nWidthEllipse指定创建的圆角的宽度逻辑单位,nHeightEllipse指定创建的圆角的高度逻辑单位
CreateRectRgn(int x1,int y1,int x2,int y2)//建立矩形区域
其中,(x1,y1)为矩形的左上角坐标,(x2,y2)为右下角坐标
CreateRectRgnIndirect(LPRECT rect)//用RECT结构建立一个区域
其中lpRect是要用来创建区域的矩形
示例代码:
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_CREATE:
{
HRGN hrgn=CreateEllipticRgn(50,50,250,250);
SetWindowRgn(hwnd,hrgn,TRUE);
break;
}
case WM_LBUTTONDOWN:
{
//这里的代码是用来让鼠标可以移动窗体的
SetCursor(LoadCursor(NULL, IDC_HAND));
UpdateWindow(hwnd);
ReleaseCapture();
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);//这句向操作系统发送消息模拟此时鼠标点击在标题栏上,由此实现移动
SetCursor(LoadCursor(NULL, IDC_ARROW));
break;
}
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
使用该方法创建”region”的过程如下:
第一步绘制所要创建的窗体形状。该步骤中使用到的一些成员函数如下:
BeginPath( HDC hdc);调用该函数后当前设备环境(DC)开始追踪绘图的过程。
SetBkMode( HDC hdc,int nBkMode );设置绘图时的背景模式,此应用中nBkMode必须取值为TRANSPARENT 。即设置绘图时背景不发生变化。
EndPath(HDC hdc);调用该函数后当前设备环境(DC)结束追踪绘图的过程。
开始绘图前,先调用BeginPath,然后调用SetBkMode。接下去就可调用其他绘图函数作图,例如Arc,AngleArc,LineTo,MoveTo,RoundRect,,Textout等等。绘图完毕调用EndPath().
第二步将绘制的结果转成”region”.
此步骤中使用SDK API函数:HRGN PathToRegion( HDC hdc );
下面详解一下可以在BeginPath和EndPath之间使用的一些绘图函数
AngleArc | 直线连弧绘制函数 |
Arc | 绘制一个椭圆圆弧 |
ArcTo | 绘制一个椭圆圆弧,ArcTo函数会将当前画笔位置设为弧的终点 |
chord | 画一条弦 |
Ellipse | 画一个椭圆 |
ExtTextOut | 该函数可以提供一个可选的矩形,用于裁剪或作不透明物或两者兼有。 |
LineTo | 画一条线,从当前位置连到一个指定的点 |
MoveToEx | 将当前绘图位置移动到某个具体的点,同时也可获得之前位置的坐标 |
Pie | 该函数画一个由椭圆和两条半径相交闭合而成的饼状楔形图,此饼图由当前画笔画轮廓,由当前画刷填充。 |
PolyBezier | 函数用于画贝赛尔样条曲线 |
PolyBezierTo | 描绘一条或多条贝塞尔(Bezier)曲线。PolyBezierTo用于将当前画笔位置设为前一条曲线的终点 |
PolyDraw | 描绘一条复杂的曲线,由线段及贝塞尔曲线组成 |
Polygon | 函数画一个由直线相连的两个以上顶点组成的多边形,用当前画笔画多边形轮廓,用当前画刷和多边形填充模式填充多边形。 |
Polyline | 用当前画笔描绘一系列线段 |
Rectangle | 画一个矩形 |
RoundRect | 函数画一个带圆角的矩形 |
TextOut | 该函数用当前选择的字体、背景颜色和正文颜色将一个字符串写到指定位置。 |
PolylineTo | 和PolyLine不同,PolyLineTo使用目前位置作为开始点,并将目前位置设定为最后 |
PolyPolygon | 该函数画一系列的多边形,每一个多边形都用当前的画笔画轮廓,用当前的画刷和多边形填充模式画填充。 |
PolyPolyline | 用当前选定画笔描绘两个或多个多边形 |
下面写一个示例:
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_CREATE:
{
HFONT hFont;
hFont = CreateFont(200,50,0,0,FW_REGULAR,FALSE,FALSE,FALSE,GB2312_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FIXED_PITCH | FF_MODERN,"华文行楷");
HDC hdc=GetWindowDC(hwnd);
BeginPath(hdc);
SetBkMode(hdc,TRANSPARENT);
SelectObject(hdc,hFont);
TextOut(hdc,10,30,"你好",6);
EndPath(hdc);
HRGN hrgn=PathToRegion(hdc);
SetWindowRgn(hwnd,hrgn, TRUE);
break;
}
case WM_LBUTTONDOWN:
{
//这里的代码是用来让鼠标可以移动窗体的
SetCursor(LoadCursor(NULL, IDC_HAND));
UpdateWindow(hwnd);
ReleaseCapture();
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);//这句向操作系统发送消息模拟此时鼠标点击在标题栏上,由此实现移动
SetCursor(LoadCursor(NULL, IDC_ARROW));
break;
}
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
注意:使用TextOut来创建path时,要注意必须自己重定义字体的。并不是所有的字体都可以创建path的,只有TrueType的字体才能够。而刚好,窗体默认的字体不是TrueType的,所以,不自己重新定义字体话,会看不到效果。。。
此法创建不规则窗体比较复杂。首先准备一张含有目标窗体形状的图片,设置透明色即将图片中部不属于窗体形状的部分,标记成同一种颜色,例如蓝色RGB(0,0,255).程序运行后先装入图片。然后逐个扫描图片的每个像素,如这个像素不属于透明色,则在相应位置创建一个只含一个像素的“region”然后将这些小”region ”合并起来组成一个任意形状的”region”。总结一下,步骤如下:
1.准备bmp位图一张,把你不需要的部分染色成MaskColor
2.把位图读取到dc中
3.创建一个dc大小的OriginRgn
4.循环此位图的各个像素点,察看像素的RGB。这里用到GetPixel()这个API
如果是你要去掉的MaskColor,就CreateRectRgn,设置宽高分别是1像素,并CombineRgn到DelRgn上
5.此时DelRgn是要去掉的所有Rgn 的 CombineRgn后的 Rgn ,OriginRgn是原图片大小的Rgn
这时候再CombineRgn,取两个Rgn不同的Rgn作为结果即可
这里用到 CombineRgn的参数 RGN_XOR,取异或。
6.SetWindowRgn,把结果Rgn用上。
下面贴出我的代码,给出实例:
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_CREATE:
{
HBITMAP hBmp=(HBITMAP)LoadImage(hInstance,"jessica_b.bmp",IMAGE_BITMAP,300,600,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
HRGN hrgn=BitmapToRegion(hBmp,RGB(0,0,0));
SetWindowRgn(hwnd,hrgn,TRUE);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
hdc =BeginPaint (hwnd, &ps) ;
HDC memDC=CreateCompatibleDC(hdc);
HBITMAP hBmp=(HBITMAP)LoadImage(hInstance,"jessica_b.bmp",IMAGE_BITMAP,300,600,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
SelectObject(memDC,hBmp);
BitBlt(hdc,-8,-30,300,600,memDC,0,0,SRCCOPY);
EndPaint(hwnd, &ps);
break;
}
case WM_LBUTTONDOWN:
{
//这里的代码是用来让鼠标可以移动窗体的
SetCursor(LoadCursor(NULL, IDC_HAND));
UpdateWindow(hwnd);
ReleaseCapture();
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);//这句向操作系统发送消息模拟此时鼠标点击在标题栏上,由此实现移动
SetCursor(LoadCursor(NULL, IDC_ARROW));
break;
}
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
如上所示,在WM_CREATE消息中,读取位图hBmp,然后使用BitmapToRegion函数将hBmp中的所需部分转化成region,随后设置窗口的region即可。但是,这并不够,在WM_CREATE消息中仅仅只是设置了窗体的region而已,并没有显示图片,仅仅建立了一个轮廓,所以,还需要在WM_PAINT消息中装载位图进去。。。
下面看看运行效果。。。左边是原来的位图,右边是依据位图创建的窗口
下面给出BitmapToRegion的代码,可以直接拿来使用:
/**
**作用:将bitmap中指定部分转化成region
**参数:hBmp是位图资源句柄
** cTransparentColor是位图中要隐藏的部分的颜色(默认为黑色)
** cTolerance是其中显示的部分,非必要
**返回值:位图中指定部分的region
**/
HRGN BitmapToRegion (HBITMAP hBmp, COLORREF cTransparentColor = 0, COLORREF cTolerance = 0x101010)
{
HRGN hRgn = NULL;
if (hBmp)
{
// Create a memory DC inside which we will scan the bitmap content
HDC hMemDC = CreateCompatibleDC(NULL);
if (hMemDC)
{
// Get bitmap size
BITMAP bm;
GetObject(hBmp, sizeof(bm), &bm);
// Create a 32 bits depth bitmap and select it into the memory DC
BITMAPINFOHEADER RGB32BITSBITMAPINFO = {
sizeof(BITMAPINFOHEADER), // biSize
bm.bmWidth, // biWidth;
bm.bmHeight, // biHeight;
1, // biPlanes;
32, // biBitCount
BI_RGB, // biCompression;
0, // biSizeImage;
0, // biXPelsPerMeter;
0, // biYPelsPerMeter;
0, // biClrUsed;
0 // biClrImportant;
};
VOID * pbits32;
HBITMAP hbm32 = CreateDIBSection(hMemDC, (BITMAPINFO *)&RGB32BITSBITMAPINFO, DIB_RGB_COLORS, &pbits32, NULL, 0);
if (hbm32)
{
HBITMAP holdBmp = (HBITMAP)SelectObject(hMemDC, hbm32);
// Create a DC just to copy the bitmap into the memory DC
HDC hDC = CreateCompatibleDC(hMemDC);
if (hDC)
{
// Get how many bytes per row we have for the bitmap bits (rounded up to 32 bits)
BITMAP bm32;
GetObject(hbm32, sizeof(bm32), &bm32);
while (bm32.bmWidthBytes % 4)
bm32.bmWidthBytes++;
// Copy the bitmap into the memory DC
HBITMAP holdBmp = (HBITMAP)SelectObject(hDC, hBmp);
BitBlt(hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDC, 0, 0, SRCCOPY);
// For better performances, we will use the ExtCreateRegion() function to create the
// region. This function take a RGNDATA structure on entry. We will add rectangles by
// amount of ALLOC_UNIT number in this structure.
#define ALLOC_UNIT 100
DWORD maxRects = ALLOC_UNIT;
HANDLE hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects));
RGNDATA *pData = (RGNDATA *)GlobalLock(hData);
pData->rdh.dwSize = sizeof(RGNDATAHEADER);
pData->rdh.iType = RDH_RECTANGLES;
pData->rdh.nCount = pData->rdh.nRgnSize = 0;
SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
// Keep on hand highest and lowest values for the "transparent" pixels
BYTE lr = GetRValue(cTransparentColor);
BYTE lg = GetGValue(cTransparentColor);
BYTE lb = GetBValue(cTransparentColor);
BYTE hr = min(0xff, lr + GetRValue(cTolerance));
BYTE hg = min(0xff, lg + GetGValue(cTolerance));
BYTE hb = min(0xff, lb + GetBValue(cTolerance));
// Scan each bitmap row from bottom to top (the bitmap is inverted vertically)
BYTE *p32 = (BYTE *)bm32.bmBits + (bm32.bmHeight - 1) * bm32.bmWidthBytes;
for (int y = 0; y < bm.bmHeight; y++)
{
// Scan each bitmap pixel from left to right
for (int x = 0; x < bm.bmWidth; x++)
{
// Search for a continuous range of "non transparent pixels"
int x0 = x;
LONG *p = (LONG *)p32 + x;
while (x < bm.bmWidth)
{
BYTE b = GetRValue(*p);
if (b >= lr && b <= hr)
{
b = GetGValue(*p);
if (b >= lg && b <= hg)
{
b = GetBValue(*p);
if (b >= lb && b <= hb)
// This pixel is "transparent"
break;
}
}
p++;
x++;
}
if (x > x0)
{
// Add the pixels (x0, y) to (x, y+1) as a new rectangle in the region
if (pData->rdh.nCount >= maxRects)
{
GlobalUnlock(hData);
maxRects += ALLOC_UNIT;
hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), GMEM_MOVEABLE);
pData = (RGNDATA *)GlobalLock(hData);
}
RECT *pr = (RECT *)&pData->Buffer;
SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1);
if (x0 < pData->rdh.rcBound.left)
pData->rdh.rcBound.left = x0;
if (y < pData->rdh.rcBound.top)
pData->rdh.rcBound.top = y;
if (x > pData->rdh.rcBound.right)
pData->rdh.rcBound.right = x;
if (y+1 > pData->rdh.rcBound.bottom)
pData->rdh.rcBound.bottom = y+1;
pData->rdh.nCount++;
// On Windows98, ExtCreateRegion() may fail if the number of rectangles is too
// large (ie: > 4000). Therefore, we have to create the region by multiple steps.
if (pData->rdh.nCount == 2000)
{
HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
if (hRgn)
{
CombineRgn(hRgn, hRgn, h, RGN_OR);
DeleteObject(h);
}
else
hRgn = h;
pData->rdh.nCount = 0;
SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
}
}
}
// Go to next row (remember, the bitmap is inverted vertically)
p32 -= bm32.bmWidthBytes;
}
// Create or extend the region with the remaining rectangles
HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
if (hRgn)
{
CombineRgn(hRgn, hRgn, h, RGN_OR);
DeleteObject(h);
}
else
hRgn = h;
// Clean up
SelectObject(hDC, holdBmp);
DeleteDC(hDC);
}
DeleteObject(SelectObject(hMemDC, holdBmp));
}
DeleteDC(hMemDC);
}
}
return hRgn;
}