MFC:窗口绘图

窗口客户区

Windows图形设备界面

Windows使用图形设备界面(Graphical Device Interface,GDI)定义输出。GDI支持在对图形输出编程时不依赖显示它的硬件,这意味着程序不进行任何修改,就可以在具有不同显示硬件的不同机器上运行。


设备上下文

在输出设备上进行绘图操作时,必须使用设备上下文。

设备上下文是一种Windows数据结构,它包含的信息允许Windows将输出请求转换成物理输出设备上的动作,输出请求采用与设备无关的GDI函数调用形式。

MFC类CDC封装了一个设备上下文,所以对该类型的对象调用函数,就可以执行所有的绘图操作。

设备上下文提供了一种称为映射模式的可选坐标系统,它将被自动转换成客户区坐标。


映射模式

设备上下文中的每种映射模式都由一个ID标志,其方式与标志Windows消息类似,每个ID都由前缀MM_,表明它定义了映射模式。

 


MFC的绘图机制

应用程序中的视图类

MFC Application Wizard生成的类:

void CSketcherView::OnDraw(CDC* /*pDC*/)
{
	CSketcherDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此处为本机数据添加绘制代码
}

CDC类

定义设备上下文对象的类。

MFC包括了一些派生于CDC的更专用的类。例如,CClientDC类优于CDC类的地方是它始终包含只代表窗口工作区的设备上下文。

CDC::MoveTo

将当前位置移动到 (xy 或 point) 指定的点。

CPoint MoveTo(
    int x,
    int y);

CPoint MoveTo(POINT point);
x指定新位置的逻辑 x 坐标。
y指定新位置的逻辑 y 坐标。
point指定新位置。 可以传递 POINT 此参数的结构或 CPoint 对象。

返回值

作为对象的上一 CPoint 位置的 x 坐标和 y 坐标。

CDC::LineTo

从当前位置绘制一条线,但不包括由 xy (或 point) 指定的点。

BOOL LineTo(
    int x,
    int y);

BOOL LineTo(POINT point);
x指定新位置的逻辑 x 坐标。
y指定新位置的逻辑 y 坐标。
point指定新位置。 可以传递 POINT 此参数的结构或 CPoint 对象。

返回值

如果绘制线条,则为非零;否则为 0。

CDC::Ellipse

绘制椭圆形。

BOOL Ellipse(
    int x1,
    int y1,
    int x2,
    int y2);

BOOL Ellipse(LPCRECT lpRect);
x1指定椭圆边界矩形左上角的逻辑 x 坐标。
y1指定椭圆边界矩形左上角的逻辑 y 坐标。
x2指定椭圆边界矩形右下角的逻辑 x 坐标。
y2指定椭圆边界矩形右下角的逻辑 y 坐标。
lpRect指定椭圆的边界矩形。 还可以传递 CRect 此参数的对象。

返回值

如果该函数成功,则为非 0;否则为 0。

CDC::Arc

绘制椭圆弧线。

BOOL Arc(
    int x1,
    int y1,
    int x2,
    int y2,
    int x3,
    int y3,
    int x4,
    int y4);

BOOL Arc(
    LPCRECT lpRect,
    POINT ptStart,
    POINT ptEnd);
x1指定边界矩形左上角的 x 坐标 (逻辑单元) 
y1指定边界矩形左上角的 y 坐标 (逻辑单元) 。
x2指定边界矩形右下角的 x 坐标 (逻辑单元) 。
y2指定边界矩形右下角的 y 坐标 (逻辑单元 )。
x3指定在(逻辑单元) 中定义弧线起点 的点的 x 坐标。 这一点不必完全躺在弧线上。
y3指定在(逻辑单元) 中定义弧的起点 的点的 y 坐标。 这一点不必完全躺在弧线上。
x4指定点的 x 坐标,该点在(逻辑单元) 中定义弧的终结点 。 这一点不必完全躺在弧线上。
y4指定在(逻辑单元) 中定义弧的终结点 的点的 y 坐标。 这一点不必完全躺在弧线上。
lpRect指定(逻辑单元) 中的边界矩形 。 可以为此参数传递一个 LPRECT 或一个 CRect 对象。
ptStart指定点的 x 坐标和 y 坐标,该点在(逻辑单元) 中定义弧的起点 。 这一点不必完全躺在弧线上。可以传递 POINT 此参数的结构或 CPoint 对象。
ptEnd指定点的 x 坐标和 y 坐标,该点在 (逻辑单元) 中定义弧线的终点。 这一点不必完全躺在弧线上。可以传递 POINT 此参数的结构或 CPoint 对象。

