mfc的position_MFC学习之路

标签:一、接触MFC  day01

1. 使用资源:

①insert resource,对话框资源

②resource view下面,右键对话框,修改properties,重要的是资源ID,一般为IDD_Dialogxx

③对资源的操作一般都是通过类来完成,因此要操作资源需要为资源添加类,基类一般都是CDialog

注意:添加类后,在class view中没有该类的浏览信息

方法一:可以删除 .ncb文件(no compile browse 无编译浏览文件),重新编译一遍

方法二:在文件视图,右键工程,添加 cpp文件和h文件

2. 对话框的创建;

2.1 头文件包含include “dialog.h”

2.2 先创建后显示

2.2.1 创建模态对话框:Ctest dlg; dlg.DoModal();

2.2.2 创建非模态对话框:

①static CTestDlg dlg; 或者设置成员变量dlg;或者使用堆对象dlg

②dlg.Create();

③dlg.ShowWindow(); 如果在Create函数中设置了WS_VISIBLE参数,则不需要调用ShowWindow函数

④如果是堆中的对象,还需要重写OnOK函数,在其中销毁dlg:dlg->Destroy()---------------------重要

3. Button的创建(通上面Dialog的创建)

CButton btn;

if(b_isCreated)

{

btn.Create();

btn.ShowWindow(SW_SHOW);

Sleep(2000); 睡眠2s

}

else

{

btn.Destroy(); // 现在就销毁资源,如果是堆空间,这句话非常重要

}

4. 让static text 静态文本框 接收消息:

①由于所有的static都是相同的ID_STATIC,因此无法添加消息,修改ID后可以通过消息映射添加消息处理函

②style中设置允许通告消息notify

5. 获得对话框中的对象: CWnd* GetDlgItem(ID_XXX)

SetWindowText("xxx");

GetWindowText(CString&);

6. 控件访问的7种方式: 原始方式  控件绑定   消息方式

6.1 GetDlgItem()->Get(Set)WindowText();

结合:atoi和itoa函数

/--------------------------------------------------/

char n1[16],n2[16],n3[16];

int i1,i2,i3;

GetDlgItem(IDC_EDIT1)->GetWindowText(n1,sizeof(n1));

..

i1=atoi(n1);

i2=atoi(n2);

i3=i1+i2;

GetDlgItem(IDC_EDIT3)->SetWindowText(itoa(i3,n3,10));

/---------------------------------------------------/

6.2 GetDlgItemText()/SetDlgItemText()

6.3 GetDlgItemInt()/SetDlgItemInt();

/---------------------------------------------------/

int i1,i2,i3;

i1=GetDlgItemInt(IDC_EDIT1);

i2=GetDlgItemInt(IDC_DEIT2);

i3=i1+i2;

SetDlgItemInt(IDC_EDIT3);

/---------------------------------------------------/

6.4 将控件和整型相关联

①ClassWizard -> Member Variables

②DDX_Text

③DoDataExchange

UpdateData(true);  // 获取值

m_num3=m_num1 + m_num2;

UpdateData(false); // 更新值

注意:使用DDX,DDV时候,需要使用UpdateData函数,一般安装先真后假的顺序执行

6.5 将控件和控件变量关联

一个控件可以和多个,多种类型的变量绑定

m_Edit.GetWindwoText(buf,sizeof(buf));

m_Edit.SetWindowText(buf);

6.6 SendMessage()

WM_GETTEXT 获取消息

WM_SETTEXT 设置消息

方法1:使用::SendMessage(hwnd,uMsg,wParam,lParam);

char buf[64]={‘\0‘};

::SendMessage(m_Edit.m_hWnd,WM_GETTEXT,sizeof(buf),(LPARAM)buf);

::SendMessage(GetDlgItem(IDC_EDIT)->m_hWnd,WM_GETTEXT,sizeof(buf),(LPARAM)buf);

int i=atoi(buf);

itoa(xx,xx,xx);

::SendMessage(m_Edit.m_hWnd,WM_SETTEXT,0,(LPARAM)buf);

方法2:使用成员 .SendMessage(uMsg,wParam,lParam);

m_Edit.SendMessage(WM_GETTEXT,sizof(buf),(LPARAM)buf);

注意:这里说明,在不同的环境下,wParam和lParam代表的参数不同含义

6.7 SendDlgItemMessage()

SendDlgItemMessage(IDC_EDIT,WM_GETTEXT,sizeof(buf),(LPARAM)buf);

7. 属性表单、向导程序wizard

属性表单和属性页

7.1 创建属性页----------------------------------------------------------需要界面资源

①插入属性页资源,设计"属性页"资源,

insert resource ->  对话框  IDD_PROPAGE_XXX

insert resource ->  对话框  IDD_PROPSHEET_XXX

②为页面添加类(继承CPropertyPage)   需要属性页资源

注意:在resource view中,复制一个资源,直接粘贴,可以快速产生多个资源

7.2 创建属性表单类(包含3个属性页类的成员)  (继承CPropertySheet)-----不需要界面资源

①insert ->  new class : CPropSheet  Base class:CPropertySheet

②物理上添加属性表单:右键属性表单类名 -->>  添加成员变量 m_page1,m_page2,...

③逻辑上添加属性表单:构造函数中:AddPage(m_page1);AddPage(m_page2);...

④主对话框添加一个按钮,添加消息响应:sheet.DoModal();

void CHelloDialog::OnBtn(){

CPropSheet sheet(第一个表单程序);

sheet.SetWizardMode();

sheet.DoModal();

}

注意:中文乱码,将资源属性中,将字体改成新宋体

向导程序

在DoModal之前调用,sheet.SetWizarMode()

总结:

-------------------------------------------

类型            基类           是否需要资源

-------------------------------------------

属性页          CPropertyPage   需要

属性表单        CPropertySheet  不需要

-------------------------------------------

7.3 完善Wizard:

关键点:上一步,下一步,确定,取消  等按钮属于Sheet

CPropertySheet::SetWizardButtons(xxx)

①属性表单处于Active状态时候: 要取消上一步,下一步按钮,

必须复写虚函数:virtual CPropertyPage::OnSetActive()

BOOL CProp1::OnSetActive(){

// 这里需要转型,因为GetParent返回一个CWnd*指针,

// 而SetWizardButtons不是虚函数,因此必须转型为CPropertySheet类型

((CPropertySheet*)GetParent())->SetWizardButtons(PS_);

}

②virtual CPropertyPage::OnWizardNext() -----------单击下一步,CPropertyPage派生类调用该函数

LRESULT CProp1::OnWizardNext(){

if(条件不满足){xxx,return -1}

CPropertyPage::OnWizardnext();

}

二、MFC下基于socket的网络通信程序           day02              看mfc的文档,注意一些框架相关的东西,这些东西以afx开头

/----------------------------------------------/

>stdafx.h

#include "afxsock.h" // afxsock.h 每个类都要使用,因此包含在应用程序公共头文件中

/----------------------------------------------/

2.1 socket版本协商

在CWinApp派生类的InitInstance中,使用AfxSocketInit()进行socket版本协商

(使用AfxSocketInit()会调用SWAStartup和WSACleanup,协商socket版本,加载套接字动态链接库)

/------------------------------------------------------------------------------------------/

服务器:协商windowssocket版本,调用合适的动态链接库(windows的socket版本比较多,需要协商)

WSAStartup() (window socket application)       win32 api

SWACleanup()

/-------------------------------------------------------------------------------------------/

使用MFC,可以直接使用封装好的协商机制: 在应用程序类的InitInstance函数中进行版本协商(AfxSocketInit())

BOOL CChatApp::InitInstance(){

if(!AfxSocketInit()){

AfxMessageBox("加载套接字失败");

return FALSE;

}

}

2.2 初始化套接字: 定义一个初始化服务器套接字的函数InitSocket,并在对话框InitDialog中调用InitSocket

①在CChatDlg中加载成员函数:BOOL InitSocket()

右键类名-->> add member function

SOCKET m_socket;

