⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chap8_3.htm

📁 VC++编程实例。非常详细
💻 HTM
📖 第 1 页 / 共 3 页
字号:
           
          <p>void FinishStroke();</p>
          <p>// Operations</p>
          <p>public:</p>
          
          <p>//绘制笔划</p>
           
          <p>BOOL DrawStroke(CDC* pDC);</p>
          <p>public:</p>
          <p>virtual void Serialize(CArchive&amp; ar);</p>
          
          <p>};</p>
          <p> 文档的初始化</p>
          <p> 文档的初始化在OnNewDocument()和OnOpenDocument()中完成。对于Draw程序来说,两者的初始化相同,因此设计一个InitDocument()函数用于文档初始化:</p>
          <b> 
          <p>void CDrawDoc::InitDocument()</p>
          <p>{</p>
          <p>m_nPenWidth=2;</p>
          <p>m_nPenCur.CreatePen(PS_SOLID,m_nPenWidth,RGB(0,0,0));</p>
          
          <p>//缺省文档大小设置为800X900个逻辑单位</p>
           
          <p>m_sizeDoc = CSize(800,900);</p>
          <p>}</p>
          </b>
          <p> InitDocument()函数将笔的宽度初值设为2,然后创建一个画笔对象。该对象在以后绘图是要用到。最后将文档尺寸大小设置为800X900个逻辑单位。</p>
          <p> 然后在OnNewDocument()和OnOpenDocument()中调用它:</p>
           
          <p>void CDrawDoc::OnNewDocument()</p>
          <p>{</p>
          <p>if (!CDocument::OnNewDocument())</p>
          <p>return FALSE;</p>
          <p>// TODO: add reinitialization code here</p>
          <p>// (SDI documents will reuse this document)</p>
          
          <p><b> </b></p>
          <b> 
          <p>InitDocument();</p>
          </b> 
          <p>return TRUE;</p>
          <p>}</p>
          
          <p>AppWizard并没有生成OnOpenDocument()的代码,因此要用ClassWizard来生成OnOpenDocument()的框架。生成框架后,在其中加入代码:</p>
           
          <p>BOOL CDrawDoc::OnOpenDocument(LPCTSTR lpszPathName) </p>
          <p>{</p>
          <p>if (!CDocument::OnOpenDocument(lpszPathName))</p>
          <p>return FALSE;</p>
          
          <p> </p>
           
          <p>// TODO: Add your specialized creation code here</p>
          
          <p><b> </b></p>
          <b> 
          <p>InitDocument();</p>
          </b> 
          <p>return TRUE;</p>
          
          <p>}</p>
          <p> 文档的清理</p>
          <p> 在关闭文档的最后一个子窗口时,框架要求文档清理数据。文档清理在文档类的DeleteContents()中完成。同样需要用ClassWizard生成DeleteContents的框架。</p>
           
          <p>void CDrawDoc::DeleteContents() </p>
          <p>{</p>
          <p>// TODO: Add your specialized code here and/or call the base class</p>
          
          <p><b> </b></p>
          <b> 
          <p>while (!m_strokeList.IsEmpty())</p>
          <p>{</p>
          <p>delete m_strokeList.RemoveHead();</p>
          <p>}</p>
          </b> 
          <p>CDocument::DeleteContents();</p>
          <p>}</p>
          
          <p>DeleteContents()从头到尾遍里链表中的所有对象指针,并通过指针删除对象,然后用RemoveHead()删除该指针。</p>
          <p> 文档的串行化</p>
          <p> 现在设计文档的Serialize函数,实现文档数据的保存和载入:</p>
           
          <p>void CDrawDoc::Serialize(CArchive&amp; ar)</p>
          <p>{</p>
          <p>if (ar.IsStoring())</p>
          <p>{</p>
          
          <p><b> </b></p>
          <b> 
          <p>ar &lt;&lt; m_sizeDoc;</p>
          </b> 
          <p>}</p>
          <p>else</p>
          <p>{</p>
          
          <p><b> </b></p>
          <b> 
          <p>ar &gt;&gt; m_sizeDoc;</p>
          </b> 
          <p>}</p>
          
          <p><b> </b></p>
          <b> 
          <p>m_strokeList.Serialize(ar);</p>
          </b> 
          <p>}</p>
          
          <p>文档的Serialize()函数首先分别保存和载入文档大小,然后调用m_strokeList的Serialize()方法。m_strokeList.Serialize()又会自动调用存放在m_strokeList中的每一个元素CStroke的串行化方法CStroke.Serialize()最终实现文档的串行化即文档所包含的对象的存储和载入。</p>
          <p> 在DrawDoc.cpp的末尾加上CStroke::Serialize()函数的定义:</p>
          <b> 
          <p>void CStroke::Serialize(CArchive&amp; ar)</p>
          <p>{</p>
          <p>if (ar.IsStoring())</p>
          <p>{</p>
          <p>ar &lt;&lt; m_rectBounding;</p>
          <p>ar &lt;&lt; (WORD)m_nPenWidth;</p>
          <p>m_pointArray.Serialize(ar);</p>
          <p>}</p>
          <p>else</p>
          <p>{</p>
          <p>ar &gt;&gt; m_rectBounding;</p>
          <p>WORD w;</p>
          <p>ar &gt;&gt; w;</p>
          <p>m_nPenWidth = w;</p>
          <p>m_pointArray.Serialize(ar);</p>
          <p>}</p>
          <p>}</p>
          </b> 
          <p>CStroke的Serialize()依次保存(载入)笔划的矩形边界、线宽度以及点数组。注意m_nPenWidth是UINT类型的,&gt;&gt;和&lt;&lt;操作符并不支持UINT类型但却支持WORD,因此要作UINT和DWORD之间的类型转换。点数组的串行化通过调用数组的每个CPoint类元素的Serialize()完成,CPoint类是MFC类,它本身支持串行化。</p>
          <p> 8.3.3 设计绘图程序的视图类</p>
          <p>视图类数据成员</p>
          <p> 现在着手设计绘图程序的视图类。首先,需要在视图中增加两个数据成员:</p>
           
          <p>class CDrawView : public CScrollView</p>
          <p>{</p>
          <p>protected: // create from serialization only</p>
          <p>CDrawView();</p>
          <p>DECLARE_DYNCREATE(CDrawView)</p>
          <p>// Attributes</p>
          <p>public:</p>
          <p>CDrawDoc* GetDocument();</p>
          
          <p><b> </b></p>
          <b> 
          <p>protected:</p>
          <p>CStroke* m_pStrokeCur; // the stroke in progress</p>
          <p>CPoint m_ptPrev; // the last mouse pt in the stroke in progress</p>
          </b>
          <p> // 其它数据成员和成员函数......</p>
          <p> };</p>
          <p> m_pStrokeCur代表正在画的那一个笔划。m_ptPrev保存鼠标上次移动位置。画图时,LineTo从这个点到当前鼠标位置画一条直线。</p>
          <p> 视图初始化</p>
          <p> 接下去,要初始化视图。由于是卷滚视图,因此要在OnInitialUpdate()中设置卷滚范围。在用户选择File-&gt;New菜单或File-&gt;Open菜单时,框架调用OnInitialUpdate函数。</p>
           
          <p>void CDrawView::OnInitialUpdate()</p>
          <p>{</p>
          
          <p><b> </b></p>
          <b> 
          <p>SetScrollSizes(MM_LOENGLISH, GetDocument()-&gt;GetDocSize());</p>
          </b> 
          <p>CScrollView::OnInitialUpdate();</p>
          <p>}</p>
          
          <p>注意我们这里将映射模式设置为MM_LOENGLISH,MM_LOENGLISH以0.01英寸为逻辑单位,y轴方向向上递增,同MM_TEXT的y轴递增方向相反。</p>
          <p> 视图绘制</p>
          <p> 在CDrawView::OnDraw()内完成视图绘制工作。在以前的文档视结构程序中,在需要绘图的时侯都是绘制整个窗口。如果窗口只有很小的一部分被覆盖,是否可以只绘制那些需要重画的部分?</p>
          <p> 回答是肯定的,而且大部分程序都这么做了。</p>
          <p> 比如,象下图这种情况:</p>
          <p>  </p>
          <p> 图8-5 
            窗口的重绘</p>
          <p> 当窗口2从窗口1上移开后,只需要重画阴影线所包围的区域就够了。</p>
          <p align="JUSTIFY"> 当Windows通知窗口要重绘用户区时,并非整个用户区都需要重绘,需要重绘的区域称为“无效矩形区”,如上图中的阴影区域。用户区中出现一个无效矩形提示Windows在应用程序队列中放置WM_PAINT消息。由于WM_PAINT消息优先级最低,可调用UpdateWindows直接立即向窗口发送WM_PAINT消息,从而立即重绘。无效矩形区限制程序只能在该区域中绘图,越界的绘图将被裁剪掉。下面三个函数与无效矩形有关:</p>
          <p> InvalidateRect 产生一个无效矩形,并生成WM_PAINT消息</p>
          <p> ValidateRect 使无效矩形区有效</p>
          <p> GetUpdateRect 获得无效矩形坐标(逻辑)</p>
          <p> Windows为每个窗口保留一个PAINTSTRUCT结构,其中包含无效矩形区域的坐标值。</p>
          <p> 要想在自己的程序高效绘图、只绘制无效矩形,首先需要重载视图的OnUpdate成员函数。</p>
          <blockquote> 
            <blockquote>  
              <p><b>virtual</b> <b>void</b> <b>CView::OnUpdate(</b> <b>CView*</b> 
                <i>pSender</i><b>,</b> <b>LPARAM</b> <i>lHint</i><b>,</b> <b>CObject*</b> 
                <i>pHint</i> <b>);</b></p>
            </blockquote>
          </blockquote>
          <p align="JUSTIFY"> 当调用文档的UpdateAllViews时,框架会自动调用OnUpdate函数,也可在视图类中直接调用该函数。OnUpdate函数一般是这样处理的:访问文档,读取文档的数据,然后对视图的数据成员或控制进行更新,以反映文档的改动。可以用OnUpdate函数使视图的某部分无效。以便触发视的OnDraw,利用文档数据重绘窗口。缺省的OnUpdate使窗口整个客户区都无效,在重新设计时,要利用提示信息lHint和pHint定义一个较小的无效矩形。修改后的OnUpdate成员函数如清单8.5。</p>
          
          <p> <b>清单</b><b>8.5 
            修改后的OnUpdate成员函数</b></p>
           
          <p>void CDrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
          </p>
          <p>{</p>
          <p>// TODO: Add your specialized code here and/or call the base class</p>
          <p>// The document has informed this view that some data has changed.</p>
          
          <p><b> </b></p>
          <b> 
          <p>if (pHint != NULL)</p>
          <p>{</p>
          <p>if (pHint-&gt;IsKindOf(RUNTIME_CLASS(CStroke)))</p>
          <p>{</p>
          <p>// The hint is that a stroke as been added (or changed).</p>
          <p>// So, invalidate its rectangle.</p>
          <p>CStroke* pStroke = (CStroke*)pHint;</p>
          <p>CClientDC dc(this);</p>
          <p>OnPrepareDC(&amp;dc);</p>
          <p>CRect rectInvalid = pStroke-&gt;GetBoundingRect();</p>
          <p>dc.LPtoDP(&amp;rectInvalid);</p>
          <p>InvalidateRect(&amp;rectInvalid);</p>
          <p>return;</p>
          <p>}</p>
          </b> 
          <p>}</p>
          <p>// We can't interpret the hint, so assume that anything might</p>
          <p>// have been updated.</p>
          <p>Invalidate(TRUE);</p>
          <p>return;</p>
          <p>}</p>
          
          <p align="JUSTIFY">这里,传给pHint指针的内容是指向需要绘制的笔画对象的指针。采用强制类型转换将它转换为笔划指针,然后取得包围该笔划的最小矩形。OnPrepareDC用于调整视图坐标原点。由于InvalidateRect需要设备坐标,因此调用LPToDP(&amp;rectInvalid)将逻辑坐标转换为设备坐标。最后,调用InvalidateRect是窗口部分区域“无效”,也就是视图在收到WM_PAINT消息后需要重绘这一区域。</p>
          <p> InvalidateRect函数原型为:</p>
           
          <blockquote> 
            <blockquote> 
              <p>void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE );</p>
            </blockquote>
          </blockquote>
          
          <p>第一个参数是指向要重绘的矩形的指针,第二个参数告诉视图是否要删除区域内的背景。</p>
          <p> 这样,当需要重画某一笔划时,只需要重画包围笔划的最小矩形部分就可以了,其他部分就不再重绘。这也是为什么在笔划对象中提供最小矩形信息的原因。</p>
          <p> 如果pHint为空,则表明是一般的重绘,此时需要重绘整个客户区。</p>
          <p> 现在,在OnDraw中,根据无效矩形绘制图形,而不是重绘全部笔划,见清单8.6。</p>
          
          <p> <b>清单</b><b>8.6 
            根据无效矩形绘制图形的OnDraw成员函数</b></p>
           

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -