zoukankan      html  css  js  c++  java
  • Visual C++ 2008入门经典 第十六章 创建文档和改进视图 简单

    /*
    第十六章 创建文档和改进视图
    
    1 MFC集合及其作用
    2 如何使用MFC List集合和STL/CLR向量容器存储曲线的点数据
    3 如何使用MFC List集合和STL/CLR列表容器存储草图数据
    4 如何在Sketcher的MFC和CLR版本中实现草图的绘制
    5 如何在MFC Sketcher中实现视图滚动
    6 如何在鼠标指针处创建上下文菜单
    7 如何醒目显示距离鼠标指针最近的元素,以使在移动和删除元素时向用户提供反馈
    8 在移动和删除元素时如何对鼠标进行编程
    
    16.1 MFC集合类
    串行化是将对像传关到文件和从文件中传出对像的过程,第18章中将对此进行介绍,其中将实现在文件中存储草图的功能,遗憾的是,MFC提供的串行化功能不支持STL容器
    对于容器类,MFC使用与STL不同的术语,MFC提供了一组提供与STL容器类似的功能的集合类--集合是按钮特定方式组织的任意数量数据项的聚集体,相比于STL容器,MFC集合类提供的主要优点是,只要存储的对像是可串行化的,则MFC集合类对像就是可串行化的
    
    
    16.1.1 集合的类型
    MFC提供了大量用于管理数据的集合类,虽然在Sketcher中只用到其中的几个集合类,但是有必要了解可用集合的类型,MFC支持3种集合,其区别在于组织数据项的方式,组织集合的方式被称为集合的形状,组织或者形状的3种类型如下
    数组  这种上下文中的数组和C++语言中的数据完全一样,它是元素的一种有序排列,等同于STL序列容器
    列表  这是一种双向链表,类似于STL列表容器
    映射  映射是数据项(键/对像对)的一种无序集合,等同于STL映射
    
    在实现每一种类型的集合时,MFC集合类提共了两种方法,一种方法是以类模板的使用为基础,可以对集合的数据进行类型安全的处理,类型安全的处理意味着将检查传递到集合类中一个函数成员的数据,以确保数据是该函数可以处理的类型
    
    另一种方法将使用大量集合类(而不是模板),但是这些集合类不执行数据检查,如果要使用集合类成为类型安全的集合类,则必须加入一些代码,在windows环境下,老版本的Visual C++可以使用后面的这些类,但是不能使用模板集合类,因为基于模板的集合类最大限度地避免了应用程序中的错误,所以本章将主要讨论这些类
    
    16.1.2 类型安全的集合类
    基于模板的类型安全的集合类支持任何本地C++类型对象的集合,并且支持指向任何本地C++类型对像的指针的集合,模板类CArray, CList和CMap支持对像集合,模板类CTypedPtrArray, CTypedPtrList和CTypedPtrMap支持指向对像的指针的集合,
    一种模板类用于存储对像,另一种模板类用于存储指向对像的指针,所以这两种集合都需要掌握
    
    16.1.3 对象集合
    定义对像集合的模板类全部派生于MFC类的CObject,之所以这样定义模板类,是为了让它们继承CObject类的属性,它们包括针对对象进行的统称为串行化的文件输入和输出操作
    
    这些模板类可以存储和管理各种对像,包括C++的所有基本数据类型,以及您或其他人定义的类或结构,因为这些类可以存储对像,所以在添加一个元素到列表,数组或映射中时,类模板对像需要复制这个对像,因此。需要在这些集合中存储的类类型必须有一个复制构造函数,复制构造函数用于创建需要存储在集合中的对像的副本
    
    1 CArray模板类
    可以使用这种模板在数组中存储任何类型的对像,并且在必要时可以使数组自动增长,以容纳更多的元素
    
    CArray<CPoint, CPoint&> PointArray;
    这个语句定义了存储CPoint对像的数组集合类对像PointArray,在调用这个对像的成员函数时,参数是一个引用,所以需要编写下列语句添加CPoint对像
    PointArray.Add(aPoint);
    能数aPoint将作为引用传递
    
    声明了数组集合以后,在使用所需元素原初始数量前,必须调用SetSize()成员函数,调整这个数量,如果不这样做,程序仍然能够运行,但是元素的初始分配量和以后的增加量将非常小,从而使操作效率低下,并且需要频繁地给这个数组重新分配内存,在指定元素的初始数量时,应当以预计的数组的典型大小以及这个大
    小的变化情况为根据
    要检索元素的内容,可以使用GetAt()函数
    aPoint = PointArray.GetAt(2);
    aPoint = PointArray[2];
    
    如果数组类型不是const,可以如下设置
    PointArray.SetAt(3, NewPoint);
    PointArray[3] = NewPoint;
    扩展数组可用Add() 和Append()它可以把一串元素添加到这个数组的末尾
    
    2 辅肋函数
    
    
    3 CList模板类
    CList<ObjectType, ObjectType&> aList;
    二个参数,要存储的对像的类型,以及在函数参数中指定对像时采用的方式
    
    在Sketcher程序中可以使用列表来管理曲线,利用下列语句可以声明一个列表集合,用于存储指定曲线对像的点
    CList<CPoint, CPoint&> PointList;
    这个语句声明一个称为PointList的列表,它存储CPoint对象,该对象由引用传递到这个类的函数中,
    
    添加元素到列表中
    可以使用AddHead()和AddTail()成员函数在列表的开始或末尾加对像
    PointList.AddTail(aPoint);
    添加了新元素以后,这个列表的大小将自动增加
    
    AddHead()和AddTail()函数都将返顺一个POSITION类型的值,它指定插入对像在列表中的位置
    
    利用GetNext()函数,可以使用POSITION类型的值检索这个列表中给定位置的对像,注意不能对POSITION类型的值执行算术运算--只能通过这个列表对像的成员函数修改位置值
    不能把位置值设置为特定的数字值,只能通过这个列表对像的成员函数设置POSITION变量
    
    在指定位置插入值
    PointList.InsertBefore(aPosition, ThePoint);
    函数InsertBeform()也返回这个新对像的位置,要在给定位置处的对像之后插入元素,应当使用函数InsertAfter(),在包含可显示几何元素的列表中,经常使用这些函数
    
    如果需要给列表中的一个现有元素设置一个特定值,那么只要知道这个对像的位置值,就可以使用函数SetAt()
    PointList.SetAt(aPosition, aPoint);
    
    循环访问列表
    如果想获取列表起点或终点的POSITION值,可以使用该类提供的成员函数GetHeadPosition()和GetTailPosition(),从列表头的POSITION值开始,通过调用GetNext()可以循环访问整个列表,直到位置值成为NULL
    
    CPoint CurrentPoint(0,0);
    POSITION aPosition = PointList.GetHeadPosition();
    while(aPosition)
    {
         CurrentPoint = PointList.GetNext(aPosition);
    }
    
    使用另一个成员函数GetPrev()可以向后处理列表,这个函数将检索当前对像,然后递减位置标识符,当然,这种情况下,需要首先调用函数据GetTailPosition()
    
    知道了列表中一个对像的位置值以后,可以利用成员函数GetAt()检索这个对像,这时要把该位置值指定为参数,并且这个对像将被返回,无效的位置值将产生错误
    
    搜索列表
    使用成员函数Find()可以找到存储在列表中的一个元素的位置
    POSITION aPosition = PointList.Find(ThePoint);
    
    如果想在列表中搜索一个与另一个对像相等的对像,则必须实现自己的CompareElements()函数的版本,以执行正确的比较运算,
    template<class TYPE, class ARG_TYPE> BOOL CompareElements(const TYPE* pElement1, const ARG_TYPE* pElement2);
    
    其中pElement1和pElement2是指向要比较的对像的指针,对于PointList集合类对像来说,
    BOOL CompareElements(CPoint* pPoint1, CPoint* pPoint2);
    在比较CPoint对象时,应当按照如下方法实现这个原型
    BOOL CompareElements(CPoint* pPoint1, CPoint* pPoint2)
    {
         return *pPoint1 == *pPoint2;
    }
    
    函数FindIndex()把int型的索引值作为参数,并且为列表中位于索引位置的对像返回一个POSITION类型的值,如果使用索引值,很可能需要知道列表中有多少对像,GetCount()函数可以返顺这个数量
    int ObjectCount = PointList.GetCount();
    
    从列表中删除对像
    使用成员函数RemoveHead()可以删除列表中的第一个元素,这个函数将返回成为新表头的对像,要删除最后一个对像,可以使用函数RemoveTail(),这两种函数都要求列表中至少有一个对像,所以应当首先使用函数IsEmpty()验证列表是否是空的
    
    if(!PointList.IsEmpty())
        PointList.RemoveHead();
    删除指定的对像
    PointList.RemoveAt(aPosition);
    删除列表的整个内容
    PointList.RemoveAll();
    这个函数还将释放针对这个列表中的元素分配的内存
    
    
    列表的辅助函数
    template< class TYPE> void ConstructElements(TYPE* pElements, int nCount)
    template< class TYPE> void DestructElements(TYPE* pElements, int nCount)
    
    void ConstructElements(CPoint* pPoint, int PointCount);
    void DestructElements(CPoint* pPoint, int PointCount);
    注意此处的参数是指针,前面提到过PointList成员函数的参数是引用,但是这不适用于辅助函数,这两个函数的参数都是相同的:
    第一个参数是指向CPoint对像的数组的指针,第二个参数是这个数组中对像的数量
    
    4 CMap模板类
    映射存储对像和键的组合,键用于确定要分配给映射的内存块中的什么地方存储对像,因此,只要键是唯一的,它就可以直接定位存储的对像,把键转换成可用于计算映射中一个项地址的整数的过程称为散列
    
    在声明一个映射时必须使用四个参数
    CMap<LONG, LONG&, CPoint, CPoint&> PointMap;
    前两个参数指定键类型以及如何把这个键作为参数传递,通常,键将作为引用传递,后两个参数指定对像类型以及如何把对像作为参数传递,
    
    在检索对刓地,应当使所示的LookUp()函数,这个函数将检索对应于指定键的对像,如果找到了该对像,这个函数将返回true,否则返回falsh,也可能使用POSITION类型的变量循环访问映射中的所有对像,尽管检索对像的顺序与它们添加到映射中的顺序没有关系,这是因为对像在映射中的存储位置是由散列值确定的,而不是由它们输入的顺序确定的
    
    CMap模板类使用的辅助函数
    HashKey()
    template<class ARG_KEY>
    UINT HashKey(ARG_KEY key);
    
    HashVlaue = Key % 101;
    
    
    16.1.4 类型化指针集合
    类型化指针集合类模板存储指向对像的指针,而不存储对像本身,这是类模板和刚才讨论的模板类之间的主要区别
    
    1 CTypedPtrList模板类
    声明
    CTypedPtrList<BaseClass, Type*> ListName;
    第一个叁数指定一个基类,它必须是MFC中定义的两种指针列表类之一
    或者是CObList,或者是CPtrList。
    选择哪一种基类取决于对像是如何定义的,
    使用CObList类创建的列表支持指向派生类CObject的对像的指针,
    而CPtrList则支持void* 指针的列表,
    
    模板的第二个参数在列表中的指针的类型
    CTypePtrList<COblist, CElement*> m_ElementList;
    
    
    2 CTypePtrList操作
    在基于CTypePtrList的类中提供的函数类似于Clist支持的函数,只不过所有操作都是针对指向对像的指针,而不是针对对像. 所以需要把这些函数列出来。
    它们分成两组,在CTypePtrList中定义的函数以及派生于基类的函数--这时的基类是CObList
    
    GetHead() 返回列表表头处的指针,在调用这个函数之前,应该使用IsEmpty()验证列表是否为空的
    
    GetTail() 返回列表末尾处的指针,在调用这个函数之前,应当使用IsEmepty()验证列表是否为空
    
    RemoveHead() 删除列表中的第一个指针.在调用这个函数之前,应当使用IsEmepty()验证列表是否为空
    
    RemoveTail()  删除列表中的最后一个指针, 在调用这个函数之前,应当使用IsEmepty()验证列表是否为空
    
    GetNext() 返回由POSITION类型变量表示的位置处的指针,这个指针作为引用参数传递,然后对这个变量进行更新,以指示列表中的下一个元素。
    到达列表终点时,这个位置变量将会被设置为NULL,可以使用这个函数对列表中的所有指针进行向前迭代
    
    GetPrev() 返回由POSITION类型变量表示的位置处的指针,这个指针作为引用参数传递,然后对这个变量进行更新,以指示列表中的下一个元素。
    到达列表终点时,这个位置变量将会被设置为NULL,可以使用这个函数对列表中的所有指针进行向后迭代
    
    
    GetAt()  返回由POSITION类型变量表示的位置处的指针,这个指针作为不能修改的引用参数传递,由于这个函数返回引用,因此只要列表不被定义为const,就可以在赋值运算符的左边使用这个函数,修改列表中的项
    
    
    CTypePtrList中派生于CObList的函数列表
    AddHead() 把作为参数传递的指针添加到列表的表头处,并返回一个对应于这个新元素的POSITION类型的值,这个函数的另一个版本可以把另一个列表添加到列表的表头处
    
    AddTail()  把作为参数传递的指针添加到列表的表尾处,并返回一个对应于这个新元素的POSITION类型的值,这个函数的另一个版本可以把另一个列表添加到列表的表尾处
    
    RemoveAll() 删除列表中的所有元素,这个函数并没删除元素所指的对像,这需要亲自动手操作
    
    GetHeadPosition()  返回位于列表头处的元素的位置
    
    GetTailPosition()  返回位于列表尾部的元素的位置
    
    SetAt()  将第二个参数指定的指针存储在列表中由第一个参数定义的位置,无效的位置值将产生错误
    
    RemoveAt() 删除列表中由POSITION类型参数指定的位置处的指针,无效的位置值将产生错误
    
    InsertBefore() 在第一个参数指定的位置前插入由第二个参数指定一个新指针,这个新元素的位置将被返回
    
    InsertAfter() 在第一个参数指定的位置后面插入由第二个参数指定的一个新指针,这个新元素的位置将被返回
    
    Find() 在列表中搜索与指定为参数的指针相同的指针,如果找到这个指针,它的位置将被返回,否则返回NULL
    
    FindeIndex() 返回列表中由基于0的整数索引参数指定的指针的位置
    
    GetCount() 返回列表中元素的数量
    
    IsEmpty() 如果列表中没有元素,则返回TRUE, 否则返回FALSH
    
    
    
    16.2 使用CList模板类
    在定义曲线对像时可以使用CList集合模板,由线由两个以上的点定义,所以处理这些点的一种好方法是把它们存储在一个列表中,首先需要把CList集合类对像定义为CCurve类的成员,存储这些点时将使用这个集合
    Clist<YourObjectType, FunctionArgTtype> ClassName;
    第一个参数列表中存储对像的类型
    第二个参数指定要使用的叁数类型,当引用一个对像时,在CList集合类的函数成员中将使用该参数类型
    
    
    
    16.2.1 绘帛曲线
    绘制曲线不同于绘制直线或圆,对于直线或圆来说,在按下鼠标左键后移动鼠标指针时,将创建一系列不同的直线或圆无素,它们共享一个公共参考点-即最初按下鼠标左键时的那个点,在绘制曲线时情况却不是这样
    
    需要考虑的问题是如何计算封闭矩形,这时需要从所有定义点中找出一对x和y值最小的点,确定矩形的左上角,然后找出一对x和y值的最大的点,确定矩形的右上角,这需要检查列表中的所有点,
    
    
    16.2.3 实现CCurve类
    
    
    16.2.4 练习使用CCurve类
    
    
    16.3 创建草图文档
    使用CTypedPtrList模板
    可以声明一个CTypedPtrList模板,将指向形状类实例的指针作为CElement指针存储起来,这时只需要在CSketcherDoc类定义中把列表声明作为新成员添加进去
    
    1 实现文档析构函数
    
    
    2 绘制文档
    可以使用列表成为public列表,但是由于这将公开列有对像中的所有函数成员,因此将破坏维护文档类中受保护成员的目的
    可以添加一个成员函数,返回指向列表指针,但是这实际上将使列表成为public列表,而且在访问列表时将产生系统开销
    可以添加一个public函数到文档中,为每个元素调用Draw()成员,然后在视图中从OnDraw()函数调用public成员,因为这能够得到需要的结果,并且仍然能够维护列表的保密性,所以不失为一个好方法,唯一不中的是函数需要访问设备环境,而事实上这是视图的应用范围
    
    可以使OnDraw()函数成为CSketcherDoc的友元,但是这将公开类中的所有成员,这是不希望看到的,特别是对于复杂的类来说
    
    可以添加一个函数,提供第一个列表元素POSITION值,然后再添加一个循环访问列表元素的成员,虽然这不会公开这个列表,但是要使用元素指针
    
    3 添加元素到文档中
    在程序中获得工作文档时需要做的最后一件事就是添加代码到CSketcherView类的OnLButtonUp()处理程序中,
    
    4 练习文档
    这个程序仍然有一些可以解决的局限性,例如:利用程序中的Window | New Window 菜单项,可以打开另一个视图窗口,这种能力内置在MDI应用程序中,它将为现有文档而非新文档打开一个新视图,但是,在一个窗口中绘图时,在另一个客口中不会给制这些元素,元素永远不会出现在绘制它们的窗口以外的窗口中,除非它们所占用的区域因其他原因需要重画
    
    只能在可见的客户区中绘图,最好能够滚动视图,在一个比较大的区域上绘图,
    
    不能删除元素,所以如果出现了错误,要么是忍受,要么是从一个新文档开始,这些都是严重的问题,能导致程序毫无用处,
    
    16.4 改进视图
    可以设法解决的第一个问题是更新绘制元素时显示的所有文档窗口,在绘制一个元素时,由于只有它所在的视图知道这个新元素,所以就不会出现问题,每个视图的行为都与其他视图无关,它们之间没有任何通信,
    
    对于将一个元素添加到文档中的视图来说,需要对其进行整理,使其他视图知道这个元素,从而采取适当的操作
    
    16.4.1 更新多个视图
    文档类包含的函数UpdateAllViews()可以很方便地解决这个问题,这个函数实际上为文档提供了一种将消息发送到其所有视图的方法,每次将新元素添加到文档中时,只需要从CSketcherView类的OnLButtonUp()函数调用UpdateAllView()即可
    
    
    void UpdateAllView(CView* pSender, LPARAM IHint=OL, CObject* pHint = NULL);
    第一个参数: 该叁数是指向当前视图的指针,这将取消对当前视图的OnUpdate()函数的调用
    第二个叁数: LPARAM是一个32位的windows类型,可用于传递有关客户区中将要更新的区域的信息
    第三个参数: 这个参数是指向一个对像的指针,该对像可以提供有关客户区中将要更新区域的信息
    
    加入这些新的修改以后,如果构建和执行Sketcher程序,应当发现所有视图都已被更新,它们反映了文档的内容
    ============我这里并没有成功=============
    
    
    16.4.2 滚动视图
    添加滚动功能到视图中乍一看相当容易,但实际上情况比较复杂,第一步是把CSketcherView类的基类从CView修改为CScrollView,这个新基类具有内置的滚动功能,所以可以把CSketcherView类的定义修改为
    
    视图类的这个新版本必须知道一些有关所绘制区域的信息,如大小以及使用滚动器时视图滚动的距离,这些信息都必须在第一次绘制视图时提供,把提供这些信息的代码放到OnInitialUpdate()函数中
    
    通过调用从CScrollView类继承的函数SetScrollSizes()可能提供需要的信息
    
    void SetScrollSizes(int MapMode, SIZE Total, const SIZE& Page=sizeDefault, const SIZE& Line=sizeDefault);
    
    int MapMode
    MM_Text MM_TWIPS
    MM_LOENGLISH MM_HEINGLISH
    MM_LOMETRIC  MM_HIMETRIC
    
    SIZE Total
    这个参数是整个绘图区域的大小,可以把它定义为:CSize Total(cx, cy);
    基中cx是逻辑单位的水平范围,cy是逻辑单位的垂直范围
    
    const SIZE& Page=sizeDefault
    这个参数定义滚动页面的水平(cx)和垂直(cy)距离,可以把它定义为:CSizePage(cx,cy);这个叁数的默认值是整个区域的1/10
    
    const SIZE& Line=sizeDefault
    这个参数定义滚动行的水平(cx)和垂直(cy)距离,可以把它定义为:CSizeLine(cx, cy);这个参数默认值是整个区域的1/10
    
    1 逻辑坐标和客户坐标
    
    2 处理客户坐标
    考虑如何纠正这个问题,可能必须处理两个方面:
    1 在使用鼠标消息创建元素之前,需要将客户坐标转换为逻辑坐标
    2 如果想在调用InvalidateRect()时使用在逻辑坐标中创建的边界矩形,则需要将它转换为客户坐标
    
    
    
    16.4.3 使用MM_LOENGLISH映射模式
    这种映射模式规定的逻辑单位是0.01英寸,它还保证绘图尺寸在不同分辨率的显示器上都是一致的,从用户的观点一,这使应用程序更加符合要求
    
    
    16.5 删除和移动形状
    能够删除形状是绘图程序的一个基本要求,与此有关的一个问题是如何选择要删除的元素,当然,决定了如何选择元素以后,在移动元素时同样要考虑选择元素的问题,所以可以把移动和删除元素看作是相关的问题,但是首先要考虑如何将移动和删除操作加入程序
    
    
    16.6实现上下文菜单
    
    16.6.1 关联菜单和类
    TrackPopupMenu()函数的第一个参数由两个进行"或"运算的标志组成,第一个标志指定如何放置弹出式菜单
    TPM_CENTERALIGN  相对于作为该函数的第二个叁数提供的x坐标,水平居中放置弹出菜单
    TPM_LEFTALIGN  使弹出式菜单的左边界与作为该函数的第二个参数提供的x坐标对齐
    TPM_RIGHTALIGN  使弹出式菜单的右边界与作为该函数的第二个参数提供的x坐标对齐
    
    第二个标志指定鼠标按钮
    TPM_LEFTMOUSEBUTTON 指定弹出式菜单跟踪鼠标左键
    TMP_RIGHTMOUSEBUTTON 指定弹出式菜单跟踪鼠标右键
    
    创建这个处理程序函数,遵循相同的步骤,创建Delete菜单项的处理程序
    对第二个上下文菜单不需要进行任何处理,因为在文档类中已经为其中的菜单项编写了处理程序,这些处理程序将自动处理来自弹出式菜单项的消息
    
    16.6.2 选择上下文菜单
    目前,无论在视图中的什么地方单击鼠标左键,OnContextMenu()处理程序只显示第一个弹出式上下文菜单,这并非我们需要的结果,第一个上下文菜单专门应用于元素,而第二个上下文菜单适用于一般情况,我们希望鼠标指针下面有元素时显示第一个菜单,没有元素时显示第二个菜单
    
    1 标识选定的元素
    为了跟踪哪里个元素位于鼠标指针下面,可以添加代码到CSketcherView类的OnMouseMove()处理程序中,每当鼠标指针称动时,都将调用这个处理程序,所以我们必须做的就是添加代码
    
    
    2 练习弹出式菜单
    
    3 启用上下文菜单项
    UINT CheckMenuItem(UINT nIDCheckItem, UINT nCheck);
    这个函数将启用或禁用上下文菜单中的菜单项,第一个参数选择上下文菜单中启用或禁用的菜单 项
    第二个参数是两个标志的组合,一个标志确定第一个参数如何指定要启用的菜单项
    另一个标志指定是启用还是禁用这个菜单项,由于每个标志都是UNIT值中的一个位,因此使用按位或运算将它们组合在一起
    MF_BYPOSITION  第一个参数是一个索引,0指定第一个菜单项,1指定第二个菜单项,依此类推
    MF_BYCOMMAND 第一个参数是菜单ID
    
    COLORREF Color = GetDocument()->GetElementColor();
    利用条件运算符,可以使用Color变量选择适当的标志,然后将结果与MF_BYCOMMAND标志组合,即可获得CheckMenuItem()函数的第二个叁数,因此设置Blank菜单项的复选标记的语句是:
    menu.CheckMenuItem(ID_COLOR_BLACK, (BLACK==COlor ? MF_CHECKED : MF_UNCHECKED) | MF_BYCOMMAND);
    
    unsigned int ElementType == GetDocumnt()->GetElementType();
    menu.CheckMenuItem(ID_ELEMENT_LINE, (LINE==ElementType ? MF_CHECKED : MF_UNCHECKED) | 
    MF_BYCOMMAND);
    
    
    16.6.3 醒目显示元素
    理想情况下,用户希望在右击获得上下文菜单之前,知道哪个元素在鼠标指针下面,在删除一个元素时,用户想知道正在操作哪里个元素,同样,用户在使用另一个上下菜单时,如修改颜色,必须确定鼠标指针下面有没有元素,为了准确地显示鼠标指针下面的元素,在单击鼠标右键以前,用户必须以某种方式醒目的显示这个元素
    
    1 绘制醒目显示的元素
    我们还需要以醒目的方式实际地绘制应该醒目显示的元素,为此,必须在某个位置将m_pSelected指针传递给各个元素的绘制函数,唯一合适的位置是在视图的OnDraw()函数中
    
    2 练习使用醒目显示功能
    ===这个练习不成功哦,没有醒目显示元素啊
    ===成功了,又成功了
    
    16.6.64 处理菜单消息
    下一步是为前面添加的Move和Delete菜单项的处理程序提供函数体代码
    
    1 删除元素
    2 移动元素
    称动选中的元素稍微有一些棘手,因为选中的元素必须随同鼠标指针移动,所有我们必须在OnMouseMove()方法中添加实现这种行为的代码,
    
    修改WM_MOUSEMOVE处理程序
    仅当处于"移动"模式之中并且指针在移动时,才应当移动元素
    
    
    3 使元素自行移动
    放下元素
    ===删除可以正常工作,但移动不能正常工作
    
    
    16.7 处理被屏蔽的元素
    
    
    16.8 扩展CLR Sketcher
    
    16.8.1 坐标系统转换
    Graphics类包含可以移动,旋转和缩放整个绘图会标系统的函数,这是非常强大的功能
    
    TranslateTransform(float dx, float dy) 将坐标系统原点在x平移dx, 在y方向上平称dy
    RotateTransform(float angle)  将坐标系统沿着原点旋转angle角度,angle为正值表示从x轴向y轴旋转,换句话说就是顺时针旋转
    ScaleTransfrom(float scaleX, float scaleY) 通过乘以scaleX缩放X轴,通过乘以scaleY缩放Y轴
    ResetTransform() 复位Graphics对像的当前转换状态,从而不执行任何转换
    在元素绘制操作中将使用TranslateTransform()函数,为了绘制一个元素,您可以将坐标系统的原点平移到由Element类的继承成员position指定的位置,相对于原点(0,0)绘制元素,然后将坐标系统还原到其初始状态
    
    16.8.2 定义曲线
    代表曲线的类需要有可以存储定义曲线的任意数量点的成员,STL/CLR容器看起来是一种折中的解决方案,而vector<Point>容器则是存储曲线上点的最佳选择,
    
    16.8.3 定义草图类
    
    
    16.8.4 在Paint事件处理程序中绘制草图
    
    
    16.8.5 实现元素的醒目显示
    当鼠标指针位于某个元素的边界矩形中时,您需要醒目显示该元素,这类似于Sketcher的MFC版本
    
    
    16.8.6  创建上下文菜单
    1 实现元素的删除操作
    为了能够删除草图中的元素,首先必须在将要删除元素的Sketch类定义中添加一个公有函数
    
    2 实现Send-To-Back操作
    
    3 实现元素移动操作
    通过按下鼠标左键拖动来移动元素,这意味着移动操作需要由鼠标事件处理程序执行与通常情况不同的一组函数,因此移动必须模式化,这与Sketcher的MFC版本相同,可以通过枚举标识可能的模式,
    
    4 移动元素
    现在相对于给定位置绘制所有元素,因此可以通过如下方法称动元素:
    修改继承的position元素以反映新的位置,并且以类似的方式调整边界矩形的位置,可以在Element基类中添加处理操作的公有函数Move()
    
    
    1 如果需要使用MFC集合类来管理对像或指针,则最好的选择是使用基于模板的集合类之一,因为它们在大多数情况下都能提供类型安全的操作
    
    2 在设备环境中绘图时,坐标是以与设置的映射模式有关的逻辑单位表示的,连同Windows鼠标消息一起提供的窗口中的点是以客户坐标表示的,这两种坐标系统通常州不相同
    
    3 定义指针位置的坐标是相对于屏幕左上角,以像素为单位量度的屏幕坐标
    
    4 CDC类中包括在MFC应用程序中使用客户坐标和逻辑坐标相互转换的函数
    
    5 Windows通过给应用程序发送WM_PAINT消息,请求重画视图,其结果是导致受影响视图的OnDraw()成员被调用
    
    6 在MFC应用程序中,任何永久性的文档绘制工作都应该在视图类的OnDraw()成员中完成,这样可以确保在Windows请求时正确绘制窗口
    
    7 通过调用CDC类的RectVisible()成员来检查是否需要重画某个实体,可以使我们的OnDraw()函数更高效
    
    8 为了在修改文档内容之后更新多个视图,我们可以调用文档对像的UpdateAllViews()成员,这将导致各个视图OnUpdate()的成员被调用
    
    9 可以给UpdateAllViews()函数传递指出的视图中哪里个区域需要重画的信息,这样会使重画视图的过程更快地完成
    
    10 在MFC应用程序中,我们可以在指针位置显示一个上下文菜单来响应鼠标右击事件,该菜单是以通常的弹出菜单形式创建的
    
    11 在windows Forms应用程序中创建菜单条,上下文菜单和工具栏,方法是在Forms Designer窗口中将Toolbox中的适当组件拖动到窗体上,通过设置窗体的ContextMenuStrip属性,可以使上下文菜单在响应右击鼠标时显示
    
    12 在windows Forms应用程序中,通过组件的Properties窗口创建组件的事件处理程序,可以改变创建的事件处理程序函数的名称,方法是改变它的name属性的值
    
    13 实现菜单项的DropDownOpening事件处理程序可以显示下拉菜单之前对其进行修改
    
    
    
    */
    

      

  • 相关阅读:
    Java使用MyEclipse2017时的一些小细节
    Windows设置双ip访问虚拟机方法
    Python-函数的参数
    Python dict.set
    FTP错误代码列表
    使用批处理文件在FTP服务器 上传下载文件(转载)
    FreeRTOS学习笔记7-FreeRTOS 队列相关
    FreeRTOS学习笔记6-调度器开启和任务相关函数
    从github hexo 跑来 博客园
    Nodejs实战 —— 测试 Node 程序
  • 原文地址:https://www.cnblogs.com/xiangxiaodong/p/2809289.html
Copyright © 2011-2022 走看看