用MFC做VS起始页界面
好的软件有好的开始,微软VS IDE的起始界面做的实在不错,色调和布局都是很舒服,这里我动手实现了一个,下面是过程。
第一步:用spy++工具观察VS IDE开始界面构成。
通过spy++,可以看到开始页由一个大窗口,里面是位图背景,在上面有一些静态窗口,按钮等,之间夹一个小视图(例如最近的项目里面是一个listctrl)。基本就是这样,实现思路就是用位图贴在静态小窗口构成一个小的栏,中间放一个小的主视图,比如项目列表、msdn新闻等。
第二步:收集位图素材。我没有那么好的艺术细胞画这么好的小图片,所以只得偷vs的。由于使用vc6,所以下面全部基于vc6环境来说。用资源的方式打开vs ide执行文件(\Microsoft Visual Studio 8\Common7\IDE\devenv.exe),可以看到位图资源基本全是起始页的。
第三步:实现思路确定。有一些问题,里面的图片好像没有主界面那么大的位图,头部好像不是太符合尺寸:
这下我的思路是先用背景色绘制窗口,然后贴图。背景色可以用取色工具获取,我用firework里面的工具。
还有一个问题就是小栏位图的四角有颜色:
贴到主窗口上去会留下与环境不相称的一点点多余的角,这里我在网上搜到一篇mask位图的文章,基于此,把位图四角的淡白色去掉。
还有就是中间的位图太小:
我准备采取重复绘制来填满一定长度,注意水平方向和垂直方向稍有不同,所以实现的时候要进行判断。
这样基本敲打实现方法,接着就动手实现。
第四步:开工。vc6建立一个单文档基于formview的程序。
工序一:绘制背景
对话框的背景颜色可以通过重载OnCtlColor来实现,而不是OnEraseBackground。
HBRUSH CVS2005StartView::OnCtlColor(CDC
*
pDC, CWnd
*
pWnd, UINT nCtlColor)
{
HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if(nCtlColor == CTLCOLOR_DLG)
{
return m_bkBrush;
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
上面只修改对话框背景,所以进行了判断,m_bkBrush就是背景画刷。
{
HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if(nCtlColor == CTLCOLOR_DLG)
{
return m_bkBrush;
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
工序二:贴图,在OnPaint里面绘制Visual studio的大位图。
void
CVS2005StartView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CDC memDC;
HBITMAP hOldBmp = NULL;
BITMAP bmp;
memDC.CreateCompatibleDC(&dc);
hOldBmp = (HBITMAP)memDC.SelectObject(&m_bmpHeader);
m_bmpHeader.GetBitmap(&bmp);
BitBlt(dc, 0, 0, bmp.bmWidth, bmp.bmHeight, memDC, 0, 0, SRCCOPY);
memDC.SelectObject(hOldBmp);
// Do not call CFormView::OnPaint() for painting messages
}
这样就所有存托背景,下面开始实现每个小栏目。实现一个就ok,思路都是一样的。
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CDC memDC;
HBITMAP hOldBmp = NULL;
BITMAP bmp;
memDC.CreateCompatibleDC(&dc);
hOldBmp = (HBITMAP)memDC.SelectObject(&m_bmpHeader);
m_bmpHeader.GetBitmap(&bmp);
BitBlt(dc, 0, 0, bmp.bmWidth, bmp.bmHeight, memDC, 0, 0, SRCCOPY);
memDC.SelectObject(hOldBmp);
// Do not call CFormView::OnPaint() for painting messages
}
工序三:实现绘制位图的静态窗口。从CStatic派生CBmpStatic类来实现绘制位图功能。考虑到上面提及的情况,函数接口如下:
void
SetBitmap(UINT nBmpID, BOOL bHorzStretch
=
TRUE);
void SetTitle(CString strTitle);
实现如下:
void SetTitle(CString strTitle);
void
CBmpStatic::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CDC srcDC;
CDC mskDC;
CBitmap mskBmp;
BITMAP bmp;
srcDC.CreateCompatibleDC(&dc);
mskDC.CreateCompatibleDC(&dc);
m_bmpBack.GetBitmap(&bmp);
mskBmp.CreateBitmap(bmp.bmWidth, bmp.bmHeight, 0, 1, NULL);
srcDC.SelectObject(&m_bmpBack);
srcDC.SetBkColor(RGB(0xEB, 0xF1, 0xFE));
mskDC.SelectObject(mskBmp);
mskDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &srcDC, 0, 0, SRCCOPY);
srcDC.SetBkColor(RGB(255,255,255));
srcDC.SetTextColor(RGB(0,0,0));
LONG lXStart = 0;
LONG lXEnd = bmp.bmWidth;
LONG lYStart = 0;
LONG lYEnd = bmp.bmHeight;
CRect rc;
GetClientRect(&rc);
if(m_bHorzStretch)
{
while(lXStart <= rc.Width())
{
if(lXEnd > rc.Width())
{
lXEnd = rc.Width();
}
dc.BitBlt(lXStart,0, lXEnd-lXStart, bmp.bmHeight, &srcDC, 0, 0, SRCINVERT);
dc.SetBkColor(RGB(255,255,255));
dc.BitBlt(lXStart, 0, lXEnd-lXStart, bmp.bmHeight, &mskDC, 0, 0, SRCAND);
dc.BitBlt(lXStart, 0, lXEnd-lXStart, bmp.bmHeight, &srcDC, 0, 0, SRCINVERT);
lXStart += bmp.bmWidth;
lXEnd += bmp.bmWidth;
}
}
else
{
while(lYStart <= rc.Height())
{
if(lYEnd > rc.Height())
{
lYEnd = rc.Height();
}
dc.BitBlt(0,lYStart, bmp.bmWidth, lYEnd-lYStart, &srcDC, 0, 0, SRCINVERT);
dc.SetBkColor(RGB(255,255,255));
dc.BitBlt(0,lYStart, bmp.bmWidth, lYEnd-lYStart, &mskDC, 0, 0, SRCAND);
dc.BitBlt(0,lYStart, bmp.bmWidth, lYEnd-lYStart, &srcDC, 0, 0, SRCINVERT);
lYStart += bmp.bmHeight;
lYEnd += bmp.bmHeight;
}
}
srcDC.DeleteDC();
mskDC.DeleteDC();
if(!m_strTitle.IsEmpty())
{
dc.SelectObject(m_ftTitle);
dc.SetTextColor(RGB(255, 255, 255));
dc.SetBkMode(TRANSPARENT);
dc.TextOut(0, 4, m_strTitle);
}
}
void CBmpStatic::SetBitmap(UINT nBmpID, BOOL bHorzStretch)
{
if(m_bmpBack.GetSafeHandle())
{
m_bmpBack.DeleteObject();
}
m_bmpBack.LoadBitmap(nBmpID);
m_bHorzStretch = bHorzStretch;
}
void CBmpStatic::SetTitle(CString strTitle)
{
m_strTitle = strTitle;
}
好了,有了显示位图的窗口,下面我们就来摆出一个栏目(最近的项目)。
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CDC srcDC;
CDC mskDC;
CBitmap mskBmp;
BITMAP bmp;
srcDC.CreateCompatibleDC(&dc);
mskDC.CreateCompatibleDC(&dc);
m_bmpBack.GetBitmap(&bmp);
mskBmp.CreateBitmap(bmp.bmWidth, bmp.bmHeight, 0, 1, NULL);
srcDC.SelectObject(&m_bmpBack);
srcDC.SetBkColor(RGB(0xEB, 0xF1, 0xFE));
mskDC.SelectObject(mskBmp);
mskDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &srcDC, 0, 0, SRCCOPY);
srcDC.SetBkColor(RGB(255,255,255));
srcDC.SetTextColor(RGB(0,0,0));
LONG lXStart = 0;
LONG lXEnd = bmp.bmWidth;
LONG lYStart = 0;
LONG lYEnd = bmp.bmHeight;
CRect rc;
GetClientRect(&rc);
if(m_bHorzStretch)
{
while(lXStart <= rc.Width())
{
if(lXEnd > rc.Width())
{
lXEnd = rc.Width();
}
dc.BitBlt(lXStart,0, lXEnd-lXStart, bmp.bmHeight, &srcDC, 0, 0, SRCINVERT);
dc.SetBkColor(RGB(255,255,255));
dc.BitBlt(lXStart, 0, lXEnd-lXStart, bmp.bmHeight, &mskDC, 0, 0, SRCAND);
dc.BitBlt(lXStart, 0, lXEnd-lXStart, bmp.bmHeight, &srcDC, 0, 0, SRCINVERT);
lXStart += bmp.bmWidth;
lXEnd += bmp.bmWidth;
}
}
else
{
while(lYStart <= rc.Height())
{
if(lYEnd > rc.Height())
{
lYEnd = rc.Height();
}
dc.BitBlt(0,lYStart, bmp.bmWidth, lYEnd-lYStart, &srcDC, 0, 0, SRCINVERT);
dc.SetBkColor(RGB(255,255,255));
dc.BitBlt(0,lYStart, bmp.bmWidth, lYEnd-lYStart, &mskDC, 0, 0, SRCAND);
dc.BitBlt(0,lYStart, bmp.bmWidth, lYEnd-lYStart, &srcDC, 0, 0, SRCINVERT);
lYStart += bmp.bmHeight;
lYEnd += bmp.bmHeight;
}
}
srcDC.DeleteDC();
mskDC.DeleteDC();
if(!m_strTitle.IsEmpty())
{
dc.SelectObject(m_ftTitle);
dc.SetTextColor(RGB(255, 255, 255));
dc.SetBkMode(TRANSPARENT);
dc.TextOut(0, 4, m_strTitle);
}
}
void CBmpStatic::SetBitmap(UINT nBmpID, BOOL bHorzStretch)
{
if(m_bmpBack.GetSafeHandle())
{
m_bmpBack.DeleteObject();
}
m_bmpBack.LoadBitmap(nBmpID);
m_bHorzStretch = bHorzStretch;
}
void CBmpStatic::SetTitle(CString strTitle)
{
m_strTitle = strTitle;
}
工序四:摆置窗口,实现栏目。这个是细致活,先把中间的小主视图控件摆上,然后四周放置8个static,基本如下(可能需要根据显示结果来适当调整)。
然后为每个static控件关联一个CBmpStatic,并设置位图和显示文本。
void
CVS2005StartView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
m_wndPrjUpLeft.SetBitmap(6609);
m_wndPrjUpMiddle.SetBitmap(6610);
m_wndPrjUpMiddle.SetTitle(_T("最近的项目"));
m_wndPrjUpRight.SetBitmap(6611);
m_wndMiddleLeft.SetBitmap(6612, FALSE);
m_wndMiddleRight.SetBitmap(6613, FALSE);
m_wndBottomLeft.SetBitmap(6614);
m_wndBottomMiddle.SetBitmap(6615);
m_wndBottomRight.SetBitmap(6616);
m_imgProject.Create(16, 16, ILC_COLOR8|ILC_MASK, 2, 0);
m_imgProject.Add(AfxGetApp()->LoadIcon(MAKEINTRESOURCE(6826)));
m_wndPrjList.SetImageList(&m_imgProject, LVSIL_SMALL);
m_wndPrjList.InsertItem(0, _T("HTMLKit"), 0);
m_wndPrjList.InsertItem(1, _T("Mapgis7"), 0);
m_wndNewsUpLeft.SetBitmap(6609);
m_wndNewsUpMiddle.SetBitmap(6610);
m_wndNewsUpMiddle.SetTitle(_T("Visual Studio 开发人员新闻"));
m_wndNewsUpRight.SetBitmap(6611);
m_wndNewsMiddleLeft.SetBitmap(6612, FALSE);
m_wndNewsMiddleRight.SetBitmap(6613, FALSE);
m_wndNewsBottomLeft.SetBitmap(6614);
m_wndNewsBottomMiddle.SetBitmap(6615);
m_wndNewsBottomRight.SetBitmap(6616);
m_wndNewsInfo.SetFont(&m_ftNewInfo);
m_wndNewsInfo.AddString(_T("当前的新闻频道可能无效或者你的 Internet"));
m_wndNewsInfo.AddString(_T("连接不可用.若要更新新闻频道,请在\"工具\"菜单上点击\"选项\",然后展开"));
}
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
m_wndPrjUpLeft.SetBitmap(6609);
m_wndPrjUpMiddle.SetBitmap(6610);
m_wndPrjUpMiddle.SetTitle(_T("最近的项目"));
m_wndPrjUpRight.SetBitmap(6611);
m_wndMiddleLeft.SetBitmap(6612, FALSE);
m_wndMiddleRight.SetBitmap(6613, FALSE);
m_wndBottomLeft.SetBitmap(6614);
m_wndBottomMiddle.SetBitmap(6615);
m_wndBottomRight.SetBitmap(6616);
m_imgProject.Create(16, 16, ILC_COLOR8|ILC_MASK, 2, 0);
m_imgProject.Add(AfxGetApp()->LoadIcon(MAKEINTRESOURCE(6826)));
m_wndPrjList.SetImageList(&m_imgProject, LVSIL_SMALL);
m_wndPrjList.InsertItem(0, _T("HTMLKit"), 0);
m_wndPrjList.InsertItem(1, _T("Mapgis7"), 0);
m_wndNewsUpLeft.SetBitmap(6609);
m_wndNewsUpMiddle.SetBitmap(6610);
m_wndNewsUpMiddle.SetTitle(_T("Visual Studio 开发人员新闻"));
m_wndNewsUpRight.SetBitmap(6611);
m_wndNewsMiddleLeft.SetBitmap(6612, FALSE);
m_wndNewsMiddleRight.SetBitmap(6613, FALSE);
m_wndNewsBottomLeft.SetBitmap(6614);
m_wndNewsBottomMiddle.SetBitmap(6615);
m_wndNewsBottomRight.SetBitmap(6616);
m_wndNewsInfo.SetFont(&m_ftNewInfo);
m_wndNewsInfo.AddString(_T("当前的新闻频道可能无效或者你的 Internet"));
m_wndNewsInfo.AddString(_T("连接不可用.若要更新新闻频道,请在\"工具\"菜单上点击\"选项\",然后展开"));
}
好了,下面来看看我们的窗口。
花费半天时间,只是图实现,封装性不是很好。
总结:
看过很多漂亮的界面,qq、泡泡堂登录界面等,思路差不多,都是基于贴图来做的,包括游戏的实现方式也差不多。界面的美观其实主要靠艺术家,包括图片效果以及窗口布局。
不足:
1、没有使用双缓存,拖动的时候背景有闪烁。
2、滚动条滑动的时候有问题,暂时没时间处理。
3、代码凌乱,不晓得资源句柄有没有释放干净。
4、开发新闻那个listbox是自绘的,可以通过spy++看到,我没时间做。
还有。。。
代码下载