BOOL CChatDlg::InitSocket(){ // m_socket为SOCKET类型

m_socket = socket(AF_INET,SOCK_DGRAM,0); // SOCK_STREAM SOCK_DGRAM

if(-1==m_socket){

AfxMessageBox("套接字出错");

return false;

}

struct socketaddr_in ser_addr;

ser_addr.sin_family = AF_INET;

ser_addr.sin_addr.s_addr = INADDR_ANY;

ser_addr.sin_port = htons(5000);

// 字节序问题byte order

// intel x86系列是小端模式;internet上是大端模式

// 0x12345678 (高->低)

//地址 低->高 12 34 56 78   小端模式

//地址 低->高 78 56 34 12   大端模式

int ret = bind(m_socket,(struct sockaddr*)&ser_addr,sizeof(sockaddr));

if(ret == -1){

AfxMessageBox("套接字绑定出错);

}

return true;

}

注意:在OnInitDialog结尾处调用InitSocket();

② 服务器接收数据是阻塞过程,单独创建一个线程,用于接收数据

typedef struct {

SOCKET sock;

HWND hWnd;

}

CChatDlg::OnInitDialog()

{

InitSocket();

//---------------------[

// RECVPARAM rp ;

// rp.sock = m_sock;

// rp.hWnd = m_hWnd;

RECVPARAM *rp = new RECVPARAM;

rp->sock = m_socket;

rp->hWnd = m_hWnd;//-----------------

CreateThread(0,0,RecvProc,rp,0,NULL); // 创建线程不是阻塞的,结束后,线程什么时候被调度不能确定

}

ThreadProc是回调函数,因此必须使用WINAPI,以遵循帕斯卡调用约定

static DWORD WINAPI RecvProc(LPVOID p)

{

RECVPARAM *rp = (RECVPARAM*)p;

while(true){

char recvBuf[128]={‘\0‘}

struct sockaddr_in from_addr;

int from_len = sizeof(struct sockaddr);

// 第3个参数为0,表示立即接收和发送

int ret = recvfrom(rp->sock,recvBuf,sizeof(recvBuf),0,(struct sockaddr_in*)&from_addr,&from_len);

if(-1 == ret){

AfxMessageBox("接收数据有误");

break;

}

CString str=recvBuf;

AfxMessageBox(str);

}

}

注意:①上述代码导致界面卡住,应该单独开一个线程:

②上述代码,会执行“接收数据有误”,而recvfrom是一个阻塞型函数,这只能说明传过来的m_socket是错误的

原因:创建线程时候,传递的参数在OnIniitDialog结束时候,参数已经销毁了,参数结构体里面是一片蛮荒之地

③使用 char buf[256],--------sprintf()

使用CString  --------------str.Format();

③自定义消息:static 线程回调函数中不能使用GetDlgItem成员函数,因此无法将消息显示在IDC_EDIT2中

方法一:发送消息SendMessage,发送自定义消息,自定义消息的四个步骤

第一步:定义消息,在对话框头文件开头地方 #define WM_RECVDATA  WM_USER+1

第二部:消息处理函数的定义和声明:

①消息处理函数宏申明  afx_msg OnRecvData();

// Generated message map functions

//{{AFX_MSG(CChatDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);   // 框架添加的

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnBtnsend();

//}}AFX_MSG

afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);  // 用户添加的

②编写OnRecvData函数

第三部:添加消息映射,源代码中,构造函数中,ON_MESSAGE(WM_RECVDATA,OnRecvData)

BEGIN_MESSAGE_MAP(CChatDlg, CDialog)

//{{AFX_MSG_MAP(CChatDlg)

ON_WM_SYSCOMMAND()                                 // 框架添加的

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_BTNSEND, OnBtnsend)

//}}AFX_MSG_MAP

ON_MESSAGE(WM_RECVDATA,OnRecvData)                     // 用户添加的

END_MESSAGE_MAP()

方法二:使用::SetDlgItemText(hWnd,IDC_EDIT2,"XXX") // 需要对话框的句柄

void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam){

SetDlgItemText(IDC_EDIT1,(char*)wParam);

}

④客户端处理

IPAddressCtrl控件:  CIPAddressCtrl::GetAddress(); 注意这个函数父类没有,必须转型

DWORD dip;

(CIPAddress*)GetDlgItem(IDC_IPADDRESS)->GetAddress(dip);

ser_addr.sin_addr.s_addr = htonl(dip);

void CChatDlg::OnBtnsend()

{

// TODO: Add your control notification handler code here

SOCKET sock_send = socket(AF_INET,SOCK_DGRAM,0); // AF_INET--internet  SOCK_DGRAM--UDP  protocol--0,返回socket描述符

struct sockaddr_in ser_addr;

ser_addr.sin_family = AF_INET;

ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 字符串ip地址 转换为 网络字节序

ser_addr.sin_port = htons(5000);

int addr_len = sizeof(struct sockaddr);

CString str;

GetDlgItemText(IDC_EDIT2,str);

sendto(sock_send,str,str.GetLength()+1,0,(struct sockaddr*)&ser_addr,addr_len);

SetDlgItemText(IDC_EDIT2,"");

}

三、win32 应用程序,了解mfc原理

①Windows的程序入口

#include "windows.h"

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine, //project -> settings -> debug -> Program arguments :添加命令行参数

