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);
        }

举一反三

根据本实例,读者可以:

实现视图的自由分割。