【GCC】自定义MFC窗口

微软为我们程序员在Windows平台上的开发提供了一套UI的标准,但我们在日常生活中,会发现越来越多的软件界面采用高度自定义的形式,如界面上可以显示出绚丽多彩的图片等。这就是我们接下来要讨论的内容――换肤。我们通过增加界面的质感、图像化以及其他一些修改手段来达到深度的自定义效果。

 一些程序提供一个“换肤引擎”来将一些皮肤文件以一种预先定义好的方式运用到软件中。只要这些皮肤文件遵循预先定义好的那些要求,皮肤设计师就可以设计出非常绚丽的用户界面,比如非矩形的窗口。尽管非矩形的样式并不是换肤中必须的,但我们时常会在一些模拟真实世界设备的软件中看到它的身影。一款播放器的样子就像你家里的音响,上面有按钮、均衡器等,人们一看到就会知道怎么用。

 在决定对你的程序支持换肤前,你也许该思考以下这个问题:这样做对程序(更重要的是,对用户)是否有意义?如果你的程序界面是按照现实世界的某种设备(比如MP3播放器)来做的,那么这样做将会是非常有益的,用户会立刻明白它的用途。然而,如果仅仅只是为了做一个很酷很玄的界面,那么这可能会得不偿失。我用过的很多有换肤效果的软件,有的需要很多时间来弄明白如何使用,这样就给用户带来很多麻烦而没有帮助了。虽然一开始它们很耐看,但真正用过之后会让人很恼火。

 言归正传,下面我们将介绍换肤的一些基本思路。

 首先,Windows SDK中提供了一个非常重要的函数――SetWindowRgn,它将是制作一个非矩形窗口的关键。通过传一个window句柄和一个region句柄给这个函数,会使得系统将窗口的所有绘制管理都交给那个窗口的window procedure。系统会创建一个普通的矩形窗口并自动将那个region应用到那个窗口上。系统然后会确保任何位于窗口界限内但在region之外的那部分窗口似乎并不存在。其他任何位于region内的内容都交给window procedure去处理。这包括任何可能会在客户区及非客户区显示的内容,如菜单、工具栏、边框等。基本上,开发者请求系统创建一个空白的绘制区。而系统通过SetWindowRgn这个函数来满足请求,但将其他所有的工作都交给开发者来完成。所以,如果你要完全的控制显示的效果,那么你将需要实现所有的一切。

 皮肤文件通常是一些图片,而对于界面上每一个元素所处的状态都必须有对应的图片文件。例如,一个按钮通常需要4张图片――普通(默认)、按下、不可用以及选中。这些图片可能被合并到一个文件中,就像工具栏的一个bitmap文件会包含工具栏上所有的按钮。所有的皮肤文件可能会被压缩打包,并且还需要一个皮肤清单来说明这个包的内容以及如何将一个图片对应到窗口的各个区域。

 引擎要正确有效的处理皮肤并将皮肤运用到窗口上,这其中皮肤清单起着十分关键的作用。在皮肤清单中,有些信息会描述图片在窗口中所处的像素级别的位置。这些指令也可能会变得非常的精确复杂,并且包含与其他皮肤之间的相互关系以及发生冲突时的处理。

 一旦皮肤设计师将图片创建好并且皮肤清单也建立了,引擎就可以处理这些信息――将图片加载进来并将它们转换到对应的region。整个过程允许有非矩形的图片。事实上,主窗口的图片通常是以这样的方式加载并转换成一个region,这个region就可以送给SetWindowRgn了。

 Region是创建非标准化界面的一个十分灵活的工具。MSDN上对对region的定义如下:
 A region is a rectangle, polygon, or ellipse (or a combination of two or more of these shapes) that can be filled, painted, inverted, framed, and used to perform hit testing (testing for the cursor location).

 从以上的定义可以看出,region其实要比rect灵活的多。但有一点这个定义中没有提到:一个region内也可以包含一些“洞”,也就是它没有占据的一些地方。将显示器上的区域对应到自定义的一些图片,region给我们提供了操控这个过程的能力。这就使得我们可以可以创建不规则的图片,而这也是换肤工作的基础。

 然而,从一个图片中获取region信息不是一件容易的事。为完成这项工作,我们需要非常清楚图片的格式。例子中,我们仅用了BMP文件来简化说明,CWndRegion这个类就是用来做这样的事情。