int nCmdShow

){

创建一个完整的窗口的步骤:

第一步:设计一个窗口类 WNCCLASS wndclass;

第二部:注册窗口类     RegisterClass(&wndclass);

第三部:创建窗口       HWND hWnd = CreateWindow("xx",...);

第四步:显示、更新窗口 ShowWindow(hWnd,SW_SHOWNORMAL);

Update

第五步:消息循环 message cycle

MSG msg;

while(GetMessage(&msg,hWnd,0,0){

TranslateMessage(&msg);

Dispatchmessage(&msg); // 投递给WinProc回调函数

}

return 0;

}

//------------------------

//project -> settings -> debug -> Program arguments :添加命令行参数

第六步:编写窗口过程函数处理消息

switch(uMsg){

case WM_PAINT:

{

PAINTSTRUCT ps; // 用于保存设备信息

HDC hdc = BeginPaint(hwnd,&ps);        // 获得dc

char buf[64]="xxx";

TextOut(hwnd,,100,50,buf,sizeof(buf)); // 使用dc绘图

EndPaint(hwnd,&ps);

}

break;

case WM_LBUTTONDOWN:

HDC hdc = GetDC(hwnd);   // 获得设备句柄

char buf[64]="xxx";

TextOut(hdc,200,50,buf,sizeof(buf));

Release(hdc);

break;

case WM_CHAR:

// WM_KEYDOWN 和 相关信息,由TranslateMessage组装成WM_CHAR消息

// 键值存放在 wParam中

char buf[64]={‘\0‘};

sprintf(buf,"char is %c",wParam);

MessageBox(hwnd,buf,"msg",MB_OK);

break;

case WM_CLOSE:

DestroyWindow(hwnd);

case WM_DESTROY:

// SendMessage(hwnd,WM_QUIT); // ERROR 不能使用这个函数,不产生队列消息,因此消息循环不结束

// PostMessage(hwnd,WM_QUIT);

// PostQuitMessage(0);

default:

return DefWindowProc(hWnd,);

}

注意:

①CALLBACK = WINAPI pascal调用约定

②获得与绘图相关的系统对象(GDI对象):

GetStockObject()  // 返回HGDIOBJ 句柄

LoadCursor(NULL,IDC_CROSS) // 第一个参数为NULL,说明使用系统资源,如果为hInstance,只能使用当前应用的资源

LoadIcon(NULL,IDI_ERROR)

③应用程序通过GetMessage获得消息,    一个窗口通过窗口过程WndProc获得消息

应用程序通过DispatchMessage派送消息

④close--WM_CLOSE:DestroyWindow()--->> WM_DESTROY:PostMessage(hWnd,WM_QUIT,0,0)-->> WM_QUIT:

Post和Send的区别:①Post后,产生队列消息,不管消息是否处理,都直接返回,因此应用程序直接销毁了;

②Send直接调用WinProc,并不是产生队列消息,因此消息循环一直不会结束,应用程序也不会结束

⑤BeginPaint() EndPaint()专用于case WM_PAINT:

GetDC()        ReleaseDC() 用在除WM_PAINT之外所有的位置

⑥WM_CHAR消息,则wParam中为字符码

⑦TranslateMessage: combine  WM_KEYDOWN,WM_KEYUP,produce WM_CHAR

-------

考题:

-------

①Windows中GetMessage()与PeekMessage()的区别

GetMessage从消息队列获取消息,直到有一个“合适”的投递消息可用时才能够返回(阻塞型)

如果是WM_QUIT消息,则返回0,否则非0,出错是-1

PeekMessage不会等待有合适的投递消息到达(非阻塞型,瞟一眼)

附加参数remove option :可以决定是否决定移除取到的消息

②Windows编程中PostMessage()与SendMessage的区别

SendMessage发出的不是队列消息,因此不经过消息循环;

PostMessage发出的消息进入线程队列,因此消息循环可以得到

③WM_CLOSE,WM_DESTROY,WM_QUIT三个消息的区别

WM_CLOSE: 是关闭按钮被按下的时候,应该调用DestroyWindow销毁窗口,DestroyWindow发出WM_DESTROY消息

WM_DESTROY: 应该发出WM_QUIT消息

④TranslateMessage()有什么作用

⑤Windows应用程序中消息如何路由

⑥while(GetMessage(lpMsg,hWnd,0,0))这样的代码为什么要尽量避免

//--------------------------------------------------------------------

因为GetMessage可能的返回值为: WM_QUIT 0; 其他消息 nonzero; error--> -1

//--------------------------------------------------------------------

while(1){

int ret = GetMessage(&msg,NULL,0,0); // null表示接受所有窗口消息

if(ret == 0){

break;

}

else if(ret == -1){

break;

}

TranslateMessage(&msg);

DispatchMessage(&msg);

}//-----------------------------------------------------------------

四、MFC----Menu 菜单编程      单文档编程

4.1 利用appwizard生成一个单文档,View Frame(不包含菜单),Doc

标准消息:除WM_COMMAND之外的,以WM_开头的消息

从CWnd继承的类可以接收这种消息

命令消息:WM_COMMAND  菜单栏,工具栏,加速键产生

MFC中以菜单ID标志;SDK中以wParam标示

通告消息:控件产生的消息,如按钮,列表框产生的消息,为的是向父窗口通知事件发生。

通常以WM_COMMAND呈现,从CCmdTarget派生的类可以接收

--------------------------------------------------

消息类型         消息ID        消息映射宏

--------------------------------------------------

标准消息         WM_XX          ON_WM_XX()

命令消息         WM_COMMAND     ON_COMMAND(id,memberFxn)

通告消息         WM_COMMAND     ON_COMMAND(id,memberFxn)

自定消息         自定义IDXX     ON_MESSAGE(IDXX,memberFxn)

-----------------------------------------------------------

4.2 消息路由

①菜单命令消息的路由:

消息映射宏: WM_COMMAND(ID_MENU,OnMENU)

路由顺序:view---doc---frame---app

注意:一定不会交由CAboutDlg处理

②通告消息路由:

控件消息(对话框上的消息),通告消息:向父窗口通知事件发生,让父窗口处理

消息映射宏:WM_COMMAND

4.3 菜单项:

标记菜单项CheckMenuItem

缺省菜单项SetDefaultItem

图形标记菜单项SetMenuItemBitmaps

GetSystemmetrics(SM_CXMENUCHECK)

new bitmap

import bitmap:自己制作bitmap,菜单上是13*13

菜单项生效,失效EnableMenuItem

note:m_bAutoMenuEnable = false;命令更新机制,注意变化

菜单设置,取消SetMenu

Detach()

LoadMenu()

①在CMainFrame的OnCrete中初始化菜单:

①GetMenu:   CWnd成员函数,获得菜单栏

②GetSubMenu:获得子菜单

③CheckMenuItem: MF_BYCOMMAND | MF_CHECKED  MF_BYPOSITION | MF_CHECKED

④SetMenuItemBitmap():

①特别注意bitmap的大小

int w = GetSystemMetrics(SM_CXMENUCHECK);  // CX

int h = GetSystemMetrics(SM_CYMENUCHECK);  // CY

QString str;

str.Format("w=%d,h=%d",w,h);

MessageBox(str);

②注意Bitmap对象生存期的问题:

使用static或者成员变量

使用Detach()函数

⑥EnableMenuItem():

// 因为afx的菜单更新机制,下面代码不起作用,需要将CMainFrame构造函数中的m_bAutoMenuEnable =false

GetMenu()->GetSubMenu(3)->EnableMenuItem(0,MF_BYPOSITION | MF_DISABLED | MF_GRAYED);

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

m_bAutoMenuEnable = false;

}

⑦SetMenu()

// OnCreate中的范例代码----------------------------------------------------------------------

GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED);

GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_OPEN,MF_BYCOMMAND | MF_CHECKED);

GetMenu()->GetSubMenu(0)->SetDefaultItem(1,true);

GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED);

CBitmap bitmap;

bitmap.LoadBitmap(IDB_BITMAP1);

GetMenu()->GetSubMenu(2)->SetMenuItemBitmaps(ID_VIEW_STATUS_BAR,MF_BYCOMMAND,&bitmap,&bitmap);

bitmap.Detach();

//-------------------------------------------------------------------------------------------

②菜单的加载和移除

menu.LoadMenu(IDR_XX);

menu.Detach();

③添加组件和控件:  project -  add to project -- components and controls

五、mfc源码分析: appmodule.cpp--WinMain  appcore.cpp--CWinApp

案例:

在view的200,200位置显示:hello visual c++!

考题:

①什么是appwizard

②WinMain在MFC程序中是如何被隐藏的:

//----------------------------

mfc中所有类以C开头

src search WinMain

CXXApp构造函数设置断点

theApp设置断点

积累CWinApp

SRC search CWinApp

APPCORE.cpp(窗口菜单找)

Go Definition of CWinApp构造函数

父类构造函数中的this指向谁

//----------------------------------------

安装目录---vc98--mfc---src---appmodule.cpp

theApp 构造函数--------CWinApp构造函数

_tWinMain和WinMain一模一样,是一个宏

_tWinMain---AfxWinMain----InitInstance----创建文档模板

|          |-------------

|          |-------------显示,更新窗口

|

|---------pThread->Run()

③theApp是如何被分配的

④MFC框架中几个类的作用于相互关系

⑤MFC框架窗口是如何产生和销毁的

⑥窗口类的PreCreateWindow和OnCreate两个函数的关系

⑦Windows窗口与C++中的CWnd类的关系

六、绘图:学会使用GetStockObject

6.1 绘图DC的种类,CBrush,CPen

①HDC hdc = ::GetDC(m_hWnd);

②CDC* cdc = GetDC();

③CClient dc(this);

④CWindowDC dc(this);

dc.SelectObject();

⑤CBrush(CBitmap)

注意:①HGIDOBJ GetStockObject()    sdk全局函数

②CBrush::FromHandle()     转换一个handle到指针

七、改变应用程序外观Style

目标:

①图像:修改图标、光标、背景的三种方法

②工具栏:

|-----增加、删除工具栏按钮;

|-----增加、显示,隐藏工具栏;

③状态栏:

|-----定制状态栏,状态栏添加时间显示;

|-----添加进度条

④操作状态栏:CView中获取状态栏对象的方法

⑤托盘:为应用程序添加启动画面

7.1 修改cs: 框架窗口出现之前修改:  PreCreateWindow中的cs参数

PreCreateWindow(CREATESTRUCT &cs)的cs中修改启动后的初始外观:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

cs.x = 0;

cs.y = 0;

cs.cx = 300;

cs.cy = 250;

return TRUE;

}

②修改图标光标:自定义窗口类:框架窗口预创建中定义窗口类,view中cs.lpszClass="class name"

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

WNDCLASS wndcls;

wndcls.cbClsExtra = 0;

wndcls.cbWndExtra = 0;

wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

wndcls.hCursor = LoadCursor(NULL,IDC_HELP);

wndcls.hIcn = Load(NULL,IID_ERROR); // 使用系统资源,因此,第一个参数为NULL

wndcls.hInstance = AfxGetInstanceHandle();

wndcls.lpfnWndProc = ::DefWindowProc; // 窗口过程必须使用四个参数的窗口过程

wndcls.lpszClassName = "wepull";

wndcls.lpszmenuName = NULL;

wndcls.style = CS_HREDRAW | CS_VREDRAW;

::RegisterClass(&wndcls);

