版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
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的标准背景颜色设为透明色键,导致窗口其它地方出现这种颜色时也变为透明色。好在这种情况不多,总体看来无甚大碍。