class CWndRegion
{
public:
    CWndRegion(void);
    virtual ~CWndRegion(void);

protected:
    // Get the number of colors the bitmap contains.
    DWORD GetNumColors(LPVOID pBmp);
    // Get the size of the palette within the bitmap.
    DWORD GetPaletteSize(LPVOID pBmp);

public:
    // Read bitmap information and create a region.
    HRGN GetRegionFromImage(HBITMAP hBmp, COLORREF clrTransparency);

    DWORD GetHeight() const { return m_dwHeight; }
    DWORD GetWidth() const { return m_dwWidth; }

protected:
    DWORD m_dwHeight;
    DWORD m_dwWidth;
};
	  


 

CWndRegion的说明:

 GetNumColors,获取bitmap中用到的颜色数量;

 GetPaletteSize,获取调色板大小;

 GetRegionFromImage,这是用户主要需要调用的方法,用户只需传入一个bitmap的句柄和一个透明色,然后它就会返回一个region句柄。

 GetHeight和GetWidth返回的分别是经过GetRegionFromImage处理的bitmap的高度和宽度。

 接下来我们用CWndRegion来改造一个从窗口:

 

class CSkinMFCDlg : public CDialog
{
…
protected:
    void ModifyRegion();
…
    CDC m_BitmapDC;
    CBitmap m_Bitmap;
    CBitmap *m_pOldBitmap;

    CWndRegion m_rgn;

    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp);
…
};

BOOL CSkinMFCDlg::OnInitDialog()
{
…
    ModifyRegion();
…
}

void CSkinMFCDlg::OnPaint()
{
    CPaintDC dc(this);
    dc.BitBlt(0, 0, m_rgn.GetWidth(), m_rgn.GetHeight(), &m_BitmapDC, 0, 0, SRCCOPY);
}

void CSkinMFCDlg::ModifyRegion()
{
    m_Bitmap.LoadBitmap(IDB_BITMAP1);

    CDC *pDC = GetDC();
    m_BitmapDC.CreateCompatibleDC(pDC);
    m_pOldBitmap = m_BitmapDC.SelectObject(&m_Bitmap);

    HRGN hBmpRgn = m_rgn.GetRegionFromImage((HBITMAP)m_Bitmap.m_hObject, RGB(255, 0, 255));

    SetWindowPos(NULL, 0, 0, m_rgn.GetWidth(), m_rgn.GetHeight(), SWP_NOZORDER | SWP_NOMOVE);

    SetWindowRgn(hBmpRgn, FALSE);
}

BOOL CSkinMFCDlg::OnEraseBkgnd(CDC* pDC)
{
    return FALSE;
}

void CSkinMFCDlg::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
    return;
}

CSkinMFCDlg::ModifyRegion()是整个过程的关键。首先,它将bitmap加载进来,再创建了一个CompatibleDC,这个DC将前面那个bitmap选进来,在OnPaint中这个DC将起到重要作用。然后调用CWndRegion::GetRegionFromImage来获得一个region句柄,这个过程中bitmap的高和宽也将被计算出来,通过SetWindowPos来改变窗口的大小,最后将region句柄可以送给SetWindowRgn来改变窗口形状。

 在CSkinMFCDlg::OnPaint中,我们利用之前创建的那个CompatibleDC来绘制,这样就可以把bitmap中的内容显示出来,从而达到窗口换肤的效果。

 另外,WM_ERASEBKGND和WM_NCCALCSIZE的消息相应函数都有被改写了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值