利用交互式技术绘制一个长短、位置可变的线段

       本文利用交互式技术绘制一个长短、位置可变的线段。具体要求:绘制一条线段:线宽为3;颜色红色;两个端点绘制边长为5的方框(方框线宽为1,无填充);在两个端点下方实时显示端点坐标;端点方框被鼠标选中拖动时,线段及坐标均随鼠标移动而变化。
具体步骤:
一、打开VS2022,创建一个基于对话框的MFC应用,项目名称:deformableLine。
二、在头文件deformableLineDlg.h中,找到类CdeformableLineDlg的声明,在public部分,声明几个变量和一个函数:
       CPoint P[2]; //线段两端坐标
       BOOL bLBDown; //左键按下状态,TRUE为按下,FALSE为弹起
       int nCount; //计数器(记录线端点序号,即是P[0]还是P[1])
       void DoubleBufferDraw(); //利用双缓冲技术绘制线段函数
三、在deformableLineDlg.cpp中,找到CdeformableLineDlg::OnInitDialog()函数,在//TODO:下面,为上边的变量赋初值,代码如下:
       CPoint P[0]=CPoint(150,150);
       CPoint P[1]=CPoint(350,150);
       bLBDown=FALSE;
       nCount=0;
四、在deformableLineDlg.cpp后部,实现DoubleBufferDraw()函数。注意:我们要把这个程序的调用放到OnPaint()中,也就是由OnPaint()调用。具体代码如下:
void CdeformableLineDlg::DoubleBufferDraw()
     {
    CDC* pDC = this->GetDC();//获取当前窗口设备上下文指针并赋值给pDC
    CRect rect; //声明矩形结构
    GetClientRect(&rect); //获取客户区数据并存入rect
    CDC memDC; //声明一个设备上下文兼容对象
    memDC.CreateCompatibleDC(pDC); //该对象生成为与pDC兼容的对象,该对象仅仅声明后实际是空的,如需在上面绘图,还要有下面的操作
    CBitmap NewBitmap, * pOldBitmap; //声明一个位图对象和一个位图指针
    NewBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); //位图对象与pDC兼容,这个位图可以用于作图
    pOldBitmap = memDC.SelectObject(&NewBitmap);//位图对象选入兼容对象中,并将原位图信息存入位图指针,此时在memDC上作图实际存入NewBitmap
    memDC.FillSolidRect(rect, pDC->GetBkColor()); //用pDC的背景色填充memDC
    CPen myPen1, * pOldPen1; //画笔1 用于画线段两端的方框
    myPen1.CreatePen(0, 1, 0x0000ff); //设置画笔的线宽即颜色
    pOldPen1 = memDC.SelectObject(&myPen1); //将画笔1选入memDC
    CBrush myBrush(0xFFFFFF); //设置一个白色画刷
    memDC.SelectObject(myBrush); //将白色画刷选入memDC
    for (int i = 0; i < 2; i++) {
        CString str; 
        str.Format((L"x = %d, y = %d "), P[i].x, P[i].y); //生成坐标位置字符串
        memDC.SetTextColor(RGB(255, 0, 0)); //设置memDC的文字颜色为红色
        memDC.TextOut(P[i].x, P[i].y+15, str); //输出字符串到memDC
        memDC.Rectangle(P[i].x - 5, P[i].y - 5, P[i].x + 5,P[i].y + 5); //给线段端点绘制小方块
        }
    CPen myPen2, * pOldPen2; //声明画笔2 用于画线段
    myPen2.CreatePen(0, 3, 0x0000FF); //画笔2线宽为3、颜色红色
    pOldPen2 = memDC.SelectObject(&myPen2); //将画笔2选入memDC
    memDC.MoveTo(P[0]); //绘制线段
    memDC.LineTo(P[1]);
    pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &memDC,0, 0, SRCCOPY); 
                     //将兼容对象中的图像copy到设备上下文对象中(屏幕)
    memDC.SelectObject(pOldBitmap); //恢复兼容对象
                memDC.SelectObject(pOldPen1); //恢复兼容对象
                memDC.SelectObject(pOldPen2); //恢复兼容对象
    NewBitmap.DeleteObject(); //删除位图对象释放内存
    //相关背景知识:Windows 显示设备的属性,共有下面几种:位图、画刷、字体、画笔、区域。如果要设置它们到当前(或兼容)设备里,就需要使用SelectObject 函数。当你创建一个位图时,这时 Windows 就会在内存里分配一块内存空间,用来保存位图的数据。当你创建字体时,也会分配一块内存空间保存字体。如果程序只是分配,而不去删除,就会造成内存使用越来越多,最后导到 Windows 崩溃。因此需要使用DeleteObject 函数去删除它们,把占用的内存释放回去给系统。
    memDC.DeleteDC(); //memDC是自己声明生成
    // 如果一个设备上下文环境的句柄是通过调用GetDC函数得到的,那么应用程序不能删除该设备上下文,应该调用ReleaseDC函数来释放该设备上下文环境。如果是自己生成的,则用DeleteDC释放所占用的资源。
    ReleaseDC(pDC); //pDC是通过GetDC()得到的
}
五、编程至此,运行程序,我们已可看到屏幕上显示出一条两端带有方框的线段,以及在下方显示的端点坐标。但鼠标与线段端点还没有建立联系,线段还不能被鼠标选中,距离完成编程还差一步。
六、通过 项目->类向导 -> 消息 -> 选择WM_LBUTTONDOWN(左键按下消息) -> 添加处理程序,添加消息处理程序。当鼠标左键按下时,我们表示鼠标左键状态的变量bLBDown赢被赋值为TRUE。具体代码为:
void CdeformableLineDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    bLBDown = TRUE; //将表示左键按起状态的变量复制为TRUE,表示左键按下,后边的程序会用到这个变量,以免鼠标经过没有按下也会选中。

    CDialogEx::OnLButtonDown(nFlags, point);
}
七、通过 项目->类向导 -> 消息 -> 选择WM_MOUSEMOVE (左键按下消息) -> 添加消息处理程序。这个程序处理的消息是鼠标移动,但同时应考虑到左键状态,否则鼠标移动线段就做出反应会带来混乱。这个程序是本次编程的核心,也就是当线段端点方框被鼠标选中(bLBDown=TRUE)并移动鼠标时,线段应随之变化。具体代码为:
void CdeformableLineDlg::OnMouseMove(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    CDC* pDC = GetDC(); //该函数检索一指定窗口的客户区域或整个屏幕的显示设备上下文环境的句柄,以后可以在GDI函数中使用该句柄来在设备上下文环境中绘图。此处,获得的指针指向电脑屏幕
    for (int i = 0; i < 2; i++) {
        if (point.xP[i].x - 5 && point.y             && point.y>P[i].y - 5) {  //判断鼠标是否在线段端点上下或左右5个像素范围内
            SetCursor(LoadCursor(NULL, IDC_HAND)); //设置光标样式,此处设置为手型
            //LoadCursor加载windows 自带的光标资源时第一个参数必须为空
            nCount = i; //标记被点击的三角形顶点序号
        } 
    }
    if (bLBDown)  P[nCount] = point;//将鼠标位置付给顶点,这里鼠标状态是即按下又在移动
    ReleaseDC(pDC);//释放pDC占用内存
    Invalidate(FALSE);
    //该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着它需要重绘,例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。
    CDialogEx::OnMouseMove(nFlags, point);
}
七、完成上述六个步骤后,当用鼠标选任一线段端点时,文章开始的目标基本实现了,但存在一个问题,选择的顶点鼠标左键抬起后仍松不开。为解决这个问题,重复前面步骤,再添加一个WM_LBUTTONUP(左键弹起消息)处理程序。具体代码如下:
void CdeformableLineDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    bLBDown = FALSE; //恢复左键弹起设置,否则选上端点后端点放不开

    CDialogEx::OnLButtonUp(nFlags, point);
}
八、编程目标达成,在本次编程过程中涉及的一些技术术语如下,可自行对号:
      绘图过程中使用的交互式技术包括回显、引力域和橡皮筋技术,具体:
      回显:将对图形的操作,用某种方式表达出来的技术。例如,在窗口客户区中使用鼠标移动顶点时,实时显示顶点坐标。
      引力域:绘图过程中,常常需要使用鼠标光标选择某一点。使用鼠标光标进行准确定位是困难的。这时可以采用引力域技术,即以某点为中心的大小适当的矩形,鼠标进入该矩形域即为选中,矩形大小要适当。
      橡皮筋:将鼠标绘图的过程连续、动态地表现出来,直到产生用户满意的结果。

你可能感兴趣的:(mfc)