返回值

如果该函数成功,则为非 0;否则为 0。

CDC::SelectObject

选择设备上下文中的对象。

CPen* SelectObject(CPen* pPen);
CBrush* SelectObject(CBrush* pBrush);
virtual CFont* SelectObject(CFont* pFont);
CBitmap* SelectObject(CBitmap* pBitmap);
int SelectObject(CRgn* pRgn);
CGdiObject* SelectObject(CGdiObject* pObject);
pPen指向要选择的对象的 CPen 指针
pBrush指向要选择的对象的 CBrush 指针。
pFont指向要选择的对象的 CFont 指针。
pBitmap指向要选择的对象的 CBitmap 指针。
pRgn指向要选择的对象的 CRgn 指针。
pObject指向要选择的对象的 CGdiObject 指针。

返回值

指向要替换的对象的指针。 这是指向派生自 CGdiObject其中一个类的对象(例如 CPen,具体取决于使用哪个版本的函数)的指针。 如果出现错误,则返回值 NULL 。 此函数可能会返回指向临时对象的指针。 此临时对象仅在处理一条Windows消息期间有效。 有关详细信息,请参阅 CGdiObject::FromHandle

采用区域参数的成员函数的版本执行与成员函数相同的任务 SelectClipRgn 。 其返回值可以是下列任一值:

  • COMPLEXREGION 新的剪辑区域具有重叠的边框。

  • ERROR 设备上下文或区域无效。

  • NULLREGION 新的剪辑区域为空。

  • SIMPLEREGION 新的剪辑区域没有重叠的边框。


Cpen类

CPen 类 | Microsoft Docs

封装一个 Windows 图形设备接口 (GDI) 笔。

CPen();

CPen(
    int nPenStyle,
    int nWidth,
    COLORREF crColor);

CPen(
    int nPenStyle,
    int nWidth,
    const LOGBRUSH* pLogBrush,
    int nStyleCount = 0,
    const DWORD* lpStyle = NULL);
PenStyle

指定笔样式。 此构造函数的第一个版本中的此参数可以是下列值之一:

  • PS_SOLID 创建纯色画笔。

  • PS_DASH 创建虚线。 仅当笔宽度为1或更低时,在设备单位中才有效。

  • PS_DOT 创建点式钢笔。 仅当笔宽度为1或更低时,在设备单位中才有效。

  • PS_DASHDOT 创建带有交替虚线和点的笔。 仅当笔宽度为1或更低时,在设备单位中才有效。

  • PS_DASHDOTDOT 创建带有交替虚线和双点的笔。 仅当笔宽度为1或更低时,在设备单位中才有效。

  • PS_NULL 创建 null 笔。

  • PS_INSIDEFRAME创建一个在由指定边框的 Windows GDI 输出函数生成的闭合形状框架内绘制线条的笔 (例如 Ellipse ,) 、 PieRectangleRoundRect 、、和 Chord 成员函数。 如果将此样式用于未指定边框的 Windows GDI 输出函数 (例如, LineTo 成员函数) ,则该笔的绘图区域不受帧的限制。

构造函数的第二个版本 CPen 指定类型、样式、结束端和联接属性的组合。 应使用按位 "or" (|) 运算符来合并每个类别的值。 钢笔类型可以是以下值之一:

  • PS_GEOMETRIC 创建几何笔。

  • PS_COSMETIC 创建修饰笔。

构造函数的第二个版本 CPen 为 nPenStyle 添加了以下笔样式:

  • PS_ALTERNATE 创建一个用于设置其他每个像素的笔。 (此样式仅适用于修饰笔。 )

PS_USERSTYLE 创建使用用户提供的样式数组的笔。

结尾端可以是下列值之一:

  • PS_ENDCAP_ROUND 结束大写字母为舍入。

  • PS_ENDCAP_SQUARE 结束大写字母为方形。

  • PS_ENDCAP_FLAT 结束端帽为平面。

