1.6 界面窗体应用实例
在设计程序界面时,首先考虑的是对话框的设计。本节通过几个实例介绍各种对话框程序界面效果的设计。
实例018 使用位图设计畸形界面
本实例是一个提高效率、人性化的程序
实例位置:光盘\mingrisoft\01\018
实例说明
在开发应用程序时为使程序界面更加美观,可以将程序界面设计成各种不规则图案。本实例实现了使用位图设计畸形界面。运行程序,程序的背景由位图的图案进行设置,然后再将位图画到程序背景上。程序运行效果如图1.23所示。
图1.23 使用位图设计畸形界面
技术要点
利用位图设计不规则窗体的主要思路是把想挖去的区域设成黑色,然后利用GetPixe方法挖去背景色,将不是背景色的区域用CombineRgn方法连接起来,最后用SetWindowRgn方法设置窗体区域。
CombineRgn方法用于设置一个CRgn对象,使它等效于两个指定的CRgn对象的联合。语法如下:
int CombineRgn( CRgn* pRgn1, CRgn* pRgn2, int nCombineMode );
参数说明:
● pRgn1:一个已经存在的区域。
● pRgn2:一个已经存在的区域。
● nCombineMode:组合两个源区域时要执行的操作。
■ RGN_AND:使用两个区域相互重叠的区域,即相交的部分。
■ RGN_COPY:创建参数pRgn1标识的区域的一个备份。
■ RGN_DIFF:创建一个区域,该区域由区域1(pRgn1标识的区域)去除在区域(2 pRgn2标识的区域)中的部分而形成的区域。
■ RGN_OR:组合两个区域的所有部分。
■ RGN_XOR:组合两个区域,去除相互重叠的区域。
实现过程
(1)新建名为Catface的对话框应用程序。
(2)向工程中添加一个位图资源。
(3)在对话框的OnPaint方法中设置窗体区域,代码如下:
void CCatfaceDlg::OnPaint() { //系统代码省略 CDC*pDC=GetDC(); //获得设备上下文 CDC memDC; CBitmap bitmap; //声明位图对象 CBitmap* bmp = NULL; COLORREF col; CRect rc; int x,y; CRgn rgn, tmp; GetWindowRect(&rc); //获得窗体区域 bitmap.LoadBitmap(IDB_BITMAP1); //装载模板位图 memDC.CreateCompatibleDC(pDC); //创建与内存兼容的设备上下文 bmp = memDC.SelectObject(&bitmap); rgn.CreateRectRgn(0,0,rc.Width(),rc.Height()); //初始化区域 //计算得到区域 for(x=0; x<=rc.Width(); x++) { for(y=0; y<=rc.Height(); y++) { //将背景部分去掉 col=memDC.GetPixel(x,y); //得到像素颜色 if(col==RGB(255,255,255)) //如果是背景颜色 { tmp.CreateRectRgn(x,y,x+1,y+1); //创建区域 rgn.CombineRgn(&rgn,&tmp,RGN_XOR); //去除相互重叠的区域 tmp.DeleteObject(); //删除区域对象 } } } SetWindowRgn((HRGN)rgn,TRUE); //设置窗体为区域的形状 }
(4)处理窗体的WM_CTLCOLOR消息,在该消息的处理函数中绘制窗体背景位图,代码如下:
HBRUSH CCatfaceDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); CBitmap m_BKGround; m_BKGround.LoadBitmap(IDB_BITMAP1); //加载图片资源 if (nCtlColor==CTLCOLOR_DLG) { CBrush m_Brush(&m_BKGround); //定义一个位图画刷 CRect rect; GetClientRect(rect); //获得窗体的客户区域 pDC->SelectObject(&m_Brush); //选中画刷 pDC->FillRect(rect,&m_Brush); //填充客户区域 return m_Brush; } else hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); return hbr; }
举一反三
根据本实例,读者可以:
实现菜单背景颜色的渐变;
实现工具栏背景颜色的渐变。
实例019 自绘窗体界面
本实例可以方便操作、提高效率
实例位置:光盘\mingrisoft\01\019
实例说明
如今的软件不仅具有强大的功能,还具有漂亮的界面,这些漂亮的界面大大增加了用户使用程序的乐趣。例如,大家上网经常使用的瑞星、腾讯OICQ等软件。本实例实现了一个自绘的窗体界面,效果如图1.24所示。
图1.24 自绘窗体界面
技术要点
要实现窗体的绘制并不像想象中那么复杂。首先需要准备几个漂亮的位图,用于作为窗体的边框和标题栏,然后利用设备上下文CDC将其绘制在窗体上就可以了。CDC提供了StretchBlt方法,用于绘制图像。其语法如下:
BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );
参数说明:
● x、y:表示目标区域的左上角坐标。
● nWidth、nHeight:表示目标区域的宽度和高度。
● pSrcDC:表示源设备上下文指针。
● xSrc、ySrc:表示源设备上下文的左上角坐标。
● nSrcWidth、nSrcHeight:表示源设备上下文的宽度和高度。
● dwRop:表示光栅效果。
绘制窗体的标题栏和边框时不能使用GetDC方法获得设备上下文指针,因为GetDC方法获得的是窗体客户区域的设备上下文指针。应使用GetWindowDC方法获得窗口设备上下文指针。
本实例在标题栏上利用位图绘制了几个按钮,当用户单击不同的按钮时,会执行相应的操作。
在绘制标题栏按钮时,需要知道标题栏按钮的显示区域(该区域由用户根据位图的大小适当设置),该区域会随着窗体的大小变化而不同。知道了按钮的显示区域,当用户鼠标在标题栏上移动时,判断鼠标点是否在按钮区域,如果在,将按钮状态m_ButtonState(是一个枚举值)设置为相应的值,然后再处理鼠标左键在非客户区域按下时的消息。在消息处理函数中判断m_ButtonState值,根据不同的值执行相应的动作。这样便实现了标题栏按钮的单击事件。
实现过程
(1)新建一个基于对话框的应用程序。
(2)在对话框中添加编辑框、静态文本、按钮和图片控件。
(3)在对话框中添加如下成员变量。
BOOL m_IsMax; //是否处于最大化状态 int m_BorderWidth; //边框宽度 int m_BorderHeight; //边框高度 int m_CaptionHeight; //标题栏的高度 CString m_Caption; //窗口标题 COLORREF m_CapitonColor; //标题字体颜色 CFont m_CaptionFont; //标题字体 int m_ButtonWidth; //按钮位图宽度 int m_ButtonHeight; //按钮位图高度 BOOL m_FirstShow; //窗口首次被显示 CRect m_OrigonRect; //原始窗口区域 CRect m_IniRect,m_MinRect,m_MaxRect,m_CloseRect; //标题栏按钮的显示区域 CButtonState m_ButtonState; //按钮状态 BOOL m_IsDrawForm; //是否需要绘制窗体
(4)在对话框中添加DrawForm方法绘制窗体,代码如下:
void CDrawFormDlg::DrawForm() { CDC*pWindowDC=GetWindowDC(); //获取窗口设备上下文 CBitmap LeftLine; BITMAPINFO bitinfo; CDC memDC; memDC.CreateCompatibleDC(pWindowDC); CRect Clientrect; GetClientRect(Clientrect); //获得窗体客户区域 int leftwidth=0; //左标题的宽度 int rightwidth=0; //右标题的宽度 int leftlinewidth=0; //左边线宽度 LeftLine.LoadBitmap(IDB_BITMAP3); //加载右标题 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获得位图大小 rightwidth=bitinfo.bmiHeader.biWidth; //获得宽度 LeftLine.DeleteObject(); int x,y; //绘制左边线 //获取位图大小 LeftLine.LoadBitmap(IDB_BITMAP4); //加载位图资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获得位图大小 leftlinewidth=x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 memDC.SelectObject(&LeftLine); //选入图片 pWindowDC->StretchBlt(1-m_BorderWidth,m_CaptionHeight+1,x+1,Clientrect.Height() +2*m_BorderHeight+5,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); /*****************************绘制左标题**************************************/ LeftLine.LoadBitmap(IDB_BITMAP2); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 leftwidth=x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(-m_BorderWidth,0,x,m_CaptionHeight+4,&memDC,0,0,x,y,SRCCOPY);//绘制图片 LeftLine.DeleteObject(); /*****************************绘制左标题****************************************/ /*****************************绘制中间标题**************************************/ LeftLine.LoadBitmap(IDB_BITMAP1); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入图片 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(leftwidth-1,0,Clientrect.Width()-leftwidth-rightwidth ,m_ CaptionHeight+4,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); /*****************************绘制中间标题**************************************/ /*****************************绘制右标题****************************************/ LeftLine.LoadBitmap(IDB_BITMAP3); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(Clientrect.Width()-x-1,0,x+m_BorderWidth+9 ,m_CaptionHeight+4,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); /*****************************绘制右标题***************************************/ /*****************************绘制右边框***************************************/ LeftLine.LoadBitmap(IDB_BITMAP4); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 x=bitinfo.bmiHeader.biWidth; //获得位图宽度 y=bitinfo.bmiHeader.biHeight; //获得位图高度 pWindowDC->StretchBlt(Clientrect.Width()+m_BorderWidth+2,m_CaptionHeight+1, x+m_BorderWidth,Clientrect.Height()+2*m_BorderHeight+5,&memDC,0,0,x,y,SRCCOPY);//绘制图片 LeftLine.DeleteObject(); /*****************************绘制右边框***************************************/ /*****************************绘制底边框***************************************/ LeftLine.LoadBitmap(IDB_BITMAP5); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入图片 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(leftlinewidth-m_BorderWidth,Clientrect.Height() +m_CaptionHeight+2,Clientrect.Width()+m_BorderWidth,y+2,&memDC,0,0,x,y,SRCCOPY);//绘制图片 LeftLine.DeleteObject(); /*****************************绘制底边框***************************************/ /*****************************绘制初始化按钮***********************************/ LeftLine.LoadBitmap(IDB_BITMAP6); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(m_IniRect.left,m_IniRect.top,m_IniRect.right ,m_IniRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); /*****************************绘制初始化按钮************************************/ /*****************************绘制最小化按钮************************************/ LeftLine.LoadBitmap(IDB_BITMAP6); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(m_MinRect.left,m_MinRect.top,m_MinRect.right, m_MinRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); /*****************************绘制最小化按钮************************************/ /*****************************绘制最大化按钮************************************/ LeftLine.LoadBitmap(IDB_BITMAP6); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(m_MaxRect.left,m_MaxRect.top,m_MaxRect.right ,m_MaxRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); /*****************************绘制最大化按钮************************************/ /*****************************绘制关闭按钮**************************************/ LeftLine.LoadBitmap(IDB_BITMAP6); //加载图片资源 LeftLine.GetObject(sizeof(bitinfo),&bitinfo); //获取位图大小 memDC.SelectObject(&LeftLine); //选入位图 x=bitinfo.bmiHeader.biWidth; //获得宽度 y=bitinfo.bmiHeader.biHeight; //获得高度 pWindowDC->StretchBlt(m_CloseRect.left,m_CloseRect.top,m_CloseRect.right ,m_CloseRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制图片 LeftLine.DeleteObject(); m_IsDrawForm = TRUE; /*****************************绘制关闭按钮*************************************/ ReleaseDC(&memDC); DrawFormCaption(); //绘制标题 }
(5)处理窗体的WM_NCMOUSEMOVE消息,确定标题栏按钮的状态,代码如下:
void CDrawFormDlg::OnNcMouseMove(UINT nHitTest, CPoint point) { CDialog::OnNcMouseMove(nHitTest, point); CRect tempIni,tempMin,tempMax,tempClose,ClientRect; CDC*pWindowDC=GetWindowDC(); //获得窗口设备上下文 CDC memDC; memDC.CreateCompatibleDC(pWindowDC); //创建兼容的设备上下文 BITMAPINFO bInfo; CBitmap LeftLine; int x,y; GetWindowRect(ClientRect); //获得窗口区域 tempIni.CopyRect(CRect(m_IniRect.left+ ClientRect.left,ClientRect.top+m_IniRect.top,m_IniRect.right+m_IniRect.left+ ClientRect.left,m_IniRect.bottom+m_IniRect.top+ClientRect.top)); //复制初始化按钮区域 tempMin.CopyRect(CRect(m_MinRect.left+ ClientRect.left,ClientRect.top+m_MinRect.top,m_MinRect.right+m_MinRect.left+ ClientRect.left,m_MinRect.bottom+m_MinRect.top+ClientRect.top)); //复制最小化按钮区域 tempMax.CopyRect(CRect(m_MaxRect.left+ ClientRect.left,ClientRect.top+m_MaxRect.top,m_MaxRect.right+m_MaxRect.left+ ClientRect.left,m_MaxRect.bottom+m_MaxRect.top+ClientRect.top)); //复制最大化按钮区域 tempClose.CopyRect(CRect(m_CloseRect.left+ ClientRect.left,ClientRect.top+m_CloseRect.top,m_CloseRect.right+ m_CloseRect.left+ClientRect.left,m_CloseRect.bottom+m_CloseRect.top +ClientRect.top)); //复制关闭按钮区域 //鼠标在初始化按钮上移动时,更改按钮显示的位图 if (tempIni.PtInRect(point)) { LeftLine.LoadBitmap(IDB_BITMAP7); //加载图片资源 LeftLine.GetObject(sizeof(bInfo),&bInfo); //获得位图信息 x=bInfo.bmiHeader.biWidth; //获得位图宽度 y=bInfo.bmiHeader.biHeight; //获得位图高度 memDC.SelectObject(&LeftLine); //选入位图 pWindowDC->StretchBlt(m_IniRect.left,m_IniRect.top,m_IniRect.right, m_IniRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制位图 m_IsDrawForm = FALSE; m_ButtonState = bsIni; LeftLine.DeleteObject(); } //鼠标在最小化按钮上移动时,更改按钮显示的位图 else if(tempMin.PtInRect(point)) { LeftLine.LoadBitmap(IDB_BITMAP7); //加载图片资源 LeftLine.GetObject(sizeof(bInfo),&bInfo); //获得位图信息 x=bInfo.bmiHeader.biWidth; //获得位图宽度 y=bInfo.bmiHeader.biHeight; //获得位图高度 memDC.SelectObject(&LeftLine); //选入位图 pWindowDC->StretchBlt(m_MinRect.left,m_MinRect.top,m_MinRect.right ,m_MinRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制位图 m_IsDrawForm = FALSE; m_ButtonState = bsMin; LeftLine.DeleteObject(); } //鼠标在最大化按钮上移动时,更改按钮显示的位图 else if (tempMax.PtInRect(point)) { LeftLine.LoadBitmap(IDB_BITMAP7); //加载图片资源 LeftLine.GetObject(sizeof(bInfo),&bInfo); //获得位图信息 x=bInfo.bmiHeader.biWidth; //获得位图宽度 y=bInfo.bmiHeader.biHeight; //获得位图高度 memDC.SelectObject(&LeftLine); //选入位图 pWindowDC->StretchBlt(m_MaxRect.left,m_MaxRect.top, m_MaxRect.right,m_MaxRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制位图 m_IsDrawForm = FALSE; if (m_IsMax) { m_ButtonState=bsMax; //最大化 } else { m_ButtonState=bsRes; //还原 } LeftLine.DeleteObject(); } //鼠标在关闭按钮上移动时,更改按钮显示的位图 else if (tempClose.PtInRect(point)) { LeftLine.LoadBitmap(IDB_BITMAP7); //加载图片资源 LeftLine.GetObject(sizeof(bInfo),&bInfo); //获得位图信息 x=bInfo.bmiHeader.biWidth; //获得位图宽度 y=bInfo.bmiHeader.biHeight; //获得位图高度 memDC.SelectObject(&LeftLine); //选入位图 pWindowDC->StretchBlt(m_CloseRect.left,m_CloseRect.top, m_CloseRect.right,m_CloseRect.bottom,&memDC,0,0,x,y,SRCCOPY); //绘制位图 m_IsDrawForm = FALSE; m_ButtonState = bsClose; LeftLine.DeleteObject(); } else { m_ButtonState = bsNone; if (m_IsDrawForm==FALSE) DrawForm(); } ReleaseDC(&memDC); }
(6)处理窗体的WM_NCLBUTTONDOWN消息,当用户在非客户区域单击时,根据按钮的状态执行相应操作,代码如下:
void CDrawFormDlg::OnNcLButtonDown(UINT nHitTest, CPoint point) { CDialog::OnNcLButtonDown(nHitTest, point); switch (m_ButtonState) { case bsClose: //关闭窗口 { DestroyWindow(); } break; case bsIni: //还原窗口到初始大小和位置 { m_IsMax = TRUE; MoveWindow(m_OrigonRect.left,m_OrigonRect.top,m_OrigonRect.Width() ,m_OrigonRect.Height()); //移动窗口位置 } break; case bsMin: //最小化窗口 { CWnd* pDesk = GetDesktopWindow(); CRect rect; pDesk->GetClientRect(rect); //获得客户区域 SetWindowPos(0 ,(rect.Width()-m_OrigonRect.Width())/2, 2,m_OrigonRect.Width(),0,SWP_SHOWWINDOW); //移动窗口位置 } break; case bsMax: //最大化窗口 { m_ButtonState = bsMax; ShowWindow(SW_SHOWMAXIMIZED); //最大化窗口 m_IsMax = FALSE; } break; case bsRes: //还原窗口 { ShowWindow(SW_RESTORE); //还原窗口 m_IsMax = TRUE; } break; } }
举一反三
根据本实例,读者可以:
设计个性的窗体界面。
实例020 以时钟显示界面
本实例可以美化界面、简化操作
实例位置:光盘\mingrisoft\01\020
实例说明
在当今紧张的生活和工作中,时间已经成为人们极为重要的财富,但是有些应用程序并不能显示时间,这就给用户带来了极大的不便。运行本实例,将根据系统时间在窗体中显示一个时钟,如图1.25所示。
技术要点
本实例通过GetCurrentTime函数获取系统时间,装入一个时钟的位图,在时钟位图中间部分使用CreateEllipticRgn函数创建一个椭圆形的区域,在该区域中绘制表针。
图1.25 以时钟显示界面窗体
实现过程
(1)新建一个基于单文档的应用程序。
(2)在ResourceView视图中右击,选择Import选项,在Import Resource窗口中添加一个表盘位图。
(3)在视图类的OnDraw方法中绘制时钟背景,并设置定时器代码如下:
void CSZxscxjmView::OnDraw(CDC* pDC) { CSZxscxjmDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); time=CTime::GetCurrentTime(); //获得系统时间 if(hour>12) //将24小时制转换为12小时制 { hour=hour-12; } sec=time.GetSecond(); //获得秒数 min=time.GetMinute(); //获得分钟 hour=time.GetHour(); //获得小时 CDC dc; CBitmap bmp; bmp.LoadBitmap(IDB_BITMAP1); //IDB_BITMAP1是待显示位图的资源ID BITMAP bm; bmp.GetBitmap(&bm); //获得位图信息 int nWidth=bm.bmWidth; //获得位图宽度 int nHeight=bm.bmHeight; //获得位图高度 dc.CreateCompatibleDC(pDC); //创建兼容的设备上下文 dc.SelectObject(&bmp); //选入位图对象 pDC->BitBlt(18,0,nWidth,nHeight,&dc,0,0,SRCCOPY); //绘制位图背景 SetTimer(1,1,NULL); //设置定时器 }
(4)在定时器处理函数中绘制表针,代码如下:
void CSZxscxjmView::OnTimer(UINT nIDEvent) { KillTimer(1); CDC*pDC=this->GetDC(); //获得设备上下文 CRect m_rect; this->GetClientRect(m_rect); //获得客户区域 CRgn rgn; HRGN m_hrgn; m_hrgn=::CreateEllipticRgn(64,40,186,165); //创建椭圆区域 rgn.Attach(m_hrgn); CBrush m_brush(1,RGB(255,255,255)); //创建画刷 pDC->SelectClipRgn(&rgn,0); pDC->FillRgn(&rgn,&m_brush); //用画刷填充区域 int x=120; int y=100; CPen pen; //声明画笔 pen.CreatePen(PS_SOLID,1,RGB(255,0,0)); //创建实线画笔 pDC->SelectObject(&pen); //将画笔选入设备上下文 pDC->MoveTo (x,y); pDC->LineTo(x+(long)55*cos(pi/2-2*pi*sec/60.0),y-(long)55*sin(pi/2-2*pi*sec/60.0)); //绘制秒针 CPen pen1; //声明画笔 pen1.CreatePen(PS_SOLID,2,RGB(0,0,0)); //创建实线画笔 pDC->SelectObject(&pen1); //将画笔选入设备上下文 pDC->MoveTo (x,y); pDC->LineTo(x+(long)45*cos(pi/2-2*pi*min/60.0),y-(long)45*sin(pi/2-2*pi*min/60.0)); //绘制分针 pDC->MoveTo (x,y); pDC->LineTo(x+(long)35*cos(pi/2-5*2*pi*hour/60.0),y-(long)35*sin(pi/2-5*2*pi*hour/60.0)); //绘制时针 sec = sec+1; if(sec==60) { sec=0; min=min+1; if(min==60) { min=0; hour=hour+1; } } SetTimer(1,1000,NULL); CView::OnTimer(nIDEvent); pen.DeleteObject(); pen1.DeleteObject(); }
举一反三
根据本实例,读者可以:
实现在单文档的程序中显示位图。
实例021 窗体融合技术
本实例是一个提高效率的程序
实例位置:光盘\mingrisoft\01\021
实例说明
在Visual C++ 6.0的集成开发环境中,可以看到许多拖动的窗口。用户可以将其拖动到任何位置,也可以将它们停靠在窗口的一边。本实例实现了该功能,效果如图1.26所示。
技术要点
在文档/视图结构的应用程序中,默认情况下,用户可以将工具栏拖动到任意位置。如果要将对话框拖动到任意位置,就需要从控制条CControlBar派生一个子类,本实例为CDockBarCtrl。在CDockBarCtrl中实现某个对话框的显示,并实现控制条的拖动功能。
图1.26 窗体融合技术
实现对话框的显示非常容易,只要在CDockBarCtrl类中定义一个对话框CDialog指针,然后在CDockBarCtrl类的Create方法中创建对话框,并设置对话框的位置就可以了。
要实现控制条的拖动,最核心的问题是设置控制条的位置和大小,即根据当前拖动的状况(在上、下、左、右停靠,或者处于浮动状态)适当设置控制条的位置和大小。在CControlBar类中提供了两个虚拟方法CalcDynamicLayout和CalcFixedLayout来计算控制条的大小。在CalcDynamicLayout方法中只是直接调用了CalcFixedLayout方法,而CalcFixedLayout只是简单地设置控制条的大小,并没有根据实际情况进行计算。因为CControlBar是一个抽象类,CalcFixedLayout方法只是进行了默认的调整。有关CDockBarCtrl类对CalcDynamicLayout和CalcFixedLayout方法的改写可以参考实现过程。
在控制条CDockBarCtrl中还应该包含一个标题栏,用于显示还原按钮、关闭按钮和两个边条。可以通过处理控制条的WM_NCCALCSIZE消息来设置标题栏的区域。需要注意的是标题栏的区域不是一成不变的,当控制条在上、下、左、右停靠时,标题栏的区域都是不同的。因此需要在WM_NCCALCSIZE消息处理函数中针对不同的情况进行设置。
void CDockBarCtrl::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp) { //根据每一种排列方式计算出非客户区域 switch (m_DockBarID) { case AFX_IDW_DOCKBAR_TOP: //控制条停靠在上方 lpncsp->rgrc[0].left += m_ncTBandHeight; lpncsp->rgrc[0].bottom-=m_ncRBandWidth; lpncsp->rgrc[0].top += m_ncLBandWidth; lpncsp->rgrc[0].right-= m_ncBBandHeight; m_ncTopHeight = m_ncRBandWidth; break; case AFX_IDW_DOCKBAR_LEFT: //控制条停靠在左方 lpncsp->rgrc[0].left +=m_ncLBandWidth; lpncsp->rgrc[0].bottom -=m_ncBBandHeight; lpncsp->rgrc[0].top +=m_ncTBandHeight; lpncsp->rgrc[0].right-=m_ncRBandWidth; m_ncLeftWidth = m_ncLBandWidth; break; case AFX_IDW_DOCKBAR_RIGHT: //控制条停靠在右方 lpncsp->rgrc[0].left +=m_ncRBandWidth; lpncsp->rgrc[0].bottom -=m_ncBBandHeight; lpncsp->rgrc[0].top +=m_ncTBandHeight; lpncsp->rgrc[0].right-=m_ncLBandWidth; m_ncLeftWidth = m_ncLBandWidth; break; case AFX_IDW_DOCKBAR_BOTTOM: //控制条停靠在下方 lpncsp->rgrc[0].left +=m_ncTBandHeight; lpncsp->rgrc[0].bottom -= m_ncLBandWidth; lpncsp->rgrc[0].top += m_ncRBandWidth; lpncsp->rgrc[0].right-=m_ncBBandHeight; m_ncTopHeight = m_ncRBandWidth; break; } }
注意:CControlBar是一个抽象类,要从该类派生一个子类,必须实现抽象方法OnUpdateCmdUI。
实现过程
(1)新建一个文档/视图结构的应用程序。
(2)创建一个对话框类CTools,设置对话框属性如图1.27所示。
(3)从CControlBar派生一个子类CDockBarCtrl,实现抽象方法OnUpdateCmdUI,代码如下:
void CDockBarCtrl::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler) { CWnd::UpdateDialogControls(pTarget, bDisableIfNoHndler); }
图1.27 对话框属性设置
(4)在CDockBarCtrl类中定义如下成员变量:
CDialog*m_pDlg; //关联的对话框 CBrush m_bkBrush; //背景画刷 CString m_Caption; //窗口标题 CRect m_FloatRect; //浮动时的窗口区域 CRect m_VerRect; //拖入时,在左、右显示时的区域 CRect m_HorRect; //拖入时,在上、下显示时的区域 //确定控制条的非客户区域部分 int m_ncTBandHeight; //非客户区域上边条高度 int m_ncRBandWidth; //右边条的宽度 int m_ncLBandWidth; //确定左边条的宽度 int m_ncBBandHeight; //确定底边条的宽度 //定义标题栏按钮的显示区域 CRect m_closeRC; //关闭按钮的显示区域 CRect m_maxRc; //最大化按钮的显示区域 int m_ncLeftWidth; //记录垂直显示时的非客户区域左边条宽度 int m_ncTopHeight; //记录水平显示时的非客户区域上边条的宽度 COLORREF m_TopBandColor,m_BottomBandColor; //标题线条的颜色 BOOL m_IsTraking; //是否处于调整控制条大小状态 BOOL m_IsInRect; //判断控制条是否处于停靠状态 CPoint m_OldPos; //鼠标按下时原始点位置 CRect m_Bandrc; //拖动的边条区域 CRect m_temprc; //临时的区域 int m_TopInterval,m_LeftInterval; //控制条相对父窗口的位置 int m_HitTest; CSize m_Min; //当控制条处于浮动状态时,限制用户改变控制小的最小区域
(5)在CDockBarCtrl类中改写Create方法,创建控制条窗口和对话框窗口,代码如下:
BOOL CDockBarCtrl::Create(CWnd*pParentWnd,CDialog*pDialog,UINT nID,DWORD dwStyle, CCreateContext*pContext) { ASSERT_VALID(pParentWnd); ASSERT(!((dwStyle&CBRS_SIZE_DYNAMIC)&&(dwStyle&CBRS_SIZE_FIXED))); m_dwStyle=dwStyle&CBRS_ALL; //注册窗口类 CString classname; classname=AfxRegisterWndClass(CS_DBLCLKS,LoadCursor(NULL,IDC_ARROW),m_bkBrush,0); //创建窗口类 CWnd::Create(classname,m_Caption,dwStyle,CRect(0,0,0,0),pParentWnd,0); m_pDlg=pDialog; m_pDlg->Create(nID,this); //创建对话框 m_pDlg->GetWindowRect(m_FloatRect); //获得对话框窗口区域 m_HorRect.CopyRect(m_FloatRect); //复制对话框区域 m_VerRect.CopyRect(m_FloatRect); //复制对话框区域 return true; }
(6)在CDockBarCtrl类中改写CalcFixedLayout方法,计算控制条停靠时的大小,代码如下:
CSize CDockBarCtrl::CalcFixedLayout(BOOL bStretch, BOOL bHorz) { int dockwidth,dockheight; CRect rc,clientrc; m_pDockSite->GetClientRect(clientrc); //获得客户区域 //如果当前处于浮动状态 if (IsFloating()) return CSize(m_FloatRect.Width(),m_FloatRect.Height()); //返回对话框的宽度和高度 else //处于固定状态 { if(bHorz) //水平方向显示 { m_pDockSite->GetControlBar(AFX_IDW_DOCKBAR_TOP)->GetWindowRect(rc); dockwidth = bStretch? 1200:rc.Width()+4; return CSize(dockwidth,m_HorRect.Height()); } else //垂直方向显示 { m_pDockSite->GetControlBar(AFX_IDW_DOCKBAR_LEFT)->GetWindowRect(rc); dockheight= bStretch?1200:rc.Height()+4; return CSize(m_VerRect.Width(),dockheight); } } }
(7)在CDockBarCtrl类中改写CalcDynamicLayout方法,根据控制条的不同状态设置控制条大小,代码如下:
CSize CDockBarCtrl::CalcDynamicLayout(int nLength, DWORD nMode) { if (IsFloating()) { //修改CMiniDockFrameWnd的风格 //允许同时调整窗口的宽度和高度 CFrameWnd*pDockFrame= GetDockingFrame(); pDockFrame->ModifyStyle(MFS_4THICKFRAME,0); } if (nMode&(LM_HORZDOCK|LM_VERTDOCK)) return CControlBar::CalcDynamicLayout(nLength,nMode); if (nMode&LM_COMMIT) return CSize(nLength,m_FloatRect.Height()); if (nMode & LM_MRUWIDTH) return CSize(m_FloatRect.Width(),m_FloatRect.Height()); CRect rect; GetParent()->GetParent()->GetWindowRect(rect); //获取CMiniDockFrameWnd的窗口区域 int CaptionHeight=GetSystemMetrics(SM_CYCAPTION); //获取CMiniDockFrameWnd的标题栏高度 if (IsFloating()) { CPoint pt; GetCursorPos(&pt); //获取鼠标当前位置 m_FloatRect.left = 0; m_FloatRect.top = 0; //判断用户在窗体的哪个角开始调整大小,实际上m_pDockContext->m_nHitTest的值 //是在CMiniDockFrameWnd的OnNcLButtonDown方法中调用m_pDockContext的StartResize方法设置的 switch (m_pDockContext->m_nHitTest) { case HTTOPLEFT: //鼠标在左上角 { m_FloatRect.left = 0; m_FloatRect.top = 0; m_FloatRect.right= (m_Min.cx>rect.right-pt.x)?m_Min.cx:(rect.right-pt.x); //调整宽度 int temp = rect.bottom - CaptionHeight - pt.y -1; m_FloatRect.bottom= (m_Min.cy>temp)?m_Min.cy:temp; //调整高度 //调整CMiniDockFrameWnd的左上角坐标为当前移动的鼠标坐标 m_pDockContext->m_rectFrameDragHorz.top = pt.y; m_pDockContext->m_rectFrameDragHorz.left =pt.x; return CSize(m_FloatRect.Width(),m_FloatRect.Height()); } case HTTOPRIGHT: //鼠标在右上角 { m_FloatRect.left = 0; m_FloatRect.top = 0; m_FloatRect.right=(m_Min.cx>pt.x-rect.left)?m_Min.cx:pt.x-rect.left; //调整宽度 int temp = rect.bottom - CaptionHeight - pt.y -1; m_FloatRect.bottom= (m_Min.cy>temp)?m_Min.cy:temp; //调整高度 m_pDockContext->m_rectFrameDragHorz.top = pt.y; return CSize(m_FloatRect.Width(),m_FloatRect.Height()); } case HTBOTTOMLEFT: //鼠标在左下角 { m_FloatRect.right=max(m_Min.cx,rect.right-pt.x); //调整宽度 m_FloatRect.bottom=max(m_Min.cy,pt.y-rect.top-CaptionHeight); //调整高度 m_pDockContext->m_rectFrameDragHorz.left =max(m_Min.cx,pt.x); return CSize(m_FloatRect.Width(),m_FloatRect.Height()); } case HTBOTTOMRIGHT: //鼠标在右下角 { m_FloatRect.right=max(m_Min.cx,pt.x-rect.left); //调整宽度 m_FloatRect.bottom=max(m_Min.cy,pt.y-rect.top-CaptionHeight);/ /调整高度 m_pDockContext->m_rectFrameDragHorz.right =pt.x; return CSize(m_FloatRect.Width(),m_FloatRect.Height()); } } } if(nMode&LM_LENGTHY) //调整控制条的高度 { m_FloatRect.top=0; m_FloatRect.bottom= max(m_Min.cy, nLength); return CSize(m_FloatRect.Width(),max(m_Min.cy, nLength)); } else if(nMode&LM_HORZ) //调整控制条的宽度 { m_FloatRect.top = 0; m_FloatRect.left = 0; m_FloatRect.right=max(m_Min.cx,nLength); //调整宽度 m_FloatRect.bottom=max(m_Min.cy,rect.Height()-CaptionHeight); //调整高度 return CSize(max(nLength,m_Min.cx),m_FloatRect.Height()); } }
举一反三
根据本实例,读者可以:
设计动画效果的控制条。
实例022 限制对话框最限制对话框最大时的窗口大小
本实例是一个提高效率的程序
实例位置:光盘\mingrisoft\01\022
实例说明
在设计程序界面时,有时需要限制对话框的大小。例如,将对话框限制在某一范围内。本实例实现了该功能,效果如图1.28所示。
图1.28 限制对话框最大时的窗口大小
技术要点
当对话框的大小和位置发生改变时,会接收到WM_GETMINMAXINFO消息。用户只要在该消息处理函数中设置对话框的大小就可以了。WM_GETMINMAXINFO消息处理函数语法如下:
afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );
参数说明:
● lpMMI:是MINMAXINFO结构指针,该结构记录着对话框最大化、最小化时的大小,用于限制对话框大小。其中,ptMaxSize成员用于设置对话框最大化时的高度和宽度,ptMaxPosition成员标识对话框最大化时的位置。
实现过程
(1)新建一个基于对话框的应用程序。
(2)在对话框中添加图片控件。
(3)处理对话框的WM_GETMINMAXINFO消息,限制对话框的大小,代码如下:
void CLimitSizeDlg::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) { lpMMI->ptMaxSize.x=800; //设置对话框最大化时的宽度 lpMMI->ptMaxSize.y=600; //设置对话框最大化时的高度 lpMMI->ptMaxPosition.x=50; //设置对话框最大化时左边的位置 lpMMI->ptMaxPosition.y=50; //设置对话框最大化时上方的位置 CDialog::OnGetMinMaxInfo(lpMMI); }
举一反三
根据本实例,读者可以:
实现限制对话框最小化时的大小。
实例023 分割视图窗口
本实例是一个提高效率的程序
实例位置:光盘\mingrisoft\01\023
实例说明
在文档/视图结构中,视图不仅用来显示文档中的数据,也可以对视图进行分割,从而使视图显示不同的信息。本实例实现了分割视图窗口的功能,效果如图1.29所示。
图1.29 分割视图窗口
技术要点
通过CSplitterWnd类可以实现视图分割,当改变分割窗口的大小时,窗口的客户区将自动重新绘制。首先调用CreateStatic方法创建静态分割的窗体,然后调用CreateView方法创建子视图。CreateStatic方法语法如下:
BOOL CreateStatic( CWnd* pParentWnd, int nRows, int nCols, DWORD dwStyle = WS_CHILD | WS_VISIBLE, UINT nID =AFX_IDW_PANE_FIRST );
参数说明:
● pParentWnd:分割窗体的父窗体对象,一般为框架窗体对象。
● nRows:分割窗体的行数,该值不能超过16。
● nCols:分割窗体的列数,该值不能超过16。
● dwStyle:设置分割后窗体的样式。
● nID:设置被创建对象所使用的资源ID值。
CreateView方法语法如下:
virtual BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );
参数说明:
● row:子视图所在行。
● col:子视图所在列。
● pViewClass:新视图的CRuntimeClass对象。
● sizeInit:指定新视图的初始大小。
● pContext:指向CCreateContext结构的指针。
实现过程
(1)新建一个基于单文档的应用程序。
(2)以CTreeView类为基类派生一个CMyTreeView类,以CListView类为基类派生一个CMyListView类。
(3)主要程序代码如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { split.CreateStatic(this,1,2); //创建静态分割窗口 split.CreateView(0,1,RUNTIME_CLASS(CMyListView),CSize(150,100),pContext); //创建列表子视图 split.CreateView(0,0,RUNTIME_CLASS(CMyTreeView),CSize(200,100),pContext); //创建树状子视图 return CFrameWnd::OnCreateClient(lpcs, pContext); }
举一反三
根据本实例,读者可以:
实现视图的自由分割。