...

// 为修改光标、背景,View的PreCreateWindow中也要加:cs.lpszClass = "your class name"

cs.lpszClass = "your class name";

return TRUE;

}

更方便的方法:

AfxRegisterWndClass()

cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,0,0,LoadIcon(NULL,IDI_CURSOR));

7.2 调用api: 框架窗口运行起来后修改:

CMainFrame::OnCreate()中修改

SetWindowLong

GetWindowLong

SetWindowPos

7.3 调用AfxRegisterWndClass

调用SetClassLong

实例:修改ICON,达到动画效果

①import 自定义ICON

②添加成员:HICON m_hicons[4]

③在CMainFrame::OnCreate中:

// 注意:因为是应用程序自己的ICON,因此第一个参数为application的句柄

m_hicon[0]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON0));

...

SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hicons[0]);

SetTimer(1,1000,NULL);

void CMainFrame::OnTimer(UINT nIDEvent)

{

SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hisons[1]);

CFrameWnd::OnTimer(nIDEvent);

}

注意:①需要将一个ID转换为一个字符串的场合:MAKEINTRESOURCE

②一个类中启动定时器id,只能在这个类中处理该定时器的超时消息;其他类可以启动相同id的定时器

7.4 CToolBar

7.4.1 添加一个toolbar

①CMainFrame的OnCreate中:

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar\n");

return -1;      // fail to create

}

②设置停靠

7.4.2 工具栏图标

7.4.2 通过菜单,显示,隐藏toolbar    ShowControlBar    涉及停靠的时候,需要重新计算布局并停靠

第一种方式:

if(m_newToolBar.IsWindowVisible()){

m_newToolBar.ShowWindow(SW_HIDE);

}else {

m_newToolBar.ShowWindow(SW_SHOW);

}

RecalcLayout();         // 重新计算布局,并停靠

DockControlBar(&m_newToolBar);

第二种方式:

ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);

7.4.2 菜单命令更新

void CMainFrame::OnUpdateNetoobar(CCmdUI* pCmdUI){

pCmdUI->SetCheck(m_newToolBar.IsWindowVisible());

}

7.5 状态栏

①在string table中添加ID

②在CMainFrame的indicators数组中添加IDS_XXX

static UINT indicators[] = {

ID_SEPERATOR,

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL

}

③创建

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar\n");

return -1;      // fail to create

}

④控制状态栏元素:

SetPlaneInfo();  设置状态栏元素属性

SetPlaneText()   设置状态栏元素文本

CommandToIndex() 由ID得到索引

//代码参考------------------------Status Bar Pane  SBP

CClientDC dc(this);

CSize sz = dc.GetTextExtent(str);

m_StatusBar.SetPaneInfo(1,IDS_TIMER,SBPS_NORMAL,size.cx);

// 最后一个参数0必须注意,如果设置为true,则一设置文本,则invalidate,开始更新

m_StatusBar.SetPaneText(1,"text",0);

//-------------------------------------

7.5 状态栏上的进度条:CProgressCtrl

①CMainFrame添加成员:CProgressCtrl ;m_progress

②OnCreate中:

OnCreate(){

...

PostMessage(UM_PROGRESS,0,0);

}

拖动定位出现问题,解决办法:在OnPaint中实现:

void CMainframe::OnPaint(){

CRect rect;

m_wndStatusBar.GetItemRect(2,&rect);// 不能再OnCreate中调用,需要自己定义消息

if(!m_progress.m_hWnd){

m_progress.Create(WS_CHILD|WS_VISIBLE PBS_VERTICAL,rect,&m_wndStatusBar,111);

}else{

m_progress.MoveWindow(rect);

m_progress.SetPos(50);

}

}

7.6 托盘

7.7 右键菜单 : 区分资源ID和菜单项标志ID       TrackPopupMenu()

①添加菜单资源 IDR_xx

②添加消息映射:选择WM_RBUTTONDOWN 或者WM_CONTEXMENU

③编写消息处理函数:

void CMenu2View::OnRButtonDown(UINT nFlags,CPoint point){

CMenu menu;

menu.LoadMenu(IDR_MENU1);

CMenu* pPopup = menu.GetSubMenu(0);

pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,point.x,point.y,

this);

CView::OnRButtonDown(nFlags,point);

}

坐标变换 ClientToScreen()

afx获取的坐标point是客户端坐标,TrackPopupMenu需要的是屏幕坐标

7.8  动态菜单;

AppendMenu        添加菜单

InsertMenu        插入菜单

CreatePopupMenu   创建弹出菜单

①OnCreate中:

CMenu menu;

menu.CreatePopupMenu();   // 创建一个弹出菜单

//GetMenu()->AppendMenu(MF_POPUP | MF_ENABLED,(UINT)menu.m_hWn,"lr");

GetMenu()->InsertMenu(4,MF_BYPOSITION | MY_POPUP,(UINT)m_hWnd,"徐老师");

menu.AppendMenu(MF_STRING,IDM_LL,"LL"); // MF == Menu Flag

menu.AppendMenu(MF_STRING,IDM_YY, "YY");

menu.Detach();

resource.h中:

#define IDM_LL 101      // 也可以自己添加一个菜单资源,然后使用这些菜单资源生成的IDM_XX

#define IDM_YY 102

注意:

①新添加的资源,菜单项,对话框控件,编译后,ID都会出现在resource.h中

②可以添加菜单资源,然后利用这些菜单资源ID,来处理相应用户消息。

②自定义消息映射

|-----头文件中申明:afx_msg void OnLL();

|     afx_msg void OnYY();

|-----源文件中定义:ON_COMMAND(IDM_LL,OnLL)

|                   ON_COMMAND(IDM_YY,OnYY)

`-----源文件中定义函数:void CMainFrame::OnLL(){}

void CMainFrame::OnYY(){}

7.9 动态菜单中出现的几个问题:    通过下面一个电话本案例来展现

①DrawMenuBar(); // 插入菜单后,需要重绘

②GetMenu()      // 获得菜单,this指向的对象必须在拥有菜单才有效

---------------------------------------------------------------

案例:view中输入姓名,电话号码,生成菜单;点击菜单,输出姓名电话

-----------------------------------------------------------------

用到的知识:

①回车ascii码:13=0xd 空格的ascii码:

②处理字符消息,最好使用WM_CHAR消息,可以直接得到nChar字符码

③menu是局部变量,如果不detach,程序会崩溃

//----------------------------------------------------------------

① 在view中添加OnChar消息处理器

void CMENU2View::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

if(0x0d == nChar){

CMenu menu;

menu.CreatePopupMenu();

//GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"通讯录");

// 崩溃的原因:

// ①局部变量,不Detach,程序会崩溃

// ②上面这句话无效:因为this指针指向的类view没有菜单,返回null指针,程序会崩溃

GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"通讯录");

GetParent()->DrawMenuBar(); // 插入菜单后,需要重绘

menu.Detach();

Invalidate(); // 引发窗口重绘

}else{

CClientDC dc(this);

m_str += nChar;

dc.TextOut(0,0,m_str);

}

CView::OnChar(nChar, nRepCnt, nFlags);

}//----------------------------------------------------------------

注意:

①调用Invalidate(),引发窗口重绘

②str.Find(" ");找到字符串中第一个空格的index

②解析m_str中的内容,根据内容,添加菜单项:

|---Resource-> insert menu

|    |---1 右键-->  classwizard

|    |---删除弹出菜单test

|    `---头文件和原件代码移动

`----Resource.h: #define IDM_HELLO #define IDM_PHONE1 #define IDM_PHONE2 ...

注意:上面的方法就是利用资源编辑工具和classwzard为动态菜单添加消息响应,

因为动态菜单项的ID需要自己分配,利用工具可以实现就像操纵静态菜单一样,知识资源ID一样

③在消息处理器重处理菜单消息

8 消息路由问题:

8.1  命令消息和通告消息的路由

①菜单命令消息的路由:

消息映射宏: WM_COMMAND(ID_MENU,OnMENU)

路由顺序:view---doc---frame---app

注意:一定不会交由CAboutDlg处理

②通告消息路由:

控件消息(对话框上的消息),通告消息:向父窗口通知事件发生,让父窗口处理

消息映射宏:WM_COMMAND

8.2  CMainFrame拦截住WM_COMMAND

不让view处理,由MainFrame自己处理,其他消息不拦截

命令消息的路由处理过程:

------------------------------------------------------------------------

AfxWndProc--AfxCallWndProc--WindowProc--OnWndMsg---OnCommand---OnCmdMsg

|

`----OnNotify ---OnCmdMsg