联接可以是以下值之一:

  • PS_JOIN_BEVEL 联接是斜切的。

  • PS_JOIN_MITER 当联接在函数所设置 SetMiterLimit 的当前限制范围内时,它们会处于斜接。 如果联接超过此限制,则它是凹凸的。

  • PS_JOIN_ROUND 联接是舍入的。

nWidth

指定笔的宽度。

  • 对于第一个版本的构造函数,如果此值为0,则无论映射模式如何,设备单位中的宽度始终为1个像素。

  • 对于构造函数的第二个版本,如果 nPenStyle 为 PS_GEOMETRIC ,则在逻辑单元中给出宽度。 如果 nPenStyle 为 PS_COSMETIC ,则宽度必须设置为1。

crColor包含用于笔的 RGB 颜色。
pLogBrushLOGBRUSH指向结构。 如果 nPenStyle 为 PS_COSMETIC , lbColor 则结构的成员 LOGBRUSH 指定笔的颜色,并且 lbStyle 结构的成员 LOGBRUSH 必须设置为 BS_SOLID 。 如果 nPenStyle 为 PS_GEOMETRIC ,则必须将所有成员用于指定笔的画笔属性。
nStyleCount指定数组的长度(以双字单位表示) lpStyle 。 如果 nPenStyle 不 PS_USERSTYLE 是,该值必须为零。
lpStyle指向一组双字值。 第一个值指定用户定义样式中第一个短划线的长度,第二个值指定第一个空间的长度,依此类推。 如果 nPenStyle 不 PS_USERSTYLE 是,则该指针必须是 NULL 。

使用指定的样式、宽度和画笔特性创建逻辑修饰或几何钢笔,并将其附加到 CPen 对象。

BOOL CreatePen(
    int nPenStyle,
    int nWidth,
    COLORREF crColor);

BOOL CreatePen(
    int nPenStyle,
    int nWidth,
    const LOGBRUSH* pLogBrush,
    int nStyleCount = 0,
    const DWORD* lpStyle = NULL);
nPenStyle指定笔的样式。 有关可能值的列表,请参见构造函数中 CPen 的 nPenStyle 参数。
nWidth

指定笔的宽度。

  • 对于的第一个版本 CreatePen ,如果此值为0,则无论映射模式如何,设备单位中的宽度始终为1个像素。

  • 对于第二个版本的 CreatePen ,如果 nPenStyle 为 PS_GEOMETRIC ,则在逻辑单元中给出宽度。 如果 nPenStyle 为 PS_COSMETIC ,则宽度必须设置为1。

crColor包含用于笔的 RGB 颜色。
pLogBrushLOGBRUSH指向结构。 如果 nPenStyle 为 PS_COSMETIC , lbColor 则结构的成员 LOGBRUSH 指定笔的颜色,并且 lbStyle 结构的成员 LOGBRUSH 必须设置为 BS_SOLID 。 如果 nPenStyle 为 PS_GEOMETRIC ,则必须将所有成员用于指定笔的画笔属性。
nStyleCount指定数组的长度(以双字单位表示) lpStyle 。 如果 nPenStyle 不 PS_USERSTYLE 是,该值必须为零。
lpStyle指向一组双字值。 第一个值指定用户定义样式中第一个短划线的长度,第二个值指定第一个空间的长度,依此类推。 如果 nPenStyle 不 PS_USERSTYLE 是,则该指针必须是 NULL 。

返回值

如果成功,则为非零; 如果方法失败,则为零。

 

CBrush

封装一个 Windows 图形设备接口 (GDI) 画笔。

class CBrush : public CGdiObject

CBrush::CBrush

构造 CBrush 对象。

CBrush();
CBrush(COLORREF crColor);
CBrush(int nIndex, COLORREF crColor);
explicit CBrush(CBitmap* pBitmap);
crColor指定画笔的前景色作为 RGB 颜色。 如果画笔为影线,则此参数指定阴影的颜色。
nIndex

指定画笔的阴影样式。 它可以是下列值之一:

  • HS_BDIAGONAL 向下影线 (向右) 45 度

  • HS_CROSS 水平和垂直交叉影线

  • HS_DIAGCROSS 45度的交叉影线

  • HS_FDIAGONAL 向上影线 (从左到右) ,以45度为单位

  • HS_HORIZONTAL 水平影线

  • HS_VERTICAL 垂直影线

pBitmap指向一个 CBitmap 对象,该对象指定画笔用于绘制的位图。

CBrush::CreateHatchBrush

