在MSDN中看着英文版的document中的scribble的例子,发现:自己的英语不够用,而且MSDN里面的教程没有目录,看着太乱了。想了想还是自己梳理一下,看着比较有条理。(MFC绘图常用函数:http://blog.sina.com.cn/s/blog_1362f005c0102vyem.html)
零、知识了解
Scribble演示了MFC编程模型,它将程序数据(文档)的存储与该数据的显示(视图)以及大多数用户与数据的交互分开。此模型支持多个视图,多个文档类型,拆分器窗口和其他有价值的用户界面功能。文档/视图的核心是四个关键类:
要更好地了解文档/视图模型如何适用于您的应用程序,请研究下表:
此表显示如何在框架应用程序中创建和管理文档和其他对象。
此表显示了您在实施文档时的职责和框架的职责。
step1:https://mindmap.airmore.cn/doc/d3a15252fbca1e73026d7492600bc41a
步骤:
一、先创建一个多文档(MDI)的MFC应用程序。
二、创建文档(doc)
Scribble中的文档存储构成绘图的线条或“笔划”。由于绘图通常由许多笔划组成,因此文档存储用户绘制的所有笔划的列表。下图显示了在Scribble文档视图中绘制的单个笔划。
成员变量 m_strokeList
用于存储笔划列表和成员函数以管理笔划列表。
1.首先,自定义CSribbleDoc(在原有的基础上自己添加代码)
添加1:
(https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa733759(v=vs.60) )
成员变量:
m_nPenWidth
和m_penCur
成员函数:InitDocument()和NewStroke()
另外,还添加了成员:m_strokeList(变量)、GetCurrentPen( )函数。
先声明上面的这几个成员,(但是m_strokeList和 GetCurrentPen( )由于其特殊性,就直接在.h文件中定义了)
CTypedPtrList<CObList, CStroke*> m_strokeList;
/*CTypedPtrList类是个模板类,一般在软件开发中用到的时候比较多,它的作用就是类似一个链表,下面是这个类的原型:
template < class BASE_CLASS, class TYPE>
class CTypedPtrList : public BASE_CLASS
第一个参数说明是列表的基类,这里必须是个指针列表类(Coblist或者CPtrlist,自己觉得用其他的列表类也可以,只要是指针列表类);
第二个参数指在列表类中所存放的类型。
为什么不直接用Coblist或者CPtrlist呢?这是有原因的,因为CTypedPtrList类又进一步的封装了
CPtrlist,并且为消除了一些错误,还提供了一些类型的强制转化,所以说一般都是用CTypePtrList这个模板
类,你也不用担心这个类封装以后会变的速度慢,因为CTypedPtrList类封装以后里面的函数是内联的,
如果你想真正想掌握这个模板类还是需要看看他提供的成员函数,下面是它的成员函数:
CPen* GetCurrentPen( ) { return &m_penCur; };
然后,在定义这些成员:
/*1、对InitDocument()*/
m_nPenWidth = 2; // Default 2-pixel pen width
// Solid black pen
m_penCur.CreatePen( PS_SOLID, m_nPenWidth, RGB(0,0,0) );
【注】①CTypedPtrList的详解:https://www.cnblogs.com/VCdog/archive/2010/04/12/1709918.html;又因为CTypedPtrList类封装后里面的函数都是内联函数,所以它最好在.h(头文件)中完成定义。
inline函数(即内联函数)对编译器而言必须是可见的,以便能够在调用点展开该函数,与非inline函数不同的是,inline函数必须在调用该函数的每个文件中定义。当然,对于同一程序的不同文件,如果inline函数出现的话,其定义必须相同。 正因为如此,建议把inline函数的定义放到头文件中,在每个调用该inline函数的文件中包含该头文件。这种方法保证了每个inline函数只有一个定义,且程序员无需复制代码,并且不可能在程序的生命周期中引起无意的不匹配的事情。 ——摘自《C++ Primer》(第三版) |
②对于函数GetCurrentPen( ),是内联函数的解释:设备描述表(DC, Device Context)。DC是一个数据结构,当程序向GDI设备中绘图时,需要访问该设备的DC。MFC将GDI的DC封装在C++类中,包括CDC类和CDC派生类,这些类中的许多成员都是对本地GDI绘图函数进行简单封装而形成的内联函数。【CDC::GetCurrentPen,即GetCurrentPen( )是内联函数】
添加2(准确说是:覆盖基类函数):
(https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa748590(v=vs.60))
成员函数:OnOpenDocument()和DeleteContents()
此次,使用“类向导”在CScribbleDoc.h中声明两个成员函数:OnOpenDocument和DeleteContents。(https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa748590(v=vs.60))
然后,要在CScribbleDoc.cpp中为这两个函数定义初始化:(https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa232160%28v%3dvs.60%29)
2. 将AfxTempl.h添加在StdAfx.h
(https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa716362(v=vs.60))
在上一步当中,用到C++集合模板类中的:CTypedPtrList和。在接下来声明CStroke类时,使用的CArray。
其中,所有模板集合类都在头文件AfxTempl.h中定义。这个MFC提供的头文件在开发Scribble应用程序的过程中不会改变,因此将其添加到Scribble的预编译头StdAfx.h中。(StdAfx.h是由AppWizard创建的,用于保存要预编译的头文件列表。它由#include语句列表组成,后跟头文件的名称。)
3.创建CStroke类
在Scribble中,笔划由一系列点组成。当用户拖动鼠标进行绘制时,Scribble会收集点并将它们存储为当前笔划的一部分。从鼠标左键按下的时间点到从Scribble绘图的一个笔划中释放的时间点收集的点。下图显示了Scribble的数据结构。Scribble使用CPen类的对象进行绘制。
添加一个CStroke类
(在CScribbleDoc类中的 ScribbleDoc.h 添加),还要声明成员。
(1)声明CStroke类 https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa716352(v=vs.60)
首先在ScribbleDoc.h中声明。
然后,在该类中声明其成员: (2)为CStroke类的成员进行定义初始化:
https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa716400(v=vs.60)
/*1、为该类的两个构造函数惊醒定义*/
//空构造函数
CStroke::CStroke()
{
// This empty constructor should be used by
// the application framework for serialization only
}
在ScribbleDoc.h中声明受保护的第一个构造函数在CStroke对象序列化期间仅由应用程序框架使用。
它的参数列表和函数体是空的。
//另一个
CStroke::CStroke(UINT nPenWidth)
{
m_nPenWidth = nPenWidth;
}
第二个构造函数供公众使用,直接构造新的笔画对象。当它构造一个新的笔触对象时,
公共构造函数初始化笔的宽度。CStroke不声明它自己的析构函数 - 它依赖于CObject默认提供一个。
/*2、对DrawStroke()函数进行定义初始化*/
BOOL CStroke::DrawStroke( CDC* pDC )
{
CPen penStroke;
if( !penStroke.CreatePen(PS_SOLID, m_nPenWidth, RGB(0,0,0)))
return FALSE;
CPen* pOldPen = pDC->SelectObject( &penStroke );
pDC->MoveTo( m_pointArray[0] );
for( int i=1; i < m_pointArray.GetSize(); i++ )
{
pDC->LineTo( m_pointArray[i] );
}
pDC->SelectObject( pOldPen );
return TRUE;
}
此代码传递DrawStroke指向CDC类对象的指针,该对象封装了Windows设备上下文(DC)。
【注意】在使用MFC编写的程序中,所有图形调用都是通过类 CDC的设备上下文对象或其派生类之一进行的。
DrawStroke:
调用CDC成员函数 - SelectObject,MoveTo,LineTo - 通过指针选择图形设备接口(GDI)笔到设备上下文中并移动笔 和绘图。
构造一个新的CPen对象,并通过调用笔的CreatePen成员函数使用当前属性对其进行初始化。
注意 这种两阶段结构是典型的框架对象。
调用SelectObject以将笔选择到设备上下文中(将现有笔保存为pOldPen
)。
调用MoveTo将笔定位到第一个点。
迭代点数组。
调用设备上下文的LineTo成员函数将前一个点与下一个点连接起来。
通过重新安装旧笔,将设备上下文恢复为先前的状态。
????不应该在这里吧????????????????????????????????
为视图的OnDraw成员函数添加实现代码
使用WizardBar跳转到类的启动器OnDraw成员函数CScribbleView。
使用ASSERT_VALID(pDoc)以下代码替换行后的// TODO注释:
// The view delegates the drawing of individual strokes to
// CStroke::DrawStroke( ).
CTypedPtrList<CObList, CStroke*>& strokeList =
pDoc->m_strokeList;
POSITION pos = strokeList.GetHeadPosition( );
while (pos != NULL)
{
CStroke* pStroke = strokeList.GetNext(pos);
pStroke->DrawStroke( pDC );
}
视图使用指针迭代笔划列表,告诉每个笔划绘制自己。当OnDraw调用DrawStroke给定的笔画对象时,
它会传递它作为参数接收的设备上下文对象。(数据绘制本身只是一种可能的策略。)
要完成Scribble的绘图,还必须将DrawStroke成员函数定义添加到类中CStroke。
3. 利用 类向导 在CSribbledoc类中,添加虚函数: OnOpenDocument() 和 DeleteContents()https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa748590(v=vs.60)
step2:https://share.mindmanager.com/#publish/9M6FiNbEqNKD4k_4878bUAffJ9n1y2SIF3m8_B7n