之前学了使用windows编程显示界面,在这首先复习一下其主要原理,主要有一下几个步骤:
1、设计窗口:创建WNDCLASS类
2、注册窗口:RegisterClass
3、创建窗口:CreateWindow
4、显示与更新:ShowWindow和UpdateWindow
5、通过循环取消息:GetMessage、TranslateMessage、DispatchMessage
6、处理消息(窗口过程):WindowProc(可自定义名字)
//本例子实现了底层方法实现显示界面的全过程
#include //底层实现窗口的头文件
//6处理窗口过程
//CALLBACK 代表__stdcall 参数的传递顺序:从右到左 以此入栈,并且在函数返回前 清空堆栈
LRESULT CALLBACK WindowProc(
HWND hwnd, //消息所属的窗口句柄
UINT uMsg, //具体消息名称 WM_XXXX 消息名
WPARAM wParam, //键盘附加消息
LPARAM lParam //鼠标附加消息
)
{
switch (uMsg)
{
case WM_CLOSE:
//所有xxxWindow为结尾的方法 ,都不会进入到消息队列中,而是直接执行
DestroyWindow(hwnd); //DestroyWindow 发送另一个消息 WM_DESTROY
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN: //鼠标左键按下
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
char buf[1024];
wsprintf(buf,TEXT("x = %d,y = %d"), xPos, yPos);
MessageBox(hwnd, buf, TEXT("鼠标左键按下"), MB_OK);
break;
}
case WM_KEYDOWN: //键盘
MessageBox(hwnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
break;
case WM_PAINT: //绘图
{
PAINTSTRUCT ps; //绘图结构体
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 100, 100, TEXT("HELLO"), strlen("HELLO"));
EndPaint(hwnd, &ps);
}
break;
}
//返回值用默认处理方式
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//程序入口函数
//WINAPI 代表__stdcall 参数的传递顺序:从右到左 以此入栈,并且在函数返回前 清空堆栈
int WINAPI WinMain(
HINSTANCE hInstance, //应用程序实例句柄
HINSTANCE hPrevInstance, //上一个应用程序句柄,在win32环境下,参数一般为NULL,不起作用了
LPSTR lpCmdLine, //char * argv[]
int nShowCmd) //显示命令 最大化、最小化 正常
{
//1、设计窗口
//2、注册窗口
//3、创建窗口
//4、显示和更新
//5、通过循环取消息
//6、处理消息 (窗口过程)
//1、设计窗口
WNDCLASS wc;
wc.cbClsExtra = 0; //类的额外的内存
wc.cbWndExtra = 0; //窗口额外的内存
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设置背景
wc.hCursor = LoadCursor(NULL, IDC_HAND); //设置光标 如果第一个参数为NULL,代表使用系统提供的光标
wc.hIcon = LoadIcon(NULL, IDI_ERROR); //图标 如果第一个参数为NULL,代表使用系统提供的光标
wc.hInstance = hInstance; //应用程序实例句柄 传入WinMain中的形参即可
wc.lpfnWndProc = WindowProc; //回调函数 窗口过程
wc.lpszClassName = TEXT("WIN"); //指定窗口类名称
wc.lpszMenuName = NULL; //菜单名称
wc.style = 0; //显示风格 0代表默认风格
//2、注册窗口类
RegisterClass(&wc);
//3、创建窗口
/*
lpClassName, 类名
lpWindowName, 标题名
dwStyle, WS_OVERLAPPEDWINDOW 风格
x, 显示坐标 CW_USEDEFAULT 默认值
y,
nWidth, 宽高
nHeight,
hWndParent, 父窗口 NULL
hMenu, 菜单 NULL
hInstance, 实例句柄 hInstance
lpParam) 附加值 NULL
*/
HWND hwnd = CreateWindow(wc.lpszClassName, TEXT("WINDOWS"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
//4、 显示和更新
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
//5、 通过循环取消息
/*
HWND hwnd; 主窗口句柄
UINT message; 具体消息名称
WPARAM wParam; 附加消息 键盘消息
LPARAM lParam; 附加消息 鼠标消息
DWORD time; 消息产生时间
POINT pt; 附加消息 鼠标消息 x y
*/
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
/*
_Out_ LPMSG lpMsg, 消息
_In_opt_ HWND hWnd, 捕获窗口 填NULL代表捕获所有的窗口
_In_ UINT wMsgFilterMin, //最小和最大的过滤的消息 一般填入0
_In_ UINT wMsgFilterMax) //填0代表捕获所有消息
*/
//if (GetMessage(&msg, NULL, 0, 0) == FALSE)
//{
// break;
//}
//翻译消息
//如将WM_PAINT 翻译成计算机可以运行的代码
TranslateMessage(&msg);
//不为false
//分发消息
DispatchMessage(&msg);
}
return 0;
}
windows中消息结构体如下所示:
typedef struct tagMSG {
HWND hWnd; //消息所属的窗口句柄
UINT message; //消息标识符,是一个数值,对应宏定义中WM_xxx的数值
WPARAM wParam;//附加消息 键盘消息
LPARAM lParam; //附加消息 鼠标消息
DWORD time; //产生消息的时间
POINT pt;//表示产生这个消息时光标或鼠标的坐标
} MSG;
在windows界面编程中,实现对于消息的处理是以消息队列的方式实现的。
比如当用户是用鼠标点击时,操作系统将点击这个动作包装成一个消息,投递到当前的应用程序消息序列中,等待处理。然后应用程序通过一个消息循环不断地从消息队列中取出消息,并进行响应。有一个专门负责处理消息的函数称为窗口过程函数,在上面的例子中WindowProc函数就是窗口过程函数,负责处理传入的消息。如下图所示,为windows消息处理的过程图。
建立消息循环的过程:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage函数负责从消息队列中获取消息,当该函数接收到WM_QUIT时函数返回零值。
TranslateMessage函数用于翻译、处理和转换消息并把新消息投放到消息队列中,并且此过程不会影响原来的消息队列。
DispatchMessage用于把收到的消息传到窗口回调函数进行分析和处理。即将消息传递给操作系统,让操作系统调用窗口回调函数,来对信息进行处理。
微软基础类库(Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
MFC把Windows SDK API函数包装成了几百个类,MFC给Windows操作系统提供了面向对象的接口,支持可重用性、自包含性以及其他OPP原则。
MFC.h:
#include //mfc头文件
class MyApp : public CWinApp //CWinApp应用程序类
{
public:
//MFC程序入口
virtual BOOL InitInstance();
private:
};
class MyFrame: public CFrameWnd //CFrameWnd 窗口框架类
{
public:
//构造函数
MyFrame();
//声明宏 提供消息映射机制
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDown(UINT, CPoint);
afx_msg void OnChar(UINT, UINT, UINT);
afx_msg void OnPaint();
};
MFC.cpp:
#include "MFC.h"//对应类声明的头文件
MyApp app; //全局应用程序对象,有且只有一个
BOOL MyApp::InitInstance()//程序入口地址
{
//1、创建框架类对象
MyFrame * frame = new MyFrame;
//2、显示窗口
frame->ShowWindow(SW_SHOWNORMAL);
//3、更新窗口
frame->UpdateWindow();
m_pMainWnd = frame;//4、保存指向应用程序主窗口的指针,即保存框架类指针
return TRUE;//返回正常的初始化
}
//分界宏
BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd)
ON_WM_LBUTTONDOWN()//鼠标左键按下
ON_WM_CHAR() //键盘
ON_WM_PAINT() //绘图宏
END_MESSAGE_MAP()
MyFrame::MyFrame()
{
Create(NULL, TEXT("mfc"));//创建窗口
}
void MyFrame::OnLButtonDown(UINT, CPoint point)
{
/*
TCHAR buf[1024];
wsprintf(buf, TEXT("x = %d, y = %d"), point.x, point.y);
MessageBox(buf);
*/
//mfc中的字符串类型为 CString
CString str;
str.Format(TEXT("x = %d,,,,y =%d"), point.x, point.y);
MessageBox(str);
}
void MyFrame::OnChar(UINT key, UINT, UINT)
{
CString str;
str.Format(TEXT("按下了 %c 键"), key);
MessageBox(str);
}
void MyFrame::OnPaint()
{
CPaintDC dc(this);
dc.TextOutA(300, 300, TEXT("hahahaha"));
//画椭圆
dc.Ellipse(10,10,100,100);
dc.Rectangle(100, 100, 200, 200);
}
注意:若运行以上代码,必须右键项目—>属性—>配置属性—>常规,将项目默认值中MFC的使用设置为在共享DLL中使用MFC
执行流程:
1、程序开始时,先实例化应用程序对象(有且只有一个)
2、执行程序的入口函数InitInstance()
3、给框架类MyFrame对象动态分配空间(自动调用它的构造函数),在其构造函数内部,通过CWnd::Create创建窗口
4、框架类对象显示窗口CWnd::ShowWindow
5、框架类对象更新窗口CWnd::UpdateWindow
6、保存框架类对象指针CWinThread::m_pMainWnd
CFrameWnd是从CWnd(窗口基类)派生出来的。CFrameWnd模仿框架窗口行为,我们可以把框架窗口作为顶层窗口看待,它是应用程序与外部世界的主要接口。
可以调用类中的CWnd::Create或CWnd::CreateEX函数创建
virtual BOOL Create(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CCreateContext* pContext = NULL
);
后六个参数有默认值,lpszClassName指定了窗口基于WNDCLASS类的名称,为此将其设定为NULL将创建一个基于已注册的WNDCLASS类的默认框架窗口。lpszWindowName参数指定将在窗口的标题栏出现的文字。
MFC应用程序的核心就是基于CWinApp类的应用程序对象。CWinApp提供了消息循环来检索消息并将消息调度给应用程序窗口。它还包括可被覆盖的、用来自定义应用程序行为的主要虚函数。
一个MFC程序可以有且仅有一个应用程序对象,此对象必须声明为在全局范围内有效,以便它在程序开始时即在内存中被实例化。
消息映射是一个将消息和成员函数相互关联的表。例如在上面的程序中,我们用鼠标左键点击窗口获取坐标,会产生一个WM_LBUTTONDOWN消息,在MFC的映射入口表达形式为ON_WM_LBUTTONDOWN( ),其对应的函数为afx_msg void OnLButtonDown( UINT, CPoint ),在点击鼠标左键后,就会调用该函数,并执行相应的操作。
消息映射添加到一个类中有如下几步:
1)所操作类中,声明消息映射宏。
2)通过放置标识消息的宏来执行消息映射,相应的类将在对BEGIN_MESSAGE_MAP和END_MESSAGE_MAP的调用之间处理消息。
3)对应消息处理函数分别在类中声明,在类外写详细的步骤。
对于上面的例子,要对按下的键盘中的键值进行观察
1)在MFC.h头文件中的CFrameWnd窗口框架类中声明宏
class MyFrame: public CFrameWnd //窗口框架类
{
public:
MyFrame();
//声明宏 提供消息映射机制
DECLARE_MESSAGE_MAP()
};
2)对于键盘的按下,产生的消息类型为WM_CHAR(),则先设置消息映射分界宏,向其中加入该消息在MFC中相应的映射入口。
在MFC.cpp中加入分界宏和映射入口
//分界宏
BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd)//(子类名,父类名)
ON_WM_CHAR() //键盘
END_MESSAGE_MAP()
3)在类中声明消息映射的函数原型,即在MFC.h中的CFrameWnd窗口框架类中声明函数原型:
class MyFrame: public CFrameWnd //窗口框架类
{
public:
MyFrame();
//声明宏 提供消息映射机制
DECLARE_MESSAGE_MAP()
//声明消息映射的函数原型
afx_msg void OnChar(UINT, UINT, UINT);
};
在类外进行具体实现,即在MFC.cpp中实现函数原型的具体操作:
void MyFrame::OnChar(UINT key, UINT, UINT)
{
CString str;//CString类型为MFC中的字符串类型
str.Format(TEXT("按下了 %c 键"), key);//xx.Format为CString类型的初始化方式
MessageBox(str);
}