使用指定的阴影模式和颜色初始化画笔。

BOOL CreateHatchBrush(
    int nIndex,
    COLORREF crColor);
crColor指定画笔的前景色作为 RGB 颜色 (阴影) 的颜色。 有关详细信息,请参阅 COLORREF 中的 Windows SDK。
nIndex

指定画笔的阴影样式。 它可以是下列值之一:

  • HS_BDIAGONAL 向下影线 (向右) 45 度

  • HS_CROSS 水平和垂直交叉影线

  • HS_DIAGCROSS 45度的交叉影线

  • HS_FDIAGONAL 向上影线 (从左到右) ,以45度为单位

  • HS_HORIZONTAL 水平影线

  • HS_VERTICAL 垂直影线

返回值

如果成功,则不为 0;否则为 0。

 

 



对鼠标进行编程

 

 


鼠标消息处理程序

.cpp自动添加

void CSketcherView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CView::OnLButtonUp(nFlags, point);
}


void CSketcherView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CView::OnLButtonDown(nFlags, point);
}


void CSketcherView::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CView::OnMouseMove(nFlags, point);
}

.h自动添加

public:
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);

CWnd::OnLButtonUp

当用户释放鼠标左键时,框架将调用此成员函数。

afx_msg void OnLButtonUp(
    UINT nFlags,
    CPoint point);
nFlags

指示各种虚拟密钥是否已关闭。 此参数可以是以下值的任意组合:

  • MK_CONTROL 设置 Ctrl 键是否关闭。

  • MK_MBUTTON 设置鼠标中间按钮是否关闭。

  • MK_RBUTTON 设置鼠标右键是否关闭。

  • MK_SHIFT 如果 SHIFT 键关闭,则设置。

point指定游标的 x 坐标和 y 坐标。 这些坐标始终相对于窗口左上角。

用鼠标绘图

InvalidateRect

InvalidateRect函数将一个矩形添加到指定窗口的更新区域。更新区域表示必须重绘的窗口客户区部分。

BOOL InvalidateRect(
  [in] HWND       hWnd,
  [in] const RECT *lpRect,
  [in] BOOL       bErase
);
[in] hWnd更新区域已更改的窗口的句柄。如果此参数为NULL,系统将无效并重绘所有窗口,而不仅仅是此应用程序的窗口,并在函数返回之前发送WM_ERASEBKGNDWM_NCPAINT消息。不建议将此参数设置为NULL 。
[in] lpRect指向RECT结构的指针,该结构包含要添加到更新区域的矩形的客户端坐标。如果此参数为NULL,则将整个客户区添加到更新区域。
[in] bErase指定在处理更新区域时是否擦除更新区域内的背景。如果此参数为TRUE ,则​​在调用BeginPaint函数时将擦除背景。如果此参数为FALSE,则背景保持不变。

返回值
如果函数成功,则返回值非零。如果函数失败,则返回值为零。



//SketcherView.h
class CSketcherView : public CView
{
protected:
	CPoint m_FirstPoint;


}
//SketcherView.cpp
CSketcherView::CSketcherView(): m_FirstPoint{ CPoint{} }
{
	// TODO: 在此处添加构造代码

}

定义元素的类

生成 CElement.h和CElement.cpp

//Element.h
#pragma once

// CElement 命令目标

class CElement : public CObject
{
public:
	CElement();
	virtual ~CElement();
};
// Element.cpp: 实现文件
//

#include "pch.h"
#include "Sketcher.h"
#include "CElement.h"


// CElement

CElement::CElement()
{
}

CElement::~CElement()
{
}

其他元素类把CElement而不是MFC类作为基类,所以应该把类类别选作C++。

  自动生产CLine.h和CLine.cpp

//Line.h
#pragma once
#include "Element.h"
class CLine :
    public CElement
{
};
//Line.cpp
#include "pch.h"
#include "Line.h"

相同步骤添加CRectangle、CCircle、CCurve,以CElement为基类。


// SketcherView.h: CSketcherView 类的接口
//

#pragma once
#include <memory>
#include "Element.h"

 

CElement类

//Element.h
#pragma once

// CElement 命令目标