------------------------------------------------------------------------

实现方法:根据以上基本原理,我们可以在OnCommand中截获并处理WM_COMMAND

OnCommand是一个虚函数,可以在MainFrame中override该函数

①添加OnCommand虚函数:

OnCommand(wParam,lParam):

①wParam的低字节Wie消息uMsg, 高位是通告消息(如果来自空间消息)

LOWORD(wParam):得到wParam的低字节序

HIGHWORD(wParam):得到高字节

②lParam:如果来自控件,则是控件ID

GetActiveView():

①在主框架窗口中处理WM_COMMAND,在view中绘制图形,需要获得view对象

bool CMainFrame::OnCommand(WPARAM wParam,LPARAM lParam){

int id = LOWORD(wParam);

CMenu2View* pView = (CMenu2View*)GetActiveView();

if(id>IDM_PHONE1 && id

CClientDC dc(pView);

dc.TextOut(0,0,(pview->m_attr).Get(id-IDM_PHONE1));

return true; // 这里一定要返回,自己范围内的事情做完就返回

}

return CFrameWnd::OnCommand(wParam,lParam);

}

注意:①GetParent(): 视图中获得框架

②GetActiveView():框架中获得视图

8.3 对话框中处理WM_KEYDOWN(按键)消息:  孙鑫深入浅出

9 设计启动画面: project -> add to project -->> componets and controls (组件和控件)

10 工具栏图标

HICON hIcon = NULL;

CImageList imageList;

CSize imageSize(45,45);

//加载icon,创建icon列表,自定义大小---coldimage

imageList.Create(imageSize.cx,imageSize.cy, ILC_COLORDDB|ILC_MASK, 2, 1);

hIcon = (HICON)::LoadImage(NULL,"image\\ToolBarColdImage\\broadcast.ico",

IMAGE_ICON,imageSize.cx,imageSize.cy,LR_LOADFROMFILE);

imageList.Add(hIcon);

hIcon = (HICON)::LoadImage(NULL,"image\\ToolBarColdImage\\stopbroadcast.ico",

IMAGE_ICON,imageSize.cx,imageSize.cy,LR_LOADFROMFILE);

imageList.Add(hIcon);

m_wndToolBar.GetToolBarCtrl().SetImageList(&imageList);

imageList.Detach();

hIcon = NULL;

//加载icon,创建icon列表,自定义大小---hotimage

imageList.Create(imageSize.cx,imageSize.cy, ILC_COLORDDB|ILC_MASK, 2, 1);

hIcon = (HICON)::LoadImage(NULL,"image\\ToolBarHotImage\\broadcast.ico",

IMAGE_ICON,imageSize.cx,imageSize.cy,LR_LOADFROMFILE);

imageList.Add(hIcon);

hIcon = (HICON)::LoadImage(NULL,"image\\ToolBarHotImage\\stopbroadcast.ico",

IMAGE_ICON,imageSize.cx,imageSize.cy,LR_LOADFROMFILE);

imageList.Add(hIcon);

m_wndToolBar.GetToolBarCtrl().SetHotImageList(&imageList);

imageList.Detach();

//设置工具栏按钮文字

m_wndToolBar.SetButtonText(m_wndToolBar.CommandToIndex(ID_BROADCAST_START),"开始广播");

m_wndToolBar.SetButtonText(m_wndToolBar.CommandToIndex(ID_BROADCAST_STOP),"停止广播");

//设置工具栏大小

CRect tbBtnRect;

m_wndToolBar.GetItemRect(0,&tbBtnRect);

m_wndToolBar.SetSizes(tbBtnRect.Size(),CSize(imageSize.cx,imageSize.cy));

11  托盘的实现:

|------- ①添加NOTIFYICONDATA nid;并使用一个成员函数初始化,调用Shell_NotifyIcon(NIM_ADD,&nid);对应①②③

|------- ②处理托盘消息:

|          操作(鼠标,按键...)托盘(tray)会产生消息(这个消息名字自己指定):自定义消息类型,并添加消息处理函数;对应④

|------- ③处理系统命令:最大化,最小化的处理

|------- ④处理close消息:

11.1 添加NOTIFYICONDATA nid成员,并初始化

①添加成员:在CMainFrame中添加成员:NOTIFYICONDATA nid;

②初始化:--------结构体赋值

|-------调用Shell_NotifyIcon(NIM_ADD,&nid)

添加成员函数

void CMainFrame::toTray(){

nid.cbSize=(DWORD)sizeof(NOTIFYICONDATA);

nid.hWnd=this->m_hWnd;

nid.uID=IDR_MAINFRAME;

nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;  // 让哪些成员有效

nid.uCallbackMessage=UM_SHOWTASK;      //自定义的消息名称

nid.hIcon=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDR_MAINFRAME));

strcpy(nid.szTip,"电子教室");          //信息提示条为“计划任务提醒”

Shell_NotifyIcon(NIM_ADD,&nid);//在托盘区添加图标

// ShowWindow(SW_HIDE);//隐藏主窗口

}

在OnCreate中调用函数 toTray()

③自定义消息:#define UM_SHOWTASK WM_USER+10

|----头文件申明     :afx_msg LRESULT OnShowTask(wParam,lParam);

|----源文件消息映射 :ON_MESSAGE(UM_SHOWTASK,OnShowTask)

