Windows SDK实现不规则窗体

1.原理

所有的 Windows 窗体都位于一个称为“region”中,窗体的大小如果超出“region”的范围,windows 会自动裁剪超出"region"范围那部分的窗体,使其不可见。所以,要创建不规则窗体有两个步骤:第一步就是创建不规则"region".第二步就是将窗体放到创建的“region”中。

其中第二步很简单就调用一条语句即可。在SDK中调用API函数SetWindowRgn,该函数原型如下:
int SetWindowRgn( HWND hWnd, HRGN hRgn, BOOL bRedraw );
其中hWnd为待设置的窗体句柄,hRgn为已经创建的"region"句柄,bRedraw代表是否要重绘窗体。

相对与第二步,创建不规则窗体的第一步要复杂许多,并且不规则窗体越复杂,创建其"region"的过程也越复杂。接下去我们将由浅入深地介绍各种创建”region”的方法。


2.利用内置函数创建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;
}

上面的代码可以创建一个圆形的窗体,图片如下:

Windows SDK实现不规则窗体_第1张图片

3.用作图法创建region

使用该方法创建”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;
}

上面的代码创建了一个字体样的不规则代码,图片如下:

Windows SDK实现不规则窗体_第2张图片


注意:使用TextOut来创建path时,要注意必须自己重定义字体的。并不是所有的字体都可以创建path的,只有TrueType的字体才能够。而刚好,窗体默认的字体不是TrueType的,所以,不自己重新定义字体话,会看不到效果。。。

4.根据图像创建region

此法创建不规则窗体比较复杂。首先准备一张含有目标窗体形状的图片,设置透明色即将图片中部不属于窗体形状的部分,标记成同一种颜色,例如蓝色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消息中装载位图进去。。。

下面看看运行效果。。。左边是原来的位图,右边是依据位图创建的窗口

Windows SDK实现不规则窗体_第3张图片                    Windows SDK实现不规则窗体_第4张图片

下面给出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;
}



你可能感兴趣的:(Windows,SDK)