class CElement : public CObject
{
public:
	virtual ~CElement();
	virtual void Draw(CDC* pDC) {}  // Virtual draw operation	
	const CRect& GetEnclosingRect() const { return m_EnclosingRect; }// Get the element enclosing rectangle

protected:
	CPoint m_StartPoint;            // Element position      
	int m_PenWidth;                 // Pen width
	COLORREF m_Color;               // Color of an element
	CRect m_EnclosingRect;          // Rectangle enclosing an element

protected:
	void CreatePen(CPen& aPen);

	// Constructors protected so they cannot be called outside the class
	CElement();
	CElement(const CPoint& start, COLORREF color, int penWidth = 1);
};
// Element.cpp: 实现文件

#include "pch.h"
#include "Sketcher.h"
#include "Element.h"

void CElement::CreatePen(CPen& aPen)
{
	if (!aPen.CreatePen(PS_SOLID, m_PenWidth, m_Color))
	{
		//创建笔失败
		AfxMessageBox(_T("Pen创建失败."), MB_OK);
		AfxAbort();
	}
}

// CElement
CElement::CElement()
{
}

CElement::~CElement()
{
}

// CElement 成员函数
CElement::CElement(const CPoint& start, COLORREF color, int penWidth) :  m_StartPoint{ start }, m_PenWidth{ penWidth }, m_Color{ color } 
{

}

CLine类

//CLine.h
#pragma once
#include "Element.h"
class CLine :  public CElement
{
public:
    virtual ~CLine();
    virtual void Draw(CDC* pDC) override;
    CLine(const CPoint& start, const CPoint& end, COLORREF color);

protected:
    CPoint m_EndPoint;

protected:
    CLine();
};
// CLine.cpp: 实现文件

#include "pch.h"
#include "Line.h"

CLine::~CLine()
{

}

void CLine::Draw(CDC* pDC)
{
	CPen aPen;		
	CreatePen(aPen);// 基类CElement::CreatePen(CPen& aPen)

	CPen* pOldPen{ pDC->SelectObject(&aPen) };	//SelectObject:指向要替换的对象的指针(旧画笔)。

	pDC->MoveTo(m_StartPoint);	//画直线
	pDC->LineTo(m_EndPoint);

	pDC->SelectObject(pOldPen);		//恢复旧画笔
}

CLine::CLine(const CPoint& start, const CPoint& end, COLORREF color) : 
	CElement{ start,color }, m_EndPoint{end}
{
	//定义封闭的矩形
	m_EnclosingRect = CRect{ start,end };
	m_EnclosingRect.NormalizeRect();		//规范化 CRect ,使高度和宽度都为正。
	m_EnclosingRect.InflateRect(m_PenWidth, m_PenWidth);	//	增加CRect的宽度和高度 。
}

CLine::CLine()
{

}

CRectangle类

//Rectangle.h
#pragma once
#include "Element.h"
class CRectangle :
    public CElement
{
public:
    virtual ~CRectangle();
    virtual void Draw(CDC* pDC) override;
    CRectangle(const CPoint& start, const CPoint& end, COLORREF color);

protected:
    CPoint m_BottomRight;   //右下角坐标点

protected:
    CRectangle();
};
//Rectangle.cpp
#include "pch.h"
#include "Rectangle.h"
#include <algorithm>

CRectangle::~CRectangle()
{
}

void CRectangle::Draw(CDC* pDC)
{
	CPen aPen;
	CreatePen(aPen);// 基类CElement::CreatePen(CPen& aPen)

	CPen* pOldPen{ pDC->SelectObject(&aPen) };	//SelectObject:指向要替换的对象的指针(旧画笔)。
	CBrush* pOldBrush{ dynamic_cast<CBrush*> (pDC->SelectStockObject(NULL_BRUSH)) };	//选择一个 CGdiObject 对象,该对象对应于预定义的股票笔、画笔或字体之一。 NULL_PEN Null 笔

	pDC->Rectangle(m_StartPoint.x, m_StartPoint.y, m_BottomRight.x, m_BottomRight.y);	//画矩形

	pDC->SelectObject(pOldBrush);   //恢复画刷
	pDC->SelectObject(pOldPen);		//恢复旧画笔
}