`----消息处理函数   :LRESULT CMainFrame::OnShowTask(WPARAM wParam,LPARAM lParam)

LRESULT CMainFrame::OnShowTask(WPARAM wParam,LPARAM lParam){

//wParam接收的是图标的ID,而lParam接收的是鼠标的行为

if(wParam!=IDR_MAINFRAME)return 1;

switch(lParam){

case WM_RBUTTONUP://右键起来时弹出快捷菜单,这里只有“显示/关闭”

{

LPPOINT lpoint=new tagPOINT;

::GetCursorPos(lpoint);//得到鼠标位置

CMenu menu,*lpSubMenuTray;

menu.LoadMenu(IDR_RCLICK_MENU);//声明一个弹出式菜单

lpSubMenuTray = menu.GetSubMenu(0);//确定弹出式菜单的位置

lpSubMenuTray->TrackPopupMenu(TPM_LEFTALIGN,lpoint->x,lpoint->y,this); //资源回收

HMENU hmenu=menu.Detach();

menu.DestroyMenu();

delete lpoint;

}

break;

case WM_LBUTTONDBLCLK: {//双击左键的处理

if(m_isShow){

this->ShowWindow(SW_HIDE);//简单的显示主窗口

m_isShow = false;

}

else{

this->ShowWindow(SW_SHOW);//简单的隐藏主窗口

m_isShow = true;

}

}

break;

}

return 0;

}

⑤关闭时候销毁托盘: 重写虚函数

void CMainFrame::OnClose(){

::ShellNofityIcon(NIM_DELETE,&nid); // 销毁托盘

CFrameWnd::OnClose();

}

void CMainFrame::OnWindowShow(){ //   不是特别清楚

ShowWindow(SW_SHOW);

}

⑥最小化时候不在任务栏出现,只出现托盘:处理WM_SYSCOMMAND

afx_msg void CWnd::OnSysCommand(xx,xx)

// This method is called by the framework when the user selects a command

// from the Control menu, or when the user selects the Maximize or the Minimize button

void CMainFrame::OnSysCommand(UINT nID,LPARAM lParam){

f(SC_MINIMIZE = nID){

ShowWindow(SW_HIDE); // 隐藏主窗口,其实不是真正的最小化

}

CFrameWnd::OnSysCommand(nID,lParam);

}

注意:这里的SysCommand指的是 最小化,最大化等消息

12 对话框最大化、最小化按钮的出现:

int CXXXXDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CDialog::OnCreate(lpCreateStruct) == -1)

return -1;

// TODO: Add your specialized creation code here

ModifyStyle(0, WS_MAXIMIZEBOX | WS_MINIMIZEBOX, SWP_FRAMECHANGED);

return 0;

}

标签:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CFile //创建/打开文件 CFile file; file.Open(_T("test.txt"),CFile::modeCreate|CFile::modeNoTruncate|CFile::modeReadWrite); 文件打开模式可组合使用,用“|”隔开,常用的有以下几种: CFile::modeCreate:以新建方式打开,如果文件不存在,新建;如果文件已存在,把该文件长度置零,即清除文件原有内容。 CFile::modeNoTruncate:以追加方式打开,如果文件存在,打开并且不将文件长度置零,如果文件不存在,会抛出异常。一般与CFile::modeCreate一起使用,则文件不存在时,新建一个文件;存在就进行追加操作。 CFile::modeReadWrite:以读写方式打开文件。 CFile::modeRead:只读。 CFile::modeWrite:只写。 //写入数据 CString strValue = "Hello World!"; file.Write(strValue,strValue.GetLength()); //追加数据 file.SeekToEnd(); //将指针移至文件末尾进行追加 file.Write(strValue,strValue.GetLength()); //关闭文件 file.Close(); CStdioFile CStdioFile是CFile的派生类,对文件进行流式操作,对于文本文件的读写很有用处,可按行读取写入。 //写入数据 CString strValue = "Hello World!"; file.WriteString(strValue); //读取数据 CString strRead; file.ReadString(strRead); 当文件存在多行数据需要逐行读取时,可用函数BOOL CStdioFile::ReadString(CString& rString),当遇到"\n "时读取截断,如果文件未读完,返回true,否则返回false。 //逐行读取文件内容,存入strRead while(file.ReadString(strRead)) { ...; } 各种关于文件的操作在程序设计中是十分常见,如果能对其各种操作都了如指掌,就可以根据实际情况找到最佳的解决方案,从而在较短的时间内编写出高效的代码,因而熟练的掌握文件操作是十分重要的。本文将对Visual C++中有关文件操作进行全面的介绍,并对在文件操作中经常遇到的一些疑难问题进行详细的分析。   1.文件的查找   当对一个文件操作时,如果不知道该文件是否存在,就要首先进行查找。MFC中有一个专门用来进行文件查找的类CFileFind,使用它可以方便快捷地进行文件的查找。下面这段代码演示了这个类的最基本使用方法。   CString strFileTitle;   CFileFind finder;   BOOL bWorking = finder.FindFile("C:\\windows\\sysbkup\\*.cab");   while(bWorking)   {   bWorking=finder.FindNextFile();   strFileTitle=finder.GetFileTitle();   }   2.文件的打开/保存对话框   让用户选择文件进行打开和存储操作时,就要用到文件打开/保存对话框。MFC的类CFileDialog用于实现这种功能。使用CFileDialog声明一个对象时,第一个BOOL型参数用于指定文件的打开或保存,当为TRUE时将构造一个文件打开对话框,为FALSE时构造一个文件保存对话框。   在构造CFileDialog对象时,如果在参数中指定了OFN_ALLOWMULTISELECT风格,则在此对话框中可以进行多选操作。此时要重点注意为此CFileDialog对象的m_ofn.lpstrFile分配一块内存,用于存储多选操作所返回的所有文件路径名,如果不进行分配或分配的内存过小就会导致操作失败。下面这段程序演示了文件打开对话框的使用方法。   CFileDialog mFileDlg(TRUE,NULL,NULL,   OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_ALLOWMULTISELECT,   "All Files (*.*)|*.*||",AfxGetMainWnd());   CString str(" ",10000);   mFileDlg.m_ofn.lpstrFile=str.GetBuffer(10000);   str
目 录 1. 概述 3 1.1 实训项目简介 3 1.2 实训功能说明 3 1.2.1 基本功能 3 1.2.2 附加功能 3 2. 相关技术 4 2.1 Windows定时器技术 4 2.2 透明贴图实现技术 4 2.3 CObList链表 5 2.4获取矩形区域 6 2.5使用AfxMessageBox显示游戏过程中的提示信息 6 2.6内存释放 6 2.7 CImageList处理爆炸效果 6 2.8对话框的应用 6 3. 总体设计与详细设计 7 3.1 系统模块划分 7 3.2 主要功能模块 8 3.2.1 系统对象类图 8 3.2.2 系统主程序活动图 9 3.2.3 系统部分流程图 9 4. 编码实现 12 4.1 绘制游戏背景位图程序 12 4.2 飞机大战游戏对象的绘制程序 13 4.3 飞机大战游戏对象战机位置的动态控制 15 4.4 飞机大战游戏对象之间的碰撞实现 17 4.5 游戏界面输出当前信息 19 5. 项目程序测试 20 5.1战机移动及子弹发射模块测试 20 5.2 敌机及炸弹模块测试 20 5.3 爆炸模块测试 20 6. 实训中遇到的主要问题及解决方法 21 7. 实训体会 21 1. 概述 1.1 实训项目简介   本次实训项目是做一个飞机大战的游戏,应用MFC编程,完成一个界面简洁流畅、游戏方式简单,玩起来易于上手的桌面游戏。该飞机大战项目运用的主要技术即是MFC编程中的一些函数、链表思想以及贴图技术。 1.2 实训功能说明 1.2.1 基本功能   (1)设置一个战机具有一定的速度,通过键盘,方向键可控制战机的位置,空格键发射子弹。   (2)界面中敌机出现的位置,以及敌机炸弹的发射均为随机的,敌机与敌机炸弹均具有一定的速度,且随着关卡难度的增大,数量和速度均增加。   (3)对于随机产生的敌机和敌机炸弹,若超过矩形区域,则释放该对象。   (4)添加爆炸效果,包括战机子弹打中敌机爆炸、敌机炸弹打中战机爆炸、战机与敌机相撞爆炸以及战机子弹与敌机炸弹相撞爆炸四种爆炸效果。且爆炸发生后敌机、子弹、炸弹均消失,战机生命值减一。 1.2.2 附加功能   (1) 为游戏界面添加了背景图片,并在战机发射子弹、战机击中敌机、敌机击中战机、以及战机敌机相撞时均添加了背景音效。   (2)为游戏设置了不同的关卡,每个关卡难度不同,敌机与敌机炸弹的速度随着关卡增大而加快,进入第二关以后敌机从上下方均会随机出现,且随机发射炸弹。   (3)第一关卡敌机从上方飞出,速度一定,战机每打掉一直敌机则增加一分,每积十分,则为战机增加一个生命值,当战机得分超过50分则可进入下一关;进入第二、三关时敌机速度加快,分别从上下两方飞出,此时战机每得分20、30分,才会增加一个生命值,得分超过100、150分则进入下一关、通关。   (4) 在游戏界面输出当前游戏进行信息,包括当前得分、当前关卡以及击中敌机数量。   (5)增加了鼠标控制战机位置这一效果,战绩的位置随着鼠标的移动而移动,并且点击鼠标左键可使得战机发射子弹。   (6)实现了暂停游戏的功能,玩家可通过键盘上的‘Z’键,对游戏进行暂停。   (7)通过对话框的弹出可提示玩家是否查看游戏说明、是否进入下一关、是否重新开始等消息,使得玩家可自己选择。 2. 相关技术 2.1 Windows定时器技术   Windows定时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。程序将时间间隔告诉Windows,然后Windows给您的程序发送周期性发生的WM_TIMER消息以表示时间到了。本程序中使用多个定时器,分别控制不同的功能。在MFC的API函数中使用SetTimer()函数设置定时器,设置系统间隔时间,在OnTimer()函数中实现响应定时器的程序。 2.2 透明贴图实现技术   绘制透明位图的关键就是创建一个“掩码”位图(mask bitmap),这个“掩码”位图是一个单色位图,它是位图中图像的一个单色剪影。   在详细介绍实现过程之前先介绍下所使用的画图函数以及函数参数所代表的功能;整个绘制过程需要使用到BitBlt()函数。整个功能的实现过程如下:    (1) 创建一张大小与需要绘制图像相同的位图作为“掩码”位图;    (2) 将新创建的“掩码”位图存储至掩码位图的设备描述表中;    (3) 把位图设备描述表的背景设置成“透明色”,不需要显示的颜色;    (4) 复制粘贴位图到“掩码”位图的设备描述表中,这个时候“掩码”位图设备描述表中存放的位图与位图设备描述表中的位图一样;    (5) 把需要透明绘制的位图与对话框绘图相应区域的背景进行逻辑异或操作绘制到对话框上;    (6) 把“掩码”位图与这个时候对话框相应区域的背景进行逻辑与的操作;    (7) 重复步骤5的操作,把需要透明绘制的位图与对话框绘图相应区域的背景进行逻辑异或操作绘制到对话框上;    (8) 最后把系统的画笔还给系统,删除使用过的GDIObject,释放非空的指针,最后把新建的设备描述表也删除。 2.3 CObList链表 MFC类库中提供了丰富的CObList类的成员函数,此程序主要用到的成员函数如下:(1) 构造函数,为CObject指针构造一个空的列表。 (2) GetHead(),访问链表首部,返回列表中的首元素(列表不能为空)。(3) AddTail(),在列表尾增加一个元素或另一个列表的所有元素。   (4) RemoveAll(),删除列表中所有的元素。   (5) GetNext(),返回列表中尾元素的位置。   (6) GetHeadPosition(),返回列表中首元素的位置。   (7) RemoveAt(),从列表中删除指定位置的元素。   (8) GetCount(),返回列表中的元素数。 在CPlaneGameView.h文件中声明各游戏对象与游戏对象链表:   (1)//创建各游戏对象 CMyPlane *myplane; CEnemy *enemy; CBomb *bomb; CBall *ball; CExplosion *explosion; (2)//创建存储游戏对象的对象链表 CObList ListEnemy; CObList ListMe; CObList ListBomb; CObList ListBall; CObList ListExplosion; 2.4获取矩形区域   首先,使用CRect定义一个对象,然后使用GetClientRect(&对象名)函数,获取界面的矩形区域rect.Width() 为矩形区域的宽度,rect.Height()为矩形区域的高度。   使用IntersectRect(&,&))函数来判断两个源矩形是否有重合的部分。如果有不为空,则返回非零值;否则,返回0。 2.5使用AfxMessageBox显示游戏过程中的提示信息   AfxMessageBox()是模态对话框,你不进行确认时程序是否往下运行时,它会阻塞你当前的线程,除非你程序是多线程的程序,否则只有等待模态对话框被确认。   在MFC中,afxmessagebox是全局的对话框最安全,也最方便。 2.6内存释放   在VC/MFC用CDC绘图时,频繁的刷新,屏幕会出现闪烁的现象,CPU时间占用率相当高,绘图效率极低,很容易出现程序崩溃。及时的释放程序所占用的内存资源是非常重要的。   在程序中使用到的链表、刷子等占用内存资源的对象都要及时的删除。Delete Brush, List.removeall()等。 2.7 CImageList处理爆炸效果   爆炸效果是连续的显示一系列的图片。如果把每一张图片都显示出来的话,占用的时间是非常多的,必然后导致程序的可行性下降。CImageList是一个“图象列表”是相同大小图象的集合,每个图象都可由其基于零的索引来参考。可以用来存放爆炸效果的一张图片,使用Draw()函数来绘制在某拖拉操作中正被拖动的图象,即可连续绘制出多张图片做成的爆炸效果。 2.8对话框的应用    在设置游戏难度、炸弹的速度等,使用对话框进行设置非常方便,又体现出界面的友好。    对话框的应用过程如下:    (1). 资源视图下,添加Dialog对话框。然后添加使用到的控件,并修改控件的ID以便于后面的使用。    (2). 为对话框添加类,在对话框模式下,点击项目,添加类。    (3). 在类视图中,为对话框类添加成员变量(控件变量)。设置变量的名称、类型、最值等信息。    (4). 在资源视图菜单中,选择相应的菜单项,右击添加时间监听程序,设置函数处理程序名称。    (5). 在处理程序函数中添加相应的信息。 3. 总体设计与详细设计 3.1 系统模块划分   该飞机大战游戏程序分为游戏背景位图绘制模块、各游戏对象绘制模块、游戏对象之间的碰撞模块、爆炸效果产生模块、游戏界面输出玩家得分关卡信息模块。   其中在游戏对象绘制模块中,战机是唯一对象,在游戏开始时产生该对象,赋予其固定的生命值,当其与敌机对象、敌机炸弹碰撞时使其生命值减一,直至生命值为零,便删除战机对象。敌机对象与敌机炸弹对象的绘制中采用定时器技术,定时产生。爆炸对象初始化为空,当游戏过程中即时发生碰撞时,在碰撞位置产生爆炸对象,添加到爆炸链表中。 3.2 主要功能模块 3.2.1 系统对象类图            CGameObject是各个游戏对象的抽象父类,继承自CObject类,其他的类:战机类、敌机类、爆炸类、子弹类、炸弹类、文字类都继承了此类。   每个游戏对象类中既继承了来自父类CGameObject的属性,又有自己的特有属性和方法。 3.2.2 系统主程序活动图    3.2.3 系统部分流程图 (1) 该飞机大战游戏执行流程图: (2) 利用定时器定时产生敌机并绘制敌机流程图 4. 编码实现 4.1 绘制游戏背景位图程序   CDC *pDC=GetDC();   //获得矩形区域对象   CRect rect;   GetClientRect(&rect;);   //设备环境对象类----CDC类。   CDC cdc;   //内存中承载临时图像的位图   CBitmap bitmap1;   //该函数创建一个与指定设备兼容的内存设备上下文环境(DC)   cdc.CreateCompatibleDC(pDC);   //该函数创建与指定的设备环境相关的设备兼容的位图。   bitmap1.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());   //该函数选择一对象到指定的设备上下文环境中,该新对象替换先前的相同类型的对象。   CBitmap *pOldBit=cdc.SelectObject(&bitmap1;);   //用固定的固体色填充文本矩形框   cdc.FillSolidRect(rect,RGB(51,255,255)); //添加背景图片   CBitmap bitmap_BackGround;   bitmap_BackGround.LoadBitmap(IDB_BACKGROUND);   BITMAP bimap2;//位图图像   bitmap_BackGround.GetBitmap(&bimap2;);   CDC cdc_BackGround;//定义一个兼容的DC   cdc_BackGround.CreateCompatibleDC(&cdc;);//创建DC   CBitmap*Old=cdc_BackGround.SelectObject(&bitmap;_BackGround);   cdc.StretchBlt(0,0,rect.Width(),rect.Height(),&cdc;_BackGround,0,0,bimap2.bmWidth,bimap2.bmHeight,SRCCOPY); 4.2 飞机大战游戏对象的绘制程序 //画战机对象(唯一) if(myplane!= NULL) { myplane->Draw(&cdc;,TRUE); } //设置定时器,随机添加敌机,敌机随机发射炸弹,此时敌机速度与数量和关卡有关 SetTimer(2,300,NULL);//敌机产生的定时器 SetTimer(3,500,NULL);//敌机炸弹产生的定时器   if(myplane!=NULL&& is_Pause == 0) { switch(nIDEvent) { case 2://设置定时器产生敌机 { if(pass_Num == 1)//第一关 { int motion =1;//设置敌机的方向,从上方飞出 CEnemy *enemy=new CEnemy(motion); ListEnemy.AddTail(enemy);//随机产生敌机 }//if else if(pass_Num >= 2)//第一关以后的关卡 { int motion1 = 1; //设置敌机的方向,从上方飞出 CEnemy *enemy1=new CEnemy(motion1); enemy1->SetSpeed_en((rand()%5 +1)* pass_Num); ListEnemy.AddTail(enemy1);//随机产生敌机 int motion2 = -1;//设置敌机的方向,从下方飞出 CEnemy *enemy2=new CEnemy(motion2); enemy2->SetSpeed_en((rand()%5 +1)* pass_Num); ListEnemy.AddTail(enemy2);//随机产生敌机 }//else if }//case break; }//switch //判断产生的敌机是否出界,若已经出界,则删除该敌机 POSITION posEn=NULL,posEn_t=NULL; posEn=ListEnemy.GetHeadPosition(); int motion = 1; while(posEn!=NULL) { posEn_t=posEn; CEnemy *enemy= (CEnemy *)ListEnemy.GetNext(posEn); //判断敌机是否出界 if(enemy->GetPoint().xGetPoint().x>rect.right ||enemy->GetPoint().yGetPoint().y>rect.bottom) { ListEnemy.RemoveAt(posEn_t); delete enemy; }//if else { enemy->Draw(&cdc;,TRUE); switch(nIDEvent) { case 3://设置定时器产生敌机炸弹 {   CBall*ball=newCBall(enemy->GetPoint().x+17,   enemy->GetPoint().y+30,enemy->GetMotion()); ListBall.AddTail(ball); }//case break; }//switch }//else }//while //判断产生的敌机炸弹是否出界,若已经出界,则删除该敌机炸弹 POSITION posball=NULL,posball_t=NULL; posball= ListBall.GetHeadPosition(); while(posball!=NULL) { posball_t=posball; ball= (CBall *) ListBall.GetNext(posball); if( ball->GetPoint().xGetPoint().x>rect.right || ball->GetPoint().yGetPoint().y>rect.bottom) { ListBall.RemoveAt(posball_t); delete ball; }//if else { ball->Draw(&cdc;,1); }//else }//while }//if 4.3 飞机大战游戏对象战机位置的动态控制 if(myplane!= NULL) { myplane->Draw(&cdc;,TRUE); } //获得键盘消息,战机位置响应,战机速度speed为30 if((GetKeyState(VK_UP) <0 || GetKeyState('W') GetPoint().ySetPoint( myplane->GetPoint().x,rect.bottom); else myplane->SetPoint(myplane->GetPoint().x,( myplane->GetPoint().y - speed) ); }//if if((GetKeyState(VK_DOWN) <0|| GetKeyState('S') < 0)&& is_Pause== 0)//下方向键{}//if if((GetKeyState(VK_LEFT) <0|| GetKeyState('A') < 0)&& is_Pause== 0)//左方向键{}//if if((GetKeyState(VK_RIGHT) <0|| GetKeyState('D') < 0)&& is_Pause== 0)//右方向键{}//if if((GetKeyState(VK_SPACE)GetPoint().x, myplane->GetPoint().y,1); ListBomb.AddTail(BombOne); CBomb*BombTwo=newCBomb(myplane->GetPoint().x+35, myplane->GetPoint().y,1); ListBomb.AddTail(BombTwo); PlaySound((LPCTSTR)IDR_WAVE2,AfxGetInstanceHandle(),SND_RESOURCE |SND_ASYNC); }//if if(GetKeyState('Z')SetPoint(point.x,point.y); } //鼠标控制战机,发射战机子弹 void CPlaneGameView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CView::OnLButtonDown(nFlags, point); if( is_Pause == 0) { CBomb *BombOne=new CBomb( myplane->GetPoint().x, myplane->GetPoint().y,1); PlaySound((LPCTSTR)IDR_WAVE2, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); ListBomb.AddTail(BombOne); CBomb *BombTwo=new CBomb( myplane->GetPoint().x+35, myplane->GetPoint().y,1); ListBomb.AddTail(BombTwo); } } 4.4 飞机大战游戏对象之间的碰撞实现 本飞机大战游戏中的碰撞考虑了飞机子弹打中敌机、敌机炸弹打中战机、战机与敌机相撞、敌机炸弹与战机子弹相撞四种情况,根据游戏对象的矩形区域是否有交叉,而确认两者是否相撞,而产生爆炸对象,添加到爆炸链表中。以战机与敌机相撞为例: if(myplane != NULL&& is_Pause== 0) { POSITION enemyPos,enemyTemp; for(enemyPos= ListEnemy.GetHeadPosition();(enemyTemp=enemyPos)!=NULL;) { enemy =(CEnemy *) ListEnemy.GetNext(enemyPos); //获得敌机的矩形区域 CRect enemyRect = enemy->GetRect(); //获得战机的矩形区域 CRect myPlaneRect = myplane->GetRect(); //判断两个矩形区域是否有交接 CRect tempRect; if(tempRect.IntersectRect(&enemyRect;,myPlaneRect)) { CExplosion *explosion = new CExplosion( enemy->GetPoint().x+18 , enemy->GetPoint().y + 18); PlaySound((LPCTSTR)IDR_WAVE,AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); ListExplosion.AddTail(explosion); //战机生命值减一 lifeNum_Me--; //删除敌机 ListEnemy.RemoveAt(enemyTemp); delete enemy; if(lifeNum_Me == 0) { //删除战机对象 delete myplane; myplane=NULL; }//if break; }//if }//for }//if 战机子弹打中敌机、敌机炸弹打中战机以及战机子弹与敌机炸弹对象的碰撞实现同上。 4.5 游戏界面输出当前信息   if(myplane != NULL&& is_Pause== 0)    {    HFONT font;    font=CreateFont(20,10,0,0,0,0,0,0,0,0,0,100,10,0);    cdc.SelectObject(font);    CString str;    cdc.SetTextColor(RGB(255,0,0));    str.Format(_T("当前关卡:%d"),pass_Num);    cdc.TextOutW(10,20,str);    str.Format(_T("当前得分:%d"),score_Me);    cdc.TextOutW(10,40,str);    str.Format(_T("剩余生命:%d"),lifeNum_Me);    cdc.TextOutW(10,60,str);    }//if    if(myplane !=NULL && lifeNum_Me >0)    {    if(score_Me > 10*count_Life*pass_Num)    {    lifeNum_Me++;//生命值加1    count_Life++;//已增加生命值加1    }    } 游戏进入下一关,以及结束游戏界面设计代码与上类似。 5. 项目程序测试 5.1战机移动及子弹发射模块测试 用例 预期结果 实际结果 问题描述 修改方案 点击A键或鼠标左移 战机向左移动 战机向左移动 点击D键或鼠标右移 战机向右移动 战机向右移动 点击W键或鼠标上移 战机向上移动 战机向上移动 点击S键或鼠标上移 战机向下移动 战机向下移动 5.2敌机及炸弹模块测试 用例 预期结果 实际结果 问题描述 修改方案 玩家得分50(通过第一关后) 敌机从上下两方向均可飞出,且速度不断增加 敌机从上下两方向均可飞出,且速度不断增加 5.3爆炸模块测试 用例 预期结果 实际结果 问题描述 修改方案 战机子弹打中敌机 敌机位置处爆炸,敌机消失,战机生命-1 敌机位置处爆炸,敌机消失,战机生命-1 敌机炸弹打中战机 战机位置处爆炸,战机生命-1 战机位置处爆炸,战机生命-1 敌机战机相撞 敌机位置处爆炸,敌机消失,战机生命-1 敌机位置处爆炸,敌机消失,战机生命-1 战机子弹与敌机炸弹相撞 敌机炸弹处爆炸,子弹与炸弹均消失消失 敌机炸弹处爆炸,子弹与炸弹均消失消失 战机生命值==0 战机消失,GameOver或者过关 战机消失,GameOver或者过关 6. 实训中遇到的主要问题及解决方法   (1)由于对C++的面向对象的思想和逻辑思路不熟悉,不明白其中的封装之类的以及多态的思想,致使开始真正的进入实训接触到项目时没有开发思路,通过逐步查询书籍整理C++面向对象编程思路,才逐步理清项目的开发步骤。   (2)本飞机大战的游戏要求使用链表实现各游戏对象的存储和释放,由于链表知识掌握的不牢固,使用起来总是出现这样那样的错误,给整个游戏开发带来了很大的障碍,通过不断的调试修改,最终使程序正确运行。   (3)在绘制各种游戏对象—敌机和敌机炸弹时,开始使用随机函数,画出敌机时而很少,总是打不到预定的效果,后来经过修改使用定时器产生敌机和敌机炸弹,使整个游戏更加人性化。 7. 实训体会 (1)在本次飞机大战游戏项目的开发过程中遇到很多问题,大部分是因为对MFC编程的不熟悉以及链表掌握不牢固所导致的。 (2)MFC编程中有很多可以直接调用的函数,由于之前缺乏对这方面编程的经验,以至于本次项目开发过程中走了很多弯路。 (3)通过寻求老师和同学的帮助,解决了开发中遇到的很多问题,也提升了自己调试错误的能力。 (4)通过本次实训,使我熟悉了MFC编程技术、巩固了链表的使用方法并加深了对面向对象编程思想的理解,对以后程序的编写打下了良好的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值