应用程序选择
文档模板属性
用户界面功能
高级功能
生成的类,会生成 APP,VIEW,DOC,MainFrame
因为这两个项目在同一个解决方案下面,所以把MFCPaint设置为启动项目
编译MFCPaint项目
在 MFCPaintView.h 文件中记录了 MFCPaintView 类的原型,在 MFCPaintView.cpp 中记录 MFCPaintView 类的实现,虚函数的实现,消息映射机制。源码如下:
BEGIN_MESSAGE_MAP(CMFCPaintView, CView)
ON_WM_CONTEXTMENU()
ON_WM_RBUTTONUP()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_CREATE()
ON_WM_CHAR()
END_MESSAGE_MAP()
一些消息比如 WM_PAINT这样的消息又框架封装,自动处理这个消息映射到OnDraw函数处理,这个函数在CView中是虚函数,MFCPaintView中仍然是虚函数,但在MFCPaintView实例化时进行了实现。还有一些消息的处理需要依赖消息映射表来处理
在 BEGIN_MESSAGE_MAP 宏中,CMFCPaintView 是接收消息的类,而 CView 则是基类。
通过将 CMFCPaintView 作为接收消息的类,我们可以在 CMFCPaintView 类中定义消息处理函数,并将它们与相应的消息进行关联。当窗口接收到对应的消息时,框架会自动调用相应的处理函数来处理该消息。
而 CView 则是作为基类,在宏的第二个参数中提供给 BEGIN_MESSAGE_MAP,它是处理消息的起点,当消息映射表中的消息没有匹配到具体的处理函数时,框架会继续向基类查找,直到找到匹配的处理函数或者到达消息映射表的末尾。这样的设计使得消息处理可以在派生类和基类中灵活地进行组织和划分。
给消息映射表添加映射,MFCPaintView右键,选择属性,选择消息,选择想要添加的消息即可
比如添加 WM_CHAR消息处理,就会在MFCPaintView中添加一个函数
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg是 MFC 框架中的一个宏,用于声明一个消息处理函数。在 MFC 中,消息处理函数需要使用afx_msg宏进行声明,以便正确连接到消息映射机制。
在MFCPaintView中就会添加一个方法。
WM_PAINT 消息是 Windows 操作系统发送给窗口的一个重要消息,它用于通知窗口需要进行绘图操作。当窗口的客户区域(即窗口内部可以进行绘图操作的区域)需要刷新或重绘时,Windows 就会发送 WM_PAINT 消息给窗口,以便窗口进行相应的绘图操作。
WM_PAINT 消息的触发通常由以下几种情况引起:
当窗口接收到 WM_PAINT 消息时,通常会执行以下操作:
在MFC消息映射表没有WM_PAINT的映射,WM_PAINT是如何调用OnDraw()函数处理?
在 MFC 中,如果消息映射表中没有 WM_PAINT 的映射,框架会自动调用 CWnd::DefWindowProc 函数来处理该消息,进而调用 OnPaint 函数来进行绘图操作。所以,即使没有显式地映射 WM_PAINT 消息,我们也可以在 OnDraw 函数中实现窗口绘图。
CView 类是 MFC(Microsoft Foundation Classes)框架中的一个基类,用于实现视图(View)的功能。在 MFC 应用程序中,视图通常用于显示和处理用户界面的可视化部分。
CView 类是从 CWnd 类派生而来的,因此它继承了 CWnd 类的一些基本窗口功能。CView 类主要用于以下几个方面的功能:
也就是说CVIEW主要控制程序的视图元素。使用VS2019时就会自动的生成一个类继承自这个类,我们在编写程序时,只需要注意处理消息即可,在类中对消息处理的实现,最后往往会再调用一个CVIEW中的消息对应处理方法。因为系统往往只会给一次消息,对这个消息可以有多个类进行处理,而且处理都不一样,这就是再调用一次的作用。
设备上下文(Device Context,简称 DC)是一个抽象概念,用于表示用于绘图和输出的设备环境。设备上下文封装了与设备相关的绘图表面、画笔、字体、剪辑区域等信息,提供了一组函数和属性,用于在特定设备上进行绘图操作。
设备上下文是由操作系统提供的,它可以代表不同类型的设备,如显示器、打印机、屏幕缓冲区等。每个设备上下文都有自己的属性和功能,可以通过设备上下文进行绘制、填充、剪辑、文本输出等操作。
在 Windows 应用程序开发中,设备上下文通常用于使用 GDI(图形设备接口)进行绘图。GDI 提供了一组函数和数据结构,用于在设备上进行绘图操作。设备上下文是 GDI 中的核心概念,通过设备上下文可以获取和操作设备相关的绘图资源,如画笔、画刷、字体等。
在 MFC(Microsoft Foundation Classes)框架中,设备上下文由 CDC(CDC 类型的指针)表示。CDC 类封装了设备上下文操作的功能,例如绘图、调色板管理、文本输出等。通过获取设备上下文指针,可以使用 CDC 类的成员函数来进行相应的绘图操作。
设备上下文的使用是有一定规则的,例如在使用完设备上下文后,需要释放相关资源,避免资源泄露。通常在获取设备上下文之后,需要调用 ReleaseDC() 或者 DeleteDC() 函数来释放设备上下文。
获取设备上下文
void CMyView::OnDraw(CDC* pDC)
{
// 获取设备上下文指针
CDC* pDC = GetDC();
// 使用设备上下文进行绘图操作
// ...
// 释放设备上下文资源
ReleaseDC(pDC);
}
CDC类中提供了很多系统调用,如获取资源,设置资源等等。
资源则是具体的实际资源,可以是文件、图像、音频、数据库连接等等,它是应用程序所需要的资料或者工具。资源可以以不同的形式存在,例如在内存中、硬盘上、网络上等。它们可以被资源类所管理和操作。
资源类通过对资源进行封装和抽象,提供了一种统一的接口和管理方式,使得应用程序能够更方便地访问和利用资源。资源类可以对资源进行创建、加载、释放、更新等操作,同时还可以提供一些额外的功能,如缓存、安全性控制、多线程支持等。
资源类就像是对资源所用的一些属性等信息进行抽象,可以提供对类实例化设置属性来设置资源的属性,再通过系统调用来使用资源,释放资源。
想要实现画线功能,需要捕捉到鼠标左键摁下,鼠标左键抬起的位置,就可以绘制处一条直线。
还可以实现更复杂的功能,当鼠标左键摁下后,不抬起,鼠标移动,同时会划出一条线跟随鼠标移动。View相关的类是控制界面的绘制,需要在其中写代码
在 MFCPaintView.h 文件中,是MFCPaintView这个类,给该类加上几个属性
CPoint m_start;// 起始点,用于绘制线条的
CPoint m_cur; // 当前点,用于绘制线条
CPoint m_ptStop; // 终止点,用于绘制线条
BOOL m_bStatus; // 绘制状态,用于绘制线条
CString m_strText; // 用户输入的字符串
有意思的时,CPoint这个类是鼠标的坐标,继承自一个结构体
typedef struct tagPOINT
{
LONG x;
LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
处理鼠标摁下消息,记录线的起始点;处理鼠标移动的消息,记录一下临时值,线的临时终点;处理鼠标抬起的消息,记录线的终点;
现在讨论一下实现的思路:
// CMFCPaintView 消息处理程序
void CMFCPaintView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_start = point;
m_bStatus = TRUE;
CView::OnLButtonDown(nFlags, point);
}
void CMFCPaintView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_ptStop = point;
//CDC* pDC = GetDC();
// 1. 什么是上下文?
// 2. 什么是设备上下文?
//pDC->MoveTo(m_start);
//pDC->LineTo(m_stop);
//ReleaseDC(pDC);
InvalidateRect(NULL);
m_bStatus = FALSE;
CView::OnLButtonUp(nFlags, point);
}
void CMFCPaintView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (m_bStatus)
{
m_cur = point;
InvalidateRect(NULL); // 调用OnDraw,界面重回
// CDC* pDC = GetDC();
// pDC->MoveTo(m_start);
// pDC->LineTo(point);
// ReleaseDC(pDC);
}
CView::OnMouseMove(nFlags, point);
}
InvalidateRect(NULL)是一种触发重绘的方法,它可以使窗口客户区域全部无效,并强制触发WM_PAINT消息,以便应用程序对客户区进行重绘。
具体而言,InvalidateRect(NULL)函数会将整个窗口客户区域标记为无效(即需要重新绘制),并记录下这个无效区域的位置和大小。然后,它会向系统发送WM_PAINT消息,以通知应用程序客户区需要重新绘制。
在应用程序收到WM_PAINT消息后,它会根据无效区域的位置和大小,重新绘制客户区。
需要指出的是,InvalidateRect(NULL)函数通常是在需要强制更新整个客户区时使用的,因为它会将整个客户区标记为无效,即使只有一小部分区域需要更新。如果只需要更新某个特定的区域,则可以使用InvalidateRect()函数的其他重载版本,它们可以指定需要更新的区域。
在OnDraw来实现画直线
// 当像素超过1时,线型会失效
CPen pen(PS_DASHDOT, 1, RGB(255, 0, 0));
// 返回一个笔
CPen* pOldPen = pDC->SelectObject(&pen);
CPoint start = m_start, cur = m_cur;
start.y--;
if (m_bStatus)
{
cur.y--;
pDC->MoveTo(m_start);
pDC->LineTo(m_cur);
pDC->MoveTo(start);
pDC->LineTo(cur);
}
else
{
cur = m_cur;
cur.y--;
pDC->MoveTo(m_start);
pDC->LineTo(m_cur);
pDC->MoveTo(start);
pDC->LineTo(cur);
}
// 还原原来的笔
pDC->SelectObject(pOldPen);
绘制矩形
if (m_bStatus)
{
// 选择NULL就会用上下文的刷子,不选空就会用指定的刷子
else
{
pDC->FillRect(CRect(m_start, m_ptStop), NULL);
}
在MFC中,画布(Canvas)是指一个表示绘图区域的设备上下文(Device Context,DC),它提供了绘制图形和文本的基本操作。
画布通常与窗口客户区关联,它对应于屏幕上的一个矩形区域,用于绘制窗口的内容。在MFC中,可以通过调用CWnd类的GetDC函数来获取当前窗口客户区的设备上下文句柄,从而获得画布的访问权。
一旦获取了画布的设备上下文句柄,就可以使用GDI(图形设备接口)函数来绘制各种图形和文本。例如,可以使用LineTo函数在画布上绘制一条直线,使用Rectangle函数绘制矩形,使用TextOut函数绘制文本等。
实例化画笔类CPen,并设置到CDC中,会返回以一个旧的画笔
CPen pen(PS_DASHDOT, 1, RGB(255, 0, 0));
// 返回一个笔
CPen* pOldPen = pDC->SelectObject(&pen);
绘制完成后,需要恢复
pDC->SelectObject(pOldBrush);
实例化画刷类CBrush,并设置到CDC中,会返回以一个旧的画刷
CBrush brush(RGB(255, 0, 0)), brush2(RGB(0, 255, 0));
CBrush* pOldBrush = pDC->SelectObject(&brush);
绘制完成后,需要恢复
pDC->SelectObject(pOldBrush);
LOGPEN logpen;
pOldPen->GetLogPen(&logpen);
TRACE("style = %d color = %08x width = %d\r\n",
logpen.lopnStyle, logpen.lopnColor, logpen.lopnWidth);
LOGPEN logpen;是一个定义变量的语句,它声明了一个名为logpen的变量,并指定了它的数据类型为LOGPEN结构体类型。
在Windows GDI中,LOGPEN结构体用于描述一个逻辑画笔的属性,包括画笔样式、宽度和颜色等信息。LOGPEN结构体的定义如下:
typedef struct tagLOGPEN {
UINT lopnStyle; // 画笔样式
POINT lopnWidth; // 画笔宽度(当样式为PS_GEOMETRIC时,表示线段两端的宽度)
COLORREF lopnColor; // 画笔颜色
} LOGPEN;
模仿写一个文本编辑器
响应字符消息WM_CHAR
void CMFCPaintView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
TRACE("%c\r\n", nChar);
CClientDC dc(this);
m_strText += (TCHAR)nChar;
InvalidateRect(NULL);
CView::OnChar(nChar, nRepCnt, nFlags);
}
每当接受一个新字符就触发重绘,在OnDraw进行实现
每次都是要遍历真个字符串,未匹配到换行符就放入到sub中;匹配到换行符,把先前sub中的打印出来,同时调用CSIZE的GetTextExtent()方法,获取此时 | 应该放置的位置并调用全局的方法SetCaretPos(sz.cx + 2, y);重置位置。
CString sub = _T("");
int y = 0;
for (int i = 0; i < m_strText.GetLength(); i++)
{
if ((m_strText.GetAt(i) == '\n') || (m_strText.GetAt(i) == '\r'))
{
pDC->TextOut(0, y, sub);
CSize sz = pDC->GetTextExtent(sub);
sub.Empty();
y += sz.cy + 2;
continue;
}
sub += m_strText.GetAt(i);
}
if (sub.IsEmpty() == false)
pDC->TextOut(0, y, sub); //Textout()只能显示单行文本
CSize sz = pDC->GetTextExtent(sub);
//方式一:
//CPoint pt; //CPoint 是Point的子类
//pt.y = y;
//pt.x = sz.cx + 2;
//SetCarePos(pt.x, pt.y);
// 方式二:
//SetCarePos(CPoint(sz.cx + 2, y));
//方式三:
::SetCaretPos(sz.cx + 2, y);
CSize sz = pDC->GetTextExtent(sub);是一个将字符串绘制到设备上下文(pDC)的语句,并返回绘制的文本区域的大小。
在MFC(Microsoft Foundation Classes)中,资源的ID号用于唯一标识和引用资源。MFC中的资源可以是窗口、对话框、菜单、图标、位图、字符串等。
资源的ID号具有以下作用:
在资源视图->IDR_MAINFRAME中可以编辑菜单栏
主菜单是没有ID的;子菜单可以设置
给菜单添加响应函数,在类视图中找打MFCPaintView,选择类向导,选择消息,选择添加处理程序
方法被添加
查看消息映射表,消息翻译为WM_COMMAND再进行区分。
有view和doc,触发了view,但是没有触发doc
去掉view类的菜单响应函数,打开doc类的响应函数。触发view类,不触发doc view>doc
去掉doc类的菜单响应函数,打开框架类的响应函数,触发doc类,不触发app doc>框架
去掉app类的菜单响应函数,打开app类的响应函数框架》app
响应菜单命令顺序:View> doc >框架 >app
工具栏资源就在资源视图 TOOLBAR中
添加一个工具
选中最后一个空的,手动绘制
需要注意,这个需要绘制两个,填入ID,处理方式与菜单一样。
删除工具,拖着方块移出区域即可。