CRectangle::CRectangle(const CPoint& start, const CPoint& end, COLORREF color) : CElement{start,color}
{
	m_StartPoint = CPoint{ (std::min)(start.x,end.x),(std::min)(start.y,end.y) };	//左上角取x,y的最小值
	m_BottomRight = CPoint{ (std::max)(start.x,end.x),(std::max)(start.y,end.y) };	//右下角取x,y的最大值

	if ((m_BottomRight.x - m_StartPoint.x) < 2)	m_BottomRight.x = m_StartPoint.x + 2;
	if ((m_BottomRight.y - m_StartPoint.y) < 2)	m_BottomRight.y = m_StartPoint.y + 2;

	m_EnclosingRect = CRect{ m_StartPoint, m_BottomRight };
	m_EnclosingRect.InflateRect(m_PenWidth, m_PenWidth);//	增加CRect的宽度和高度 。
}

CRectangle::CRectangle()
{
}

Circle

//Circle.h
#pragma once
#include "Element.h"
class CCircle :
    public CElement
{
public:
    virtual ~CCircle();
    virtual void Draw(CDC* pDC) override;
    CCircle(const CPoint& start, const CPoint& end, COLORREF color);

protected:
    CPoint m_BottomRight;

protected:
    CCircle();
};
//Circle.cpp
#include "pch.h"
#include "Circle.h"
#include <cmath>

CCircle::~CCircle()
{
}

void CCircle::Draw(CDC* pDC)
{
	CPen aPen;
	CreatePen(aPen);// 基类CElement::CreatePen(CPen& aPen)

	CPen* pOldPen{ pDC->SelectObject(&aPen) };	//SelectObject:指向要替换的对象的指针(旧画笔)。
	CBrush* pOldBrush{ dynamic_cast<CBrush*> (pDC->SelectStockObject(NULL_BRUSH)) };	//选择一个 CGdiObject 对象,该对象对应于预定义的股票笔、画笔或字体之一。 NULL_PEN Null 笔

	pDC->Ellipse(m_StartPoint.x, m_StartPoint.y, m_BottomRight.x, m_BottomRight.y);	//画矩形

	pDC->SelectObject(pOldBrush);   //恢复画刷
	pDC->SelectObject(pOldPen);		//恢复旧画笔
}

CCircle::CCircle(const CPoint& start, const CPoint& end, COLORREF color) : CElement{start , color}
{
	long radius{static_cast<long>(sqrt(static_cast<double>(pow(end.x-start.x,2)+pow(end.y-start.y,2))))};
	if (radius < 1L) radius = 1L;

	m_StartPoint = CPoint{start.x - radius,start.y-radius};
	m_BottomRight = CPoint{ start.x + radius,start.y + radius };

	m_EnclosingRect = CRect{ m_StartPoint.x,m_StartPoint.y,m_BottomRight.x,m_BottomRight.y };
	m_EnclosingRect.InflateRect(m_PenWidth, m_PenWidth);	//	增加CRect的宽度和高度 。
}

CCircle::CCircle()
{
}

CCurve类

//Curve.h
#pragma once
#include "Element.h"
#include <vector>

class CCurve :  public CElement
{
public:
    virtual ~CCurve();
    virtual void Draw(CDC* pDC) override;
    CCurve(const CPoint& start, const CPoint& second, COLORREF color);
    void AddSegment(const CPoint& point);   // 向曲线添加段

protected:
    std::vector<CPoint> m_Points;           // 定义曲线的点群

protected:
    CCurve();
};
//Curve.cpp
#include "pch.h"
#include "Curve.h"
#include <algorithm>

CCurve::~CCurve()
{
}

void CCurve::Draw(CDC* pDC)
{
	CPen aPen;
	CreatePen(aPen);// 基类CElement::CreatePen(CPen& aPen)

	CPen* pOldPen{ pDC->SelectObject(&aPen) };	//SelectObject:指向要替换的对象的指针(旧画笔)。

	pDC->MoveTo(m_StartPoint);	//画直线
	for(const auto& point : m_Points)
		pDC->LineTo(point);

	pDC->SelectObject(pOldPen);		//恢复旧画笔
}

CCurve::CCurve(const CPoint& start, const CPoint& second, COLORREF color) : CElement{ start,color }
{
	m_Points.push_back(second);
	m_EnclosingRect = CRect{ (std::min)(start.x,second.x),(std::min)(start.y,second.y),(std::max)(start.x,second.x),(std::max)(start.y,second.y) };
	m_EnclosingRect.InflateRect(m_PenWidth, m_PenWidth);	//	增加CRect的宽度和高度 。
}

