MFC:窗口绘图

窗口客户区

MFC:窗口绘图_第1张图片

Windows图形设备界面

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


设备上下文

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

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

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

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


映射模式

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

MFC:窗口绘图_第2张图片

 MFC:窗口绘图_第3张图片


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。

MFC:窗口绘图_第4张图片

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。

MFC:窗口绘图_第5张图片

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

MFC:窗口绘图_第6张图片

使用指定的样式、宽度和画笔特性创建逻辑修饰或几何钢笔,并将其附加到 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 颜色。
pLogBrush LOGBRUSH指向结构。 如果 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 对象,该对象指定画笔用于绘制的位图。

MFC:窗口绘图_第7张图片

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。

 

 



对鼠标进行编程

MFC:窗口绘图_第8张图片

 MFC:窗口绘图_第9张图片

 MFC:窗口绘图_第10张图片

MFC:窗口绘图_第11张图片

MFC:窗口绘图_第12张图片


鼠标消息处理程序MFC:窗口绘图_第13张图片

.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 坐标。 这些坐标始终相对于窗口左上角。

MFC:窗口绘图_第14张图片

用鼠标绘图

MFC:窗口绘图_第15张图片

MFC:窗口绘图_第16张图片

InvalidateRect

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

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

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

MFC:窗口绘图_第17张图片



MFC:窗口绘图_第18张图片

MFC:窗口绘图_第19张图片

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


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

}

定义元素的类

MFC:窗口绘图_第20张图片

MFC:窗口绘图_第21张图片

MFC:窗口绘图_第22张图片

MFC:窗口绘图_第23张图片

生成 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++。

MFC:窗口绘图_第24张图片

MFC:窗口绘图_第25张图片

  自动生产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 
#include "Element.h"

MFC:窗口绘图_第26张图片

MFC:窗口绘图_第27张图片

MFC:窗口绘图_第28张图片

MFC:窗口绘图_第29张图片

 

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类

MFC:窗口绘图_第30张图片

//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 

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 (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

MFC:窗口绘图_第31张图片

//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 

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 (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(sqrt(static_cast(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类

MFC:窗口绘图_第32张图片

//Curve.h
#pragma once
#include "Element.h"
#include 

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 m_Points;           // 定义曲线的点群

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

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()
{
}

鼠标消息处理程序

MFC:窗口绘图_第33张图片

MFC:窗口绘图_第34张图片

 MFC:窗口绘图_第35张图片

 MFC:窗口绘图_第36张图片

// 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(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 CreateElement(void) const;           //在堆上创建一个新元素
}
// SketcherView.cpp

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

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

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

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

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

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

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

MFC:窗口绘图_第37张图片

//SketcherDoc.h

#include "Element.h"	//添加
#include 	//添加
#include 	//添加

class CSketcherDoc : public CDocument
{

protected:
	std::list> m_Sketch;	//添加


// 实现 Implementation
public:
	void AddElement(std::list>& pElement);	//添加元素
	void DeleteElement(std::list>& pElement);	//删除元素
}
// SketcherDoc.cpp:
void CSketcherDoc::AddElement(std::shared_ptr& pElement)
{
	m_Sketch.push_back(pElement);
}
void CSketcherDoc::DeleteElement(std::shared_ptr& 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>::const_iterator begin() const;
	// 为sketch提供一个结束迭代器 
	std::list>::const_iterator end() const;
}
// SketcherDoc.cpp:
// 为 sketch提供一个开始迭代器
std::list>::const_iterator CSketcherDoc::begin() const
{
	return std::begin(m_Sketch);
}
// 为sketch提供一个结束迭代器 
std::list>::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);	
		}
	}
}

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

MFC:窗口绘图_第38张图片

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

 MFC:窗口绘图_第39张图片

 

你可能感兴趣的:(c++,mfc,windows,c++)