第一次做程序界面美化工作(1):添加背景图片

Tag: 编程 C++ MFC 经验

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://shujiantang.blogbus.com/logs/29644154.html

前不久做了一个MFC项目。项目做完了,客户觉得MFC的标准界面太难看。尽管之前从未做过界面美化工作,但心想这东西网上资源丰富,应该不是难事,就满口答应了。这一答应不打紧,着实让我下足了工夫。

 

先大致介绍一下项目界面的构成。项目界面采用MFC的对话框模式,在父窗口中镶嵌子窗口来表现程序的层次结构,而且这种镶嵌不止一级。

 

具体的美化资源由客户提供,一张背景图和若干按钮图片,倒也简单。

 

我选择先添加背景图。因为设置背景应该相对容易点,毕竟那么多按钮做下来工作量还是挺大的。

我通过IDE的向导将图片添加到资源中,资源名为IDB_BITMAP_BACKGROUND

随后覆写了主窗口类的OnEraseBkgnd函数,将图片用于主窗口的背景。

但是写完一运行就傻眼了。背景图片只在主窗口的空余区显示出来了。占窗口大部分区域的控件和子窗口内都保持不变。这和我的预期相错太远了。这样的话,岂不是要为每个控件和子窗口写背景代码?即便采用继承方式也相当麻烦啊。

但是等等,MFC会不会提供特殊的机制来解决这个问题呢?比如说可以让子窗口(控件本质上也是子窗口)的背景继承父窗口呢?想想,还真有这种可能。于是就开始上网搜索,查看MSDN,但并未找到关于背景继承的机制,看来MFC并未提供这样的功能。

不过在找的过程中,我接触到了“窗口透明化”的信息。“透明窗口”倒很熟悉,因为之前做截图程序时用到过。仔细想来,“透明窗口”对我的设想还真有点用处。我可以将子窗口们设为透明(或者半透明),这样父窗口的背景不就显示出来了吗?而且半透明说不定能表现出更好的效果呢!

但是(又是但是),我按照想法做完之后,再次郁闷。窗口透明针对的是整个窗口,这样一来子窗口中的所有内容都是透明的了,而不仅仅是背景!

难道必须放弃透明窗口的方案吗?不甘心的我再次仔细阅读MSDN,发现分层窗口可以仅透明化指定的颜色,相关信息如下:

函数原型:

BOOL CWnd::SetLayeredWindowAttributes(COLORREF crKey,BYTE bAlpha,DWORD dwFlags);

参数解释:

crKey

Pointer to a COLORREF value that specifies the transparency color key to be used when composing the layered window. All pixels painted by the window in this color will be transparent. To generate a COLORREF, use the RGB macro.

指定用于构造分层窗口时的透明色键(用COLORREF结构描述)。窗口中颜色为指定色键的像素将被透明处理。可以用RGB宏生成COLORREF值。

 

对于标准的MFC窗口来说,背景颜色全都是灰色。如果将“crKey”设为这种灰色,不就可以仅透明化背景了吗?将背景图用于主窗口上,子窗口透明背景色应该可以达到预期效果,看来实现有望。

但仍有问题。一方面子窗口众多,一个个实现仍很麻烦;另一方面,主窗口上的控件颜色亦要想办法处理。

有没有一个简洁的办法呢?我有了一个大胆的想法:

1.       添加一个背景窗口,在其上显示背景图。

2.       背景窗口不显示在任务栏中,更准确的说是不能让用户觉察到背景窗口的存在。

3.       背景窗口与主窗口保持同样尺寸,重合于主窗口并紧位于其后,与主窗口同步移动。

4.       主窗口的标准背景颜色设为透明(主窗口透明化,其麾下的子窗口和控件自然跟随其透明化,省去很多麻烦)。

如此一来就能实现预期的目标了,但要增加一个窗口。这会对窗口操作带来大的影响吗?只有尝试一下了。

于是我创建了背景窗口类CbackgroundWnd,直接继承于CWnd,并覆写了它的OnEraseBkgnd函数,在其上显示背景图:

//清除背景响应

BOOL CBackgroundWnd::OnEraseBkgnd(CDC* pDC)

{

     //获得客户区尺寸

     CRect rect;  

    GetClientRect(&rect);

     //加载背景位图

     CBitmap bitmap;

     bitmap.LoadBitmap(IDB_BITMAP_BACKGROUND);

     //创建内存DC

     CDC dc;

     dc.CreateCompatibleDC(pDC);

     //选择位图

     CBitmap* pOldBitmap=dc.SelectObject(&bitmap);

     //绘制位图

     pDC->BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);

     //恢复原有位图

     dc.SelectObject(pOldBitmap);

 

     return TRUE;

}

 