void CCurve::AddSegment(const CPoint& point)
{
	m_Points.push_back(point);
	m_EnclosingRect.DeflateRect(m_PenWidth, m_PenWidth);	//减小 CRect 的宽度和高度。
	m_EnclosingRect = CRect{ (std::min)(point.x,m_EnclosingRect.left),(std::min)(point.y,m_EnclosingRect.top),(std::max)(point.x,m_EnclosingRect.right),(std::max)(point.y,m_EnclosingRect.bottom) };
	m_EnclosingRect.InflateRect(m_PenWidth, m_PenWidth);	//	增加CRect的宽度和高度 。
}

CCurve::CCurve()
{
}

鼠标消息处理程序

 

 

// SketcherView.cpp: 

#include "Line.h"
#include "Rectangle.h"
#include "Circle.h"
#include "Curve.h"

void CSketcherView::OnMouseMove(UINT nFlags, CPoint point)
{
	//CClientDC 类:负责在构建时调用 Windows 函数GetDC ,在销毁时调用ReleaseDC。 CClientDC(CWnd* pWnd);
	CClientDC aDC{ this };	//创建设备上下文
	if (nFlags & MK_LBUTTON)	//MK_LBUTTON:按下鼠标左键
	{
		m_SecondPoint = point;//保存当前坐标
		if (ElementType::CURVE == GetDocument()->GetElementType())	//线段 CDocument* m_pDocument;
		{
			std::dynamic_pointer_cast<CCurve>(m_pTempElement)->AddSegment(m_SecondPoint);	//添加线段
			m_pTempElement->Draw(&aDC);	//绘图
			return;
		}
		else  //临时元素存在,不是曲线
		{
			aDC.SetROP2(R2_NOTXORPEN);	//SetROP2函数设置当前的前景混合模式。
			m_pTempElement->Draw(&aDC);	//绘图
		}
	}
	//如果临时元素存在,但不是曲线,或没有临时元素存在
	m_pTempElement = CreateElement();	//创建新元素
	m_pTempElement->Draw(&aDC);	//按正常方式绘图

	//CView::OnMouseMove(nFlags, point);
}
// SketcherView.h:

class CSketcherView : public CView
{
protected:
	std::shared_ptr<CElement> CreateElement(void) const;           //在堆上创建一个新元素
}
// SketcherView.cpp

//创建元素
std::shared_ptr<CElement> CSketcherView::CreateElement(void) const
{
	CSketcherDoc* pDoc = GetDocument();	//获取指向视图文档的指针
	ASSERT_VALID(pDoc);//检查指针有效

	COLORREF color{static_cast<COLORREF>(pDoc->GetElementColor())};	//获取颜色指针

	switch (pDoc->GetElementType())
	{
	case ElementType::RECTANGLE:
		return std::make_shared<CRectangle>(m_FirstPoint,m_SecondPoint,color);	// std::make_shared 可以返回一个指定类型的 std::shared_ptr ,执行构造

	case ElementType::CIRCLE:
		return std::make_shared<CCircle>(m_FirstPoint, m_SecondPoint, color);

	case ElementType::CURVE:
		return std::make_shared<CCurve>(m_FirstPoint, m_SecondPoint, color);

	case ElementType::LINE:
		return std::make_shared<CLine>(m_FirstPoint, m_SecondPoint, color);

	default:
		AfxMessageBox(_T("Bad element code"),MB_OK);
		AfxAbort();
		return nullptr;
	}
}

//SketcherDoc.h

#include "Element.h"	//添加
#include <list>	//添加
#include <memory>	//添加

class CSketcherDoc : public CDocument
{

protected:
	std::list<std::shared_ptr<CElement>> m_Sketch;	//添加


// 实现 Implementation
public:
	void AddElement(std::list<std::shared_ptr<CElement>>& pElement);	//添加元素
	void DeleteElement(std::list<std::shared_ptr<CElement>>& pElement);	//删除元素
}
// SketcherDoc.cpp:
void CSketcherDoc::AddElement(std::shared_ptr<CElement>& pElement)
{
	m_Sketch.push_back(pElement);
}
void CSketcherDoc::DeleteElement(std::shared_ptr<CElement>& pElement)
{
	m_Sketch.remove(pElement);
}

