在了解GDI的一些基本知识之后,我们就可以着手编写绘图程序了。这个绘图程序可以让读者用鼠标器在窗口内任意涂写,并可以保存所画的内容。这里我们参考了Visual C++的例子Scribble,并作了一些修改和简化。
8.3.1 MDI应用程序框架
首先用AppWizard生成绘图程序的基本框架:
选择File->New,弹出New对话框,选择MFC AppWizard(exe),并指定项目文件名为Draw。
在MFC AppWizard-Step1对话框中指定框架类型为Multiple Document(多文档,这是缺省设置)。
Step2,3按缺省值。在MFC AppWizard Step 4 of 6对话框中,点“Advanced...”按钮,弹出Advanced Options对话框。在File Extension编辑框中指定文件名后缀为.drw,按OK关闭Advanced Options对话框。
Step5按缺省设置。在MFC AppWizard Step 6 of 6中,在应用程序所包含的类列表中选择CDrawView,并为其指定基类为CScrollView,因为绘图程序需要卷滚文档。现在点Finish按钮生成绘图所需的应用程序框架。
在往框架里添加代码实现绘图程序之前,先看看多文档框架与单文档框架的差别。
AppWizard为多文档框架创建了以下类:
CAboutDlg:“关于”对话框
CChildFrame:子框架窗口,用于容纳视图
CDrawApp:应用程序类
CDrawDoc:绘图程序视图类
CDrawView:绘图视图类
CMainFrame:主框架窗口,用来容纳子窗口,它是多文档应用程序的主窗口。
在生成的类上,MDI比SDI多了一个CChildFrame子框架窗口类,而且CMainFrame的职责也不同了。
另外,MDI和SDI在初始化应用程序实例上也有所不同。MDI应用程序InitInstance函数如清单8.2定义。
清单8.2 多文档程序的InitInstance成员函数定义
BOOL CDrawApp::InitInstance()
{
//一些初始化工作......
// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_DRAWTYPE,
RUNTIME_CLASS(CDrawDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CDrawView));
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
// Enable DDE Execute open
EnableShellOpen();
RegisterShellFileTypes(TRUE);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}
在注册文档模板时,首先创建一个CMultiDocTemplate类型(在SDI下是CSingleDocTemplate)的模板对象,然后用AddDocTemplate()把它加入到文档模板链表中去。
CMultiDocTemplate构造函数带四个参数,第一个参数是文档使用的资源ID定义。第二个是文档类型,第三个是子窗口类型,第四个是视图类型。
与SDI不同,由于MDI的主框架窗口并不直接与文档相对应,因此无法通过创建文档来创建主框架窗口,而需要自己去创建。
//定义一个主窗口类指针,并创建一个窗口的空的实例
CMainFrame* pMainFrame = new CMainFrame;
//从资源文件中载入菜单、图标等信息,并创建窗口
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
//将应用程序对象的主窗口指针数据成员设为当前创建的窗口
m_pMainWnd = pMainFrame;
8.3.2 设计绘图程序的文档类
Draw需要保存用户在屏幕上涂写的每一个笔划。一副画由许多笔划组成,可以把它看作是笔划组成的链表。每一个笔划可以看作一个对象,它由许多点组成。这样,我们可以把绘图文档的数据看作是笔划对象CStroke组成的链表。另外,我们还需要一些数据成员表示当前画图所使用的画笔和画笔的宽度。
修改后的文档类声明文件如清单8-1:
清单8.3文档类声明
// DrawDoc.h : interface of the CDrawDoc class
//
/////////////////////////////////////////////////////////////////////////////
#if !defined(AFX_DRAWDOC_H__143330AE_85BC_11D1_9304_444553540000__INCLUDED_)
#define AFX_DRAWDOC_H__143330AE_85BC_11D1_9304_444553540000__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class CDrawDoc : public CDocument
{
protected: // create from serialization only
CDrawDoc();
DECLARE_DYNCREATE(CDrawDoc)
// Attributes
public:
UINT m_nPenWidth; // current user-selected pen width
CPen m_penCur; // pen created according to
// user-selected pen st