第11单元 资源本单元教学目标介绍资源的概念以及图标、位图、菜单、工具栏和状态栏等资源的编程方法。
学习要求掌握Developer Studio的各种资源编辑器的用法,能在应用程序中添加图标、位图、菜单和工具条等资源并进行相应的编程工作。
授课内容除了文档对象的数据成员和变量外,典型的Windows 应用程序还会用到一些称为资源的数据,包括确定应用程序的用户界面的一些文本和图形,如菜单、加速键、位图、光标、图标、对话框、控件、字符串和工具栏等。
当Windows调用一个应用程序时就从程序的可执行文件中读取程序代码和初始化数据,并将其复制到分配的内存中。然而,除了一些特殊情况,资源数据仍保留在硬盘上的可执行文件中。实际上,当应用程序创建窗口、显示对话框或装载位图时,才从EXE文件或DLL文件中读取相应的资源数据。
Developer Studio提供了几种资源编辑器,可以通过它们创建或编辑应用项目的资源数据。本单元介绍在应用程序中首先会遇到的几种界面元素,包括图标、位图、菜单、工具栏、加速键和状态栏。对话框和控件分别放在第14单元和第15单元讨论。
11.1 资源脚本文件和资源头文件项目在一个扩展名为.rc的文件中定义资源,资源文件常与项目同名。资源文件是文本文件,可用文本编辑器编辑阅读,然而更方便的方法是通过Developer Studio的各种资源编辑器进行编辑。在资源文件中定义了菜单脚本和字符串等内容,但不包含位图和图标的图形数据。图形资源保存在单独的文件中,在资源文件中只保存了它们的名字和位置。
在项目中,资源通过资源标识符加以区别。通常在使用资源编辑器建立资源或将一外部资源插入项目时为资源命名标识符。标识符的命名有一定的规则,如IDR_MAINFRAME代表主框架窗口的菜单和工具栏,IDD_ABOUTBOX代表About(关于)对话框。通常将一个项目中所有的资源标识符均放在头文件Resource.h中定义,因此在源代码文件中要包含此头文件。下面列出一些MFC使用的资源标识符前缀:
标识符前缀 说明
IDR_ 主菜单、工具栏、加速键表和应用程序图标
IDD_ 对话框
IDC_ 控件和光标
IDS_ 字符串
IDP_ 消息框提示字符串
ID_ 菜单命令资源编译器是Developer Studio的一个独立组成部分,负责将程序的文本和图形资源编译成目标文件。这些目标文件将和源程序编译生成的目标文件一起连接为可执行文件。
11.2 图标(Icon)
在Windows中,每个文件均有一个图标,通过双击该图标可打开该文件。作为应用程序的图标,应该醒目、美观,并能与程序的功能有所关联。但在前几单元中,我们没有对程序的图标做相应处理,因此所有程序均使用了MFC缺省的程序图标,缺乏特色。下面介绍在应用程序中添加图标的方法。
向程序中添加资源要经过经过如下步骤:如果项目中还没有资源文件,则首先应为项目建立一个资源文件。然后将资源加入项目,并在程序中引用资源。向项目中添加资源可使用Developer Studio的Insert/Resource…菜单选项,选择相应的资源类型并加入新资源(参看11.8:“向项目中添加资源”)。
在应用程序中引用资源,首先要在程序首部添加文件包含命令
#include,resource.h”
该头文件中包含了项目中所有资源的标识符定义(由资源编辑器自动插入)。
与设定图标有关的代码通常放在应用程序类的成员函数InitInstance()中。首先使用CWinApp类的成员函数
HICON CWinApp::LoadIcon(LPCTSTR lpszResourceName)const;
HICON CWinApp::LoadIcon(UINT nIDResource)const;
载入图标。这里HICON为图标句柄类型,参数lpszResourceName为图标的名称,nIDResource为图标的ID。该函数的返回值即该图标的句柄。
然后调用CWnd类的成员函数
HICON CWnd::SetIcon(HICON hIcon,BOOL bBigIcon);
安装图标。其中参数bBigIcon确定安装的是何种图标。图标有两种:大图标为32×32点阵,小图标为16×16点阵。使用哪个图标是由操作系统的设置决定的,作为应用程序,应该同时提供两种图标。该函数的返回值为窗口原来图标的句柄。
[例11-1] 为例9-3的吹泡泡程序添加一标识符为IDI_MAINICON的图标(该图标应已按11.8:“向项目中添加资源”中的方法建立并加入项目)。
说 明:建立项目的方法见9.8:“用Visual C++集成开发环境开发Win32应用程序”。
程 序:在例9-3程序前面添加一文件包含命令:
#include,resource.h”
并将CMyApp::InitInstance()函数修改为:
BOOL CMyApp::InitInstance()
{
HICON hIcon;
hIcon = LoadIcon(IDI_MAINICON); // 载入图标
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("吹泡泡程序"));
pFrame->SetIcon(hIcon,TRUE); // 设置大图标
pFrame->SetIcon(hIcon,FALSE); // 设置小图标
pFrame->ShowWindow(m_nCmdShow);
this->m_pMainWnd = pFrame;
return TRUE;
}
分 析:同一图标有大、小两种尺寸,均用同一标识符表示,同时载入程序,但要分别安装。
11.3 位图(Bitmap)
所谓位图,即点阵式图象。与图标不同的是位图的尺寸非常灵活,可以任意设置。实际上,应用程序通常使用两种位图:一种可由位图编辑器(参看11.9.2:“位图编辑器”)生成、编辑,通常尺寸较小,颜色种类数较少(最多256色)。这类位图往往用作应用程序中的某种标志或小幅图象。另一种位图就是通常的图象文件,可通过专用的图象工具软件生成,也可通过扫描仪等设备直接将照片、图片等输入计算机,可以是真彩图象。Developer Studio可将.BMP格式的图象文件作为资源加入项目。
在MFC中,用CBitmap类对象存放位图的参数。CBitmap类有以下几个重要成员函数:
1.载入位图资源:
BOOL LoadBitmap( LPCTSTR lpszResourceName );
BOOL LoadBitmap( UINT nIDResource );
其中参数lpszResourceName和nIDResource分别为资源名称和标识符。装载成功时该函数返回非零值,否则返回零。
2.读位图信息
int GetBitmap( BITMAP* pBitMap );
其中参数pBitMap为一BITMAP结构体对象的地址。BITMAP结构体类型用于存放位图有关信息:
typedef struct tagBITMAP {
int bmType; // 位图类型(0)
int bmWidth; // 位图宽
int bmHeight; // 位图高
int bmWidthBytes; // 位图每行的字节数
BYTE bmPlanes; // 位平面数
BYTE bmBitsPixel; // 每点字节数
LPVOID bmBits; // 位图数据指针
} BITMAP;
与一般的图形相比,位图的显示过程稍复杂些。首先应建立一合适的内存设备环境:
CDC MemDC;
MemDC,CreateCompatibleDC(NULL);
并将位图选入该设备环境:
MemDC.SelectObject(&m_Bitmap);
然后可用CDC类的BitBlt()成员函数从内存设备环境中将位图复制到指定设备(如窗口或打印机)。BitBlt()函数的原型为:
BOOL BitBlt ( int x,int y,int nWidth,int nHeight,CDC* pSrcDC,
int xSrc,int ySrc,DWORD dwRop );
其中参数x,y为目标区左上角坐标,nWidth和nHeight分别为目标区的宽度和高度(逻辑坐标),pSrcDC为内存设备指针,xSrc和ySrc为原图中欲显示块左上角坐标,dwRop为复制方式,常用值为SRCCOPY,即将位图按原样复制。通过恰当设置这些参数,可以输出位图中的某个矩形区域。
[例11-2] 显示一张位图文件(.BMP)。
说 明:首先建立Win32 Application空白项目和源代码文件(不要忘记设置项目使之可以使用MFC类库),然后按11.8:“向项目中添加资源”的方法为项目建立资源文件,并将待显示的位图文件作为资源装入项目。
程 序:
// Example 11-2:显示BMP图片
#include <afxwin.h>
#include "resource.h"
// 框架窗口类
class CMyWnd,public CFrameWnd
{
CBitmap m_Bitmap;
int m_nHeight;
int m_nWidth;
public:
CMyWnd();
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
CMyWnd::CMyWnd()
{
m_Bitmap.LoadBitmap(IDB_BITMAP1);
BITMAP BM;
m_Bitmap.GetBitmap(&BM);
m_nWidth = BM.bmWidth;
m_nHeight = BM.bmHeight;
}
// 响应绘制窗口客户区消息
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CDC MemDC;
MemDC.CreateCompatibleDC(NULL);
MemDC.SelectObject(&m_Bitmap);
dc.BitBlt(0,0,m_nWidth,m_nHeight,&MemDC,0,0,SRCCOPY);
}
// 应用程序类
class CMinMFCApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
// 初始化应用程序实例
BOOL CMinMFCApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("Beautiful Cats”));
pFrame->ShowWindow(m_nCmdShow);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMinMFCApp ThisApp;
输 出:在框架窗口中显示一幅图片,如图11-1所示。
分 析:在框架窗口类的构造函数中载入位图资源并取得其宽度和高度数据。在OnPaint()函数中,首先建立一内存设备环境并选入已装载的位图资源,然后使用BitBlt()函数将位图显示在框架窗口的客户区。
CDC类的成员函数StreTchBlt()也可用来显示位图资源。其原型为:
BOOL StretchBlt( int x,int y,int nWidth,int nHeight,CDC* pSrcDC,
int xSrc,int ySrc,int nSrcWidth,int nSrcHeight,DWORD dwRop );
与BitBlt()对比,可发现StretchBlt()多了两个参数nSrcWidth和nSrcHeight。这两个参数与xSrc和ySrc配合可确定位图中的一个矩形区域。通过改变由参数nWidth和nHeight确定的目标区矩形,可实现图象的缩放输出(参看例11-3)。
11.4 菜单(Menu)
菜单在Windows应用程序中具有非常重要的地位。菜单也是一种资源,因此也要通过Developer Studio的资源编辑器编辑。“弹出式菜单”(popup)、“选项”(menu item)和“分隔线”(separator)是构成“树状菜单的三大要素”,如图11-2所示。
通过“弹出式菜单”可以调出一个子菜单,也可以说弹出式菜单就是一个子菜单。分隔线用来区分一组选项。
通过菜单中的“选项”可以调用应用程序的某项功能。每个选项均有一个标识符,而且只有选项才有标识符,弹出式菜单和分隔线都没有标识符,在应用程序中只对选项编程。
当用户选择了一个菜单选项后,就会向应用程序发送一个命令消息WM_COMMAND。该消息的格式为:
ON_COMMAND ( id,memberFxn )
其中参数id可以是菜单选项的标识符,而参数memberFxn为接收该消息的窗口中用于处理该消息的成员函数名。
在MFC程序中,通过CMenu类对象对菜单进行操作。
在应用程序中加入菜单的要通过三个步骤:
1,编辑菜单资源,设置菜单属性(见11.8:“向项目中添加资源”和11.9.3:“菜单编辑器”);
2,在Wnd类的PreCreateWindow()成员函数中载入菜单;
3,为每个菜单选项添加WM_COMMAND消息映射和对应的消息处理函数。
载入菜单的工作可在CWnd类的PreCreateWindow()函数中进行,其原型为:
virtual BOOL PreCreateWindow( CREATESTRUCT& cs );
其中参数cs的类型为CREATESTRUCT。该类型用于存放建立窗口的初始化参数:
typedef struct tagCREATESTRUCT {
LPVOID lpCreateParams; // 创建窗口的参数指针
HANDLE hInstance; // 实例句柄
HMENU hMenu; // 菜单
HWND hwndParent; // 父窗口
int cy; // 窗口高
int cx; // 窗口宽
int y; // 窗口左上角y坐标
int x; // 窗口左上角x坐标
LONG style; // 窗口类型
LPCSTR lpszName; // 窗口名称
LPCSTR lpszClass; // 窗口类名
DWORD dwExStyle; // 附加类型
} CREATESTRUCT;
PreCreateWindow()函数在窗口创建前被调用,通过重载该函数,程序员可以设置各种窗口参数,也可以使用LoadMenu()载入菜单资源。其方法为:
cs.hMenu = LoadMenu(NULL,MAKEINTRESOURCE(IDR_MAINMENU));
载入菜单后,还需为每个菜单选项添加消息映射和消息处理成员函数。
[例11-3] 修改11-2,使之可以不同比例放大或缩小图象。
说 明:使用StretchBlt()函数代替BitBlt()函数就可实现图象的缩放显示。在项目中加入一个弹出式菜单(将标识符改为IDR_MAINMENU),内含3个菜单选项:缩小1倍显示,按原尺寸显示和放大1倍显示,其标识符分别改为ID_SHRINK,ID_BESTFIT和ID_ZOOMOUT。
程 序:
// Example 11-3:以不同尺寸显示BMP图片
#include <afxwin.h>
#include "resource.h"
// 框架窗口类
class CMyWnd,public CFrameWnd
{
CBitmap m_Bitmap;
float m_fTimes;
int m_nHeight;
int m_nWidth;
public:
CMyWnd();
BOOL PreCreateWindow(CREATESTRUCT &cs);
protected:
afx_msg void OnPaint();
afx_msg void OnShrink();
afx_msg void OnBestFit();
afx_msg void OnZoomOut();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_PAINT()
ON_COMMAND(ID_SHRINK,OnShrink)
ON_COMMAND(ID_BESTFIT,OnBestFit)
ON_COMMAND(ID_ZOOMOUT,OnZoomOut)
END_MESSAGE_MAP()
// 主窗口类的成员函数
CMyWnd::CMyWnd()
{
BITMAP BM;
m_Bitmap.LoadBitmap(IDB_BITMAP1);
m_Bitmap.GetBitmap(&BM);
m_nWidth = BM.bmWidth;
m_nHeight = BM.bmHeight;
m_fTimes = 1.0;
}
// 装入菜单
BOOL CMyWnd::PreCreateWindow(CREATESTRUCT &cs)
{
cs.hMenu = LoadMenu(NULL,MAKEINTRESOURCE(IDR_MAINMENU));
return CFrameWnd::PreCreateWindow(cs);
}
// 缩小图象
void CMyWnd::OnShrink()
{
m_fTimes = 0.5;
Invalidate();
}
// 放大图象
void CMyWnd::OnZoomOut()
{
m_fTimes = 2.0;
Invalidate();
}
// 原样显示
void CMyWnd::OnBestFit()
{
m_fTimes = 1.0;
Invalidate();
}
// 响应绘制窗口客户区消息
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CDC MemDC;
MemDC.CreateCompatibleDC(NULL);
MemDC.SelectObject(&m_Bitmap);
dc.StretchBlt(0,0,(int)(m_nWidth*m_fTimes),
(int)(m_nHeight*m_fTimes),
&MemDC,0,0,m_nWidth,m_nHeight,SRCCOPY);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用窗口类的成员函数
// 初始化应用程序实例
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("Show Bitmap 1.0"));
pFrame->ShowWindow(SW_SHOWMAXIMIZED);
pFrame->UpdateWindow();
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
输入输出:通过菜单可选择图片的显示尺寸,如图11-3所示。
自学内容
11.5 快捷键
快捷键总是与菜单选项配合使用。如果要为某菜单配上相应的快捷键,首先要插入快捷键表资源,然后插入对每个菜单项的快捷键(参看11.8:“向项目中添加资源”和11.9.4:“快捷键编辑器”)。
在程序中载入快捷键资源的方法与载入菜单资源的方法类似,均可在Wnd类的成员函数PreCreateWindow()中进行。载入快捷键使用Wnd类的成员函数LoadAccelTable(),其原型为:
BOOL LoadAccelTable ( LPCTSTR lpszResourceName );
其中参数lpszResourceName为快捷键表名称。如果要使用资源标识符,可使用带参宏定义MAKEINTRESOURCE()将其转换为资源名称。
由于快捷键是与菜单选项配合使用,所以无需专为快捷键消息添加消息映射和消息处理函数。
11.6 字符串表
应用程序窗口中使用的一些字符串信息如窗口标题和菜单项的说明信息等也是一种资源,通常存放在字符串表中。对字符串表的编辑与其他资源的编辑类似,可参看11.9.5:“字符串编辑器”。
11.7 为框架自动装入资源
对于基于框架窗口的应用程序,可以简化主窗口使用的各种资源(如图标、标题字符串和菜单等)的装入过程,这可以通过给这些资源以相同的资源标识符来实现。其方法为使用CFrameWnd类的成员函数LoadFrame(),原型为:
virtual BOOL LoadFrame(UINT nIDResource,
DWORD dwDefaultStyle = WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,
CWnd* pParentWnd = NULL,CCreateContext* pContext = NULL );
虽然该函数的参数很多,但我们感兴趣的主要是第一个参数nIDResource(资源标识符),其他参数均可使用默认值。nIDResource参数应为各种与框架窗口有关的资源(如图标、窗口标题字符串、主菜单和快捷键表)的共有资源标识符。使用LoadFrame()可免除分别载入各种资源的麻烦,其应用见例11-4。
编程与调试
11.8 向项目中添加资源向项目中添加资源的方法如下:
1.如果项目中尚无资源文件,则使用Developer Studio的菜单项File/New…,从Files选项卡中选择Resource Script(资源描述),并在选项卡右方添入资源文件名(通常与项目名相同)后按OK按钮。此时即可发现Developer Studio的工作区窗口中新添了一个ResourceView。通过ResourceView可以查看项目中的各种资源,也可以通过双击某资源标识来快速打开相应的资源编辑器。
2.使用Developer Studio的菜单选项Insert/Resource…调出Insert Resource对话框,然后在其中选择相应的资源如Bitmap(位图)、Icon(图标)和Menu(菜单)等;
3.如果要用的资源尚不存在,按下对话框右方的New…按钮即可进入相应的资源编辑器;
4.如果要用的资源已存在(如位图文件.BMP)则按下对话框右方的Import按钮,输入图象文件的路径。在文件路径对话框中要注意正确设置文件类型。如果该文件为真彩照片文件,则会弹出一个消息框,说明Developer Studio的位图编辑器无法编辑该文件。插入一项资源后,会在的工作区窗口的ResourceView中显示出该项资源。
11.9 资源编辑器
资源编辑器包括各种资源的可视化编辑器和资源属性对话框。各种资源的属性对话框中的项目各不相同,但均包含对资源标识符的编辑功能。通常在生成一个新的资源时,Developer Studio均会为其自动指定一个资源标识符,但在编程时往往要修改该标识符使之与该项资源的含义更相符。
11.9.1 图标编辑器
图标编辑器如图11-4所示。
在图标编辑器左方以实际大小显示目前正在编辑的图标,旁边是放大的图象,也是实际的编辑区。上方的Device框中显示的是正在编辑的图标的种类,其右方的按钮用来增加图标种类(缺省图标为32×32点阵)。右上方的浮动窗口是调色板,调色板下面的浮动窗口为作图工具箱,其中有笔、刷子、矩形和圆等常用工具。
11.9.2 位图编辑器
位图编辑器与图标编辑器非常相似,同样具有编辑区、调色板和工具箱,如图11-5所示。
与图标编辑器不同的是位图的大小比较灵活,可通过拖动编辑区边界上的方格来改变位图的大小。
11.9.3 菜单编辑器
菜单编辑器如图11-6所示,其下方是其资源属性对话框,可以通过双击待编辑的菜单选项打开。
在属性对话框中,ID是该菜单项的标识符。通常菜单编辑器会自动产生一个ID,但最好为每个菜单项另取与其含义相符的标识符。
在标识符框下面有一些复选框表示菜单项的属性,如Separator表示分隔线,Popup选项可引出一子菜单。具有Popup性质的菜单项没有标识符,因为在程序中只对具体的菜单选项编程,Popup菜单只起将若干选项组合在一起的功能。Checked表示在菜单选项前打勾;Inactive表示该选项失效,但还可正常显示;Grayed也表示该菜单项失效,但会以灰色显示。
Caption是菜单项的文字部分(标题),如果在其中的某字母前加一个“&”符号,则该字母在显示时带一下划线,该字母就成为这个菜单选项的记忆键。在程序运行时,用户可通过按下组合键“alt+记忆键”来使用菜单选项。显然,各菜单选项的记忆键应唯一。
Prompt框是该菜单项的提示文字,可在提示栏中显示。
Visual Studio支持鼠标拖曳调整菜单项位置。要调整菜单项位置,只需要选中某菜单项并将其拖至适当位置即可。
11.9.4 快捷键编辑器要编辑快捷键,选择Accelerator资源类型,即可打开快捷键编辑器,如图11-7所示。双击打开指定的快捷键资源。如果要删除一个快捷键,可以直接按Del键。如果要增加快捷键,可以按Ins键以弹出加速键属性对话框。在其ID下拉列表框中选择欲增加快捷键的菜单项标识符,在Key一栏中输入快捷键码即可完成快捷键设置。
11.9.5 字符串表编辑器
在Workspace窗口的Resource View中选择String Table资源可打开字符串表编辑器,如图11-8所示。字符串表编辑器的使用方法与快捷键编辑器使用方法完全相同。
11.10 编译和链接编译运行程序,可以使用Developer Studio菜单的Build/Build ×××.exe选项(其中×××为项目名)或按快捷键F7。编译完成再使用Build/Execute ×××.exe菜单选项或按快捷键CTRL+F5运行该程序。也可以直接按快捷键CTRL+F5(直接运行),这时系统会提示是否要编译该项目,回答“Yes”,Visual Studio就会自动编译、连接该项目,然后运行相应的可执行程序。
在Build菜单下有Compile,Build,Rebuild All三个菜单项用于编译程序。其中Compile用于编译当前打开的活动文档;Build只编译工程中上次修改过的文件,并链接程序生成可执行文件。如果以前没有作过编译,它会自动调用Rebuild All操作,依次编译资源文件、源程序文件等;Rebuild All不管文件是否作过修改,都会编译工程中的所有源文件。由于编译链接过程中会产生大量的中间文件和目标文件,它们会占用许多硬盘空间,因此Visual Studio在Build下提供了Clean菜单项用于清除这些中间文件。用户在完成一个工程后,应及时清理这些中间文件,否则硬盘空间会很快被耗尽。
程序设计举例
[例11-4] 拼图程序。
设计思想:将一张图片切分成若干小片,打乱顺序任意显示。用户可用鼠标拖动各小片到正确位置以恢复用来的图象。
说 明:首先创建一Win32 Application空项目,然后向项目中添加C++ Source File(源程序)和Resource Script(资源描述)文件。在添加资源描述文件后应将其关闭。
按本单元介绍的有关内容为本程序添加图标、字符串(窗口标题)和下拉菜单(标题为“游戏”)等资源,其标识符均取IDR_MAINFRAME。
为“游戏”下拉菜单添4个菜单选项,一个是“自动拼图”,和一个“结束”,标识符分别为ID_BEGIN和ID_END。剩下3个用来控制拼图的难度,标识符分别为ID_GRAD01,ID_GRAD02和ID_GRAD03。
另选一幅漂亮的图片,作为资源装入项目。该图片即拼图的底图。然后输入以下程序,注意在编译前选择使用MFC。
程 序:
// Example 11-4,拼图程序
#include <afxwin.h>
#include <afxext.h>
#include <stdlib.h>
#include <time.h>
#include "resource.h"
#define MAX_CHIPS 500
// 派生一个框架窗口类
class CPuzzleWnd,public CFrameWnd
{
CBitmap m_bmpPuzzle; // 位图
int m_nPuzzleWidth; // 位图宽
int m_nPuzzleHeight; // 位图高
int m_nColCount; // 每行拼图块数
int m_nRowCount; // 每列拼图块数
CRect m_rectChips[MAX_CHIPS]; // 每个拼图块的位置
int m_nChipWidth; // 拼图块宽
int m_nChipHeight; // 拼图块高
BOOL m_bCaptured;
CPoint m_pointMouse;
int m_nCurrIndex;
public:
CPuzzleWnd();
void InitPuzzle(int colcount,int rowcount);
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnLButtonUp(UINT nFlags,CPoint point);
afx_msg void OnMouseMove(UINT nFlags,CPoint point);
afx_msg void OnPaint();
afx_msg void OnShowFig();
afx_msg void OnGrad01();
afx_msg void OnGrad02();
afx_msg void OnGrad03();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CPuzzleWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_PAINT()
ON_COMMAND(ID_SHOWFIG,OnShowFig)
ON_COMMAND(ID_GRAD01,OnGrad01)
ON_COMMAND(ID_GRAD02,OnGrad02)
ON_COMMAND(ID_GRAD03,OnGrad03)
END_MESSAGE_MAP()
// 主窗口类的成员函数
// 构造函数,装入位图资源
CPuzzleWnd::CPuzzleWnd()
{
BITMAP BM;
m_bmpPuzzle.LoadBitmap(IDB_BITMAP1);
m_bmpPuzzle.GetObject(sizeof(BM),&BM);
m_nPuzzleWidth = BM.bmWidth;
m_nPuzzleHeight = BM.bmHeight;
InitPuzzle(4,3);
m_bCaptured = FALSE;
}
// 初始化拼图数组
void CPuzzleWnd::InitPuzzle(int colcount,int rowcount)
{
m_nColCount = colcount;
m_nRowCount = rowcount;
m_nChipWidth = m_nPuzzleWidth/m_nColCount;
m_nChipHeight = m_nPuzzleHeight/m_nRowCount;
srand((unsigned)time( NULL));
for(int row=0; row<m_nRowCount; row++)
for(int col=0; col<m_nColCount; col++)
{
int index = row*m_nColCount+col;
m_rectChips[index].left = rand()%m_nPuzzleWidth+20;
m_rectChips[index].top = rand()%m_nPuzzleHeight+20;
m_rectChips[index].right = m_rectChips[index].left
+m_nChipWidth;
m_rectChips[index].bottom = m_rectChips[index].top
+m_nChipHeight;
}
}
// 自动完成拼图
void CPuzzleWnd::OnShowFig()
{
for(int row=0; row<m_nRowCount; row++)
for(int col=0; col<m_nColCount; col++)
{
int index = row*m_nColCount+col;
m_rectChips[index].left = col*m_nChipWidth;
m_rectChips[index].top = row*m_nChipHeight;
m_rectChips[index].right = m_rectChips[index].left
+m_nChipWidth;
m_rectChips[index].bottom = m_rectChips[index].top
+m_nChipHeight;
}
Invalidate();
}
// 设置一级难度
void CPuzzleWnd::OnGrad01()
{
InitPuzzle(4,3);
Invalidate();
}
// 设置二级难度
void CPuzzleWnd::OnGrad02()
{
InitPuzzle(8,6);
Invalidate();
}
// 设置三级难度
void CPuzzleWnd::OnGrad03()
{
InitPuzzle(16,12);
Invalidate();
}
// 按下鼠标左键
void CPuzzleWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
for(int row=m_nRowCount-1; row>=0; row--)
for(int col=m_nColCount-1; col>=0; col--)
{
int index = row*m_nColCount+col;
if(m_rectChips[index].PtInRect(point))
{
SetCapture();
m_bCaptured = TRUE;
m_pointMouse = point;
m_nCurrIndex = index;
return;
}
}
}
// 释放鼠标左键
void CPuzzleWnd::OnLButtonUp(UINT nFlags,CPoint point)
{
if(m_bCaptured)
{
::ReleaseCapture();
m_bCaptured = FALSE;
}
}
// 移动鼠标左键
void CPuzzleWnd::OnMouseMove(UINT nFlags,CPoint point)
{
if(m_bCaptured)
{
InvalidateRect(m_rectChips[m_nCurrIndex]);
CSize offset(point-m_pointMouse);
m_rectChips[m_nCurrIndex] += offset;
InvalidateRect(m_rectChips[m_nCurrIndex],FALSE);
m_pointMouse = point;
}
}
// 显示当前拼图
void CPuzzleWnd::OnPaint()
{
CPaintDC dc(this);
CDC MemDC;
MemDC.CreateCompatibleDC(NULL);
MemDC.SelectObject(&m_bmpPuzzle);
for(int row=0; row<m_nRowCount; row++)
for(int col=0; col<m_nColCount; col++)
{
int index = row*m_nColCount+col;
dc.BitBlt(m_rectChips[index].left,
m_rectChips[index].top,
m_nChipWidth,
m_nChipHeight,
&MemDC,
col*m_nChipWidth,
row*m_nChipHeight,
SRCCOPY);
}
}
// 应用程序类
class CPuzzleApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 初始化应用程序实例
BOOL CPuzzleApp::InitInstance()
{
CPuzzleWnd *pFrame = new CPuzzleWnd;
pFrame->LoadFrame(IDR_MAINMENU);
pFrame->ShowWindow(SW_SHOWMAXIMIZED);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CPuzzleApp TheApp;
输入输出:可用鼠标拖动各拼图块,如图11-9。
分 析:本程序使用了多种资源,其中程序图标、框架窗口名和菜单等使用同一标识符IDR_MAINFRAME,在应用程序类的成员函数InitInstance()中使用LoadFrame()函数统一载入了这些资源。
本程序的基本功能是将一位图划分成若干拼图块,然后在窗口中随机显示这些拼图块。用户可使用鼠标左键拖动各拼图块在窗口中移动以恢复原来的图象。 为此,在CPuzzleWnd类中声明了一个拼图块数组,用来存放各数组块的位置信息。在InitPuzzle()函数中随机生成各拼图块的初始位置。在框架窗口类的构造函数中,首先载入图片资源,然后初始化各拼图块的位置等参数。
框架窗口类要响应的消息较多,除了鼠标左键的按下、弹起和移动鼠标的消息外,还有自动拼图和难度等菜单选项的命令消息。
[例11-5] 防空战游戏程序。本游戏涉及4种对象:敌方飞机,在屏幕上方自动飞行;敌方飞机炸弹,由敌方飞机投掷,下落路线为与飞机飞行方向一致的抛物线;我方自行高炮,由用户使用键盘上的左、右方向键操纵;高炮炮弹,用户按下空格键发射。
说 明:项目创建方法同上例。4种对象的图象均使用自绘位图实现。
程 序:见附录4:“防空战游戏程序”。
分 析:在本程序中,对飞机、炸弹、高炮和炮弹分别定义了相应的类,各类中均有相应对象的位图和显示位置等数据成员,以及初始化对象、显示对象、取对象位置和移动对象等成员函数。
在框架窗口类中,对应菜单选项“开始游戏”和“结束游戏”的命令消息处理函数用来设置定时器。定时器消息响应函数控制飞机、炸弹和炮弹的运行。高炮的运动和射击由用户按键控制,左、右方向键移动高炮,空格键发生炮弹。
单元上机练习题目为例10-4的中国象棋程序添加图标和窗口标题字符串。
进一步完善例11-4的拼图游戏程序,添加可用菜单选择若干种拼图图片的功能。
仿例11-5程序的结构,编一“猫捉老鼠”游戏程序。用户通过键盘上的上、下、左、右方向键控制猫,5只老鼠由定时器控制在窗口中自由活动,可能随时改变行走方向,遇窗口边界则折回。猫碰到老鼠后则该鼠被吃,不再显示。吃掉所有老鼠后游戏结束并显示所用时间。可为游戏增加难度菜单,控制老鼠的移动速度。