// SketcherView.cpp
void CSketcherView::OnLButtonUp(UINT nFlags, CPoint point)
{
	if (m_pTempElement)
	{
		GetDocument()->AddElement(m_pTempElement);	//添加Element元素
		InvalidateRect(&m_pTempElement->GetEnclosingRect());	//CWnd::InvalidateRect通过将该矩形添加到 CWnd 更新区域,使给定矩形中的工作区失效。
		m_pTempElement.reset();	// 释放资源并转换为空的 shared_ptr 对象
	}
	//CView::OnLButtonUp(nFlags, point);
}

让视图对象获得绘制草图所需的迭代器

//SketcherDoc.h

class CSketcherDoc : public CDocument
{
public:
	// 为 sketch提供一个开始迭代器
	std::list<std::shared_ptr<CElement>>::const_iterator begin() const;
	// 为sketch提供一个结束迭代器 
	std::list<std::shared_ptr<CElement>>::const_iterator end() const;
}
// SketcherDoc.cpp:
// 为 sketch提供一个开始迭代器
std::list<std::shared_ptr<CElement>>::const_iterator CSketcherDoc::begin() const
{
	return std::begin(m_Sketch);
}
// 为sketch提供一个结束迭代器 
std::list<std::shared_ptr<CElement>>::const_iterator CSketcherDoc::end() const
{
	return std::end(m_Sketch);
}
// SketcherView.cpp

// CSketcherView 绘图
void CSketcherView::OnDraw(CDC* pDC)
{
	CSketcherDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	//绘制
	for (auto iter=pDoc->begin();iter != pDoc->end();++iter)	//遍历
	{
		for (const auto& pElement : *pDoc)  //遍历元素
		{
			if (pDC->RectVisible(pElement->GetEnclosingRect()))	//DC::RectVisible 确定给定矩形的任何部分是否位于显示上下文的剪辑区域中。
				pElement->Draw(pDC);	
		}
	}
}

运行并不正常,捕获鼠标消息。

// SketcherView.cpp

void CSketcherView::OnLButtonDown(UINT nFlags, CPoint point)
{
	m_FirstPoint = point;	//记录起点
	SetCapture();	//导致所有后续鼠标输入都发送到当前 CWnd 对象,而不考虑光标的位置。
	//CView::OnLButtonDown(nFlags, point);
}

// SketcherView.cpp

void CSketcherView::OnLButtonUp(UINT nFlags, CPoint point)
{
	if (this == GetCapture())	//GetCapture 检索具有鼠标捕获的 CWnd。
		ReleaseCapture();	//从当前线程中的窗口释放鼠标捕获并恢复正常的鼠标输入处理。

	if (m_pTempElement)
	{
		GetDocument()->AddElement(m_pTempElement);	//添加Element元素
		InvalidateRect(&m_pTempElement->GetEnclosingRect());	//CWnd::InvalidateRect通过将该矩形添加到 CWnd 更新区域,使给定矩形中的工作区失效。
		m_pTempElement.reset();	// 释放资源并转换为空的 shared_ptr 对象
	}
	//CView::OnLButtonUp(nFlags, point);
}

// SketcherView.cpp

void CSketcherView::OnMouseMove(UINT nFlags, CPoint point)
{
	//CClientDC 类:负责在构建时调用 Windows 函数GetDC ,在销毁时调用ReleaseDC。 CClientDC(CWnd* pWnd);
	CClientDC aDC{ this };	//创建设备上下文
	if ((nFlags & MK_LBUTTON) && (this == GetCapture()))	//MK_LBUTTON:按下鼠标左键
	{
		m_SecondPoint = point;//保存当前坐标
		if (m_pTempElement)
		{
			if (ElementType::CURVE == GetDocument()->GetElementType())	//线段 CDocument* m_pDocument;
			{
				std::dynamic_pointer_cast<CCurve>(m_pTempElement)->AddSegment(m_SecondPoint);	//添加线段
				m_pTempElement->Draw(&aDC);	//绘图
				return;
			}
			else  //临时元素存在,不是曲线
			{
				aDC.SetROP2(R2_NOTXORPEN);	//SetROP2函数设置当前的前景混合模式。
				m_pTempElement->Draw(&aDC);	//绘图
			}
		}
		//如果临时元素存在,但不是曲线,或没有临时元素存在
		m_pTempElement = CreateElement();	//创建新元素
		m_pTempElement->Draw(&aDC);	//按正常方式绘图		
	}
	//CView::OnMouseMove(nFlags, point);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值