为了不让背景窗口显示在任务栏上,我在CbackgroundWnd类的Create函数中将其扩展窗口类型设为WS_EX_TOOLWINDOW

//创建函数

BOOL CBackgroundWnd::Create()

{

     //注册窗口类

     CString className;

     try

     {

         className=AfxRegisterWndClass(NULL);

     }

     catch(CResourceException* pEx)

     {

         pEx->Delete();

         return false;

     }

 

     //创建窗口

     //设置为工具窗口可以使背景窗口不显示在任务栏中

     if(!CreateEx(WS_EX_TOOLWINDOW,className,NULL,WS_POPUP,0,0,1,1,NULL,NULL))

         return false;

 

     return true;

}

 

还有一个问题要处理。背景窗口被设计为主窗口的背景,是不允许用户直接对其操作的。本来我想背景窗口被主窗口完全覆盖,是不可能接收到用户的鼠标点击信息的。但是始料未及的是,窗口透明后鼠标指令就可以穿透窗口达到后方。于是我在CbackgroundWnd类中覆写了函数OnMouseActivate,屏蔽了鼠标激活:

//鼠标激活响应

int CBackgroundWnd::OnMouseActivate(CWnd* pDesktopWnd,UINT nHitTest,UINT message)

{

     //屏蔽鼠标激活

     //背景窗口位于主窗口下方,不能被激活

     return MA_NOACTIVATEANDEAT;

}

 

这样背景窗口类就创建完成了。接下来我在主窗口类CMainDlg中定义了背景窗口成员变量m_backgroundWnd,并在CMainDlg类的OnInitDialog函数中创建了背景窗口,同时也在其中将主窗口设为背景透明:

//初始化对话框函数

BOOL CMainDlg::OnInitDialog()

{

     //其它代码...

 

     //设置分层窗口

     LONG style=::GetWindowLong(GetSafeHwnd(),GWL_EXSTYLE);

     style|=WS_EX_LAYERED;

     style=::SetWindowLong(GetSafeHwnd(),GWL_EXSTYLE,style);

     style=::GetWindowLong(GetSafeHwnd(),GWL_EXSTYLE);

     //获得窗口默认背景色

     COLORREF bkColor=::GetSysColor(COLOR_3DFACE);

     //设置分层窗口属性

     SetLayeredWindowAttributes(bkColor,100,LWA_COLORKEY);

 

     //创建背景窗口

     m_backgroundWnd.Create();

 

     //其它代码...

 

     return TRUE;  // 除非将焦点设置到控件,否则返回TRUE

}

 

为了使背景窗口与主窗口保持同样尺寸,重合于主窗口并紧位于其后,与主窗口同步移动,我在CMainDlg类中创建了AdjustBackGroundWnd函数,用于调整背景窗口的尺寸和位置。该函数在需要的时候被调用:

//调整背景窗口位置

void CMainDlg::AdjustBackGroundWnd()

{

     if(m_backgroundWnd.GetSafeHwnd()==NULL)

         return;

 

     //获得窗口客户区位置

     CRect clientRect;

     GetClientRect(&clientRect);

     ClientToScreen(&clientRect);

 

     //调整尺寸

     m_backgroundWnd.SetWindowPos(this,clientRect.left,clientRect.top,clientRect.Width(),clientRect.Height(),SWP_NOACTIVATE|SWP_SHOWWINDOW);

}

 

该在哪些场合下调用该函数呢?起初我只想到了移动主窗口时,所以对CMainDlg类的OnMove函数进行了覆写:

//窗口移动响应

void CFlyMessagerDlg::OnMove(int x,int y)

{

     AdjustBackGroundWnd();

}

 

这样写好后,运行程序测试,不管是窗口移动还是最小化,效果都不错,用户绝不会发觉还有一个背景窗口。但还是发现了一个问题:当窗口被其它窗口遮挡,点击窗口有效部位激活窗口时,背景窗口没有紧跟上来,导致窗口遮挡部分显示错误。不过这倒好办,我对CMainDlg类的OnActivateApp函数进行覆写,解决了这个问题:

//激活程序响应

void CFlyMessagerDlg::OnActivateApp(BOOL bActive,DWORD dwThreadID)

{

     if(bActive)

     {

         AdjustBackGroundWnd();

     }

}

 

再次进行测试,一切运行正常!到此为止,终于完成了背景设置工作。稍有遗憾的是,由于存在一个背景窗口,导致从最小化恢复窗口时会有较明显的闪烁现象。另外,由于将MFC的标准背景颜色设为透明色键,导致窗口其它地方出现这种颜色时也变为透明色。好在这种情况不多,总体看来无甚大碍。

你可能感兴趣的:(第一次做程序界面美化工作(1):添加背景图片)