MFC---序幕

许多同学一开始就进行MFC的学习,或是通过看一些教学视频,学的一二,由于他不知道其中的原理,导致了很多人无法灵活的使用mfc,或是遇到问题的时候,无从下手,这个时候呢,很多人总是通过加入一些聊天群,通过问别人来获取怎么做,这种都是小问题,如果是相对大一点的问题,当你向别人提问的时候,别人常常无法帮助到你,因为只有你才熟悉自己的程序,自己的设计的想法。在我开始讲mfc之前,我用这篇文章来讲讲windows窗口程序的基本结构,在后面的文章中,我会讲到mfc的各个方面的知识,对于新手学习mfc来说,希望能帮助到他们。我是通过自己看msdn,学习的windows编程,在学习之初,也遇到了一些问题,但是后来都解决了,也接触了一些教程,不管是视频教程或是书本上的,我将从不同于他们的角度和方法去阐释mfc。之所以我要用这一章来讲,因为在后面我们学习的其他方面的编程,将总是会看到这些影子。这是一个最基本的基础。

1窗口类

窗口类并不是一个我们c++中说的类的概念,我觉得应该说成是一个结构体,更合适一些,窗口类有一些成员,通过对相应的成员进行赋值,对我们将要的创建的窗口设置一些基本的属性,一个窗口类注册一次,可以多次使用,也就是我们只注册一个窗口类,就可以用这个窗口类创建多个窗口,相互是独立的,具有独立的窗口句柄,但是这样并不推荐,因为,一般都是一个窗口对应一个窗口,为什么呢,因为窗口类中有一个成员是设置窗口过程的函数的地址,只要用这个窗口类创建的窗口,获取的消息,都会发送到同一个窗口过程当中,这样一来,就极为不方便,我们还要在窗口过程中判断窗口句柄来判断是哪个窗口的消息,所以不推荐这样,下面我们来看看窗口类:

WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;//窗口样式
wcex.lpfnWndProc= WndProc;//窗口过程函数,这个是我们自定义的函数,函数名我们可以自己取,但是调用规范必须要遵守,下面会讲什么是调用规范
wcex.cbClsExtra= 0;保留的,必须是0
wcex.cbWndExtra= 0;保留的,必须是0
wcex.hInstance= hInstance;//实例句柄,这个是应用程序实例,代表了这个程序
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32TEST));//图标,这个图标用于窗口显示的时候,标题栏上的图标和任务栏的图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);鼠标样式,也就是说,当窗口激活的时候,鼠标在这个窗口的客户区的时候的指针形状
wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);背景,下面会对这个进行详细讲述
wcex.lpszMenuName= MAKEINTRESOURCE(IDC_WIN32TEST);菜单下面会详细讲述
wcex.lpszClassName= L“classname”;窗口类的名字,是一个字符串,这个窗口了被注册后,这个窗口类结构就被销毁了,那么之后就用这个这个名字来确定和使用这个窗口类,在后面创建窗口的时候,就会用到这个名字
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));如果我们设置了上面的图标成员,这个可以设置为NULL,如果设置为NULL,系统会自动搜索可用图标资源来设置这个图标,这个是设置一个小图标,和先前设置的图标只是尺寸上的差别


return RegisterClassEx(&wcex);注册窗口类

这些窗口类的成员的含义和可以附的值,在MSDN中是有的,下面详细讲讲上面提到的要详细解释的几个:首先是窗口过程函数,窗口过程是很关键的一个函数,我们对软件开发功能,基本都是在这个当中进行的,我们在当中处理消息和发送消息等等动作。后面窗口过程会单独讲解,这里讲讲,调用规范的问题,有的地方是说调用规则是什么意思呢,如下:

LRESULT CALLBACK WindowProc(
  _In_  HWND hwnd,
  _In_  UINT uMsg,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
);
在windows中有很多这样的函数,我们可以改变函数名,但是函数名前面的确不应该省略( LRESULT CALLBACK),参数就更不必说了,顺序也不可以有改变,它定义了windows对这个函数的一些调用规范,类似的,还有winmain函数,我们在声明的时候,也不应该省略winmain前面的部分,winmain更加特殊,名字也不可以改,它是程序的入口点,当程序开始的时候,除了全局的变量的初始化,实际真正的执行是从winmain开始的,如下:

int CALLBACK WinMain(
  _In_  HINSTANCE hInstance,
  _In_  HINSTANCE hPrevInstance,
  _In_  LPSTR lpCmdLine,
  _In_  int nCmdShow
);
接着,我们说说窗口背景,为什么要说说这个呢,因为很多人不知道窗口显示的原理,当然 主要是指客户区,因为非客户区是系统管理的,窗口开始绘制的时候,首先,是画一个背景,简单的说就是用一个位图或是画刷(纯色)来刷一个窗口背景,有点像我们的黑板,我们先把墙上刷黑,然后再在上面写字,画图,窗口也是这样,先有一个背景,在窗口类中的这个设置窗口背景的成员其实就是设置了一个默认用什么颜色来刷我们的背景的什么颜色的画刷,就用这个颜色来刷我们的背景,比如说我这里设置的是白色,那窗口建立的时候,就是白色。另外,在对话框程序中,我们看到控件,窗口背景都是灰色的,也是这个道理,不同的是,对话框是系统注册的一个窗口类,而不是我们注册。那么我们要在程序运行的时候,改变窗口的背景,应该怎么做呢,其实在窗口要开始刷背景的时候,就发送了一个消息WM_CTLCOLORDLG,在这里,wParam参数就是一个dc,改变这个dc中的字体,画笔等等,就可以改变控件和对话框上字体和别的东西,我们可以创建一个画刷,然后返回这个画刷,再将这个消息传给默认窗口过程,就可以改变刷的背景,当然,这个消息从字面上看,就知道是针对对话框的,如果我们不再这里处理,还可以处理一个消息,这个消息对普通窗口和对话框窗口都是有效的。那就是WM_ERASEBKGND,从字面意思也可以看到,就是要擦除背景,在这里我们就像在处理paint消息一样,在这里面画我们想要的背景,可以是纯色,也可以是位图。如果我们处理了这个消息,那么我们应该返回一个非0值,告诉系统不用再用窗口类中的设置的画刷来刷窗口背景了,如果你返回0,系统还会用窗口类中的画刷来刷窗口背景。

MFC---序幕_第1张图片 MFC---序幕_第2张图片 MFC---序幕_第3张图片

case WM_ERASEBKGND :
{
HDC m_hdc=(HDC)wParam;
HDC m_chdc=CreateCompatibleDC(m_hdc);
HGDIOBJ old=SelectObject(m_chdc,bitmap1);
BitBlt(m_hdc,0,0,200,200,m_chdc,0,0,SRCCOPY);
SelectObject(m_chdc,old);
DeleteObject (m_chdc);
return 1;
}

第一个图是原来的,用的白色背景画刷,第二个图,我用的是灰色的,第三个图是,使用了WM_ERASEBKGND消息,呈现了不同的背景。这个背景每次在窗口需要重新绘制的时候,都会绘制一遍,如在窗口大小改变,窗口有无效区存在,最大化和最小化的时候。

下面再说说菜单,菜单的种类有很多,这里这种是静态的方式,就是在窗口创建的时候就显示了的,在上面的窗口中,我们看见了菜单,这个菜单是我们在资源编辑器中创建的,为什么我们在上面的复制中用了 MAKEINTRESOURCE这个宏,因为我们看,这个成员函数要求的是资源的名称,而我们常常知道的,在创建资源的时候,是资源的ID,那么这个宏就可以为我们将资源的ID转换成对应的资源名称,在以后我们的别的编程中也会遇到这样的情况。菜单除了在窗口类中就指定以外,我们还可以在调用函数CreateWindowEx或是CreateWindow的时候,可以指定菜单,不过这时候是是需要的菜单句柄,我们可以用LoadMenu就可以获取菜单的句柄,这个函数的参数就是资源名称和程序实例句柄。通过这种方式也可以添加上面那种静态的菜单。

2注册窗口类

在上面的代码,我们也看到,注册窗口类就一个函数,很简单:return RegisterClassEx(&wcex);注册窗口类,这样注册了窗口类之后,我们就可以用这个窗口类创建我们的窗口了。在mfc中,我们有时候也要像这样注册一个我们自己的窗口类,如,我们在使用资源编辑器中的自定义控件的时候,就要我们自己指定窗口类别,这里我们就可以知道你个我们自己注册的窗口类名称。

3创建窗口

 hWnd = CreateWindow(L"classname", szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

在这里我们就看到了,第一个参数就是我们注册的窗口类的名称,第二个参数是标题栏,第三个是窗口的样式,接着四个参数是窗口的坐标位置和大小,再接着是父窗口句柄,如果有的话,再接着是菜单句柄,上面我们已经在窗口类指定了,这里就不指定了,最后一个是指针,指向一个变量,这个变量可以通过CREATESTRUCT结构体一个成员函数传递到WM_CREATE消息中被使用。这里我们需要特别注意的是,当这个函数成功以后,就会返回窗口句柄,这个时候,窗口已经在内存中创建了,但是并没有显示出来,同时还发送了一个WM_CREATE消息,这里也行有人会问,还没有进入消息循环,怎么就开始收到消息了呢,因为消息是分队列消息和非队列消息,非队列消息时不经过消息循环直接发送到窗口过程的。为什么窗口创建了确不显示呢,而是发送了一个WM_CREATE消息呢,因为这个创建只是创建了主窗口,也许我们窗口上还有控件或是临时修改窗口的一些属性和样式呢,如果我们现在就显示,那不就是看着控件在窗口上一个一个的绘制了吗,这样不好,而是一次性创建完成了显示给我们看。这个消息的lParam参数是一个CREATESTRUCT结构体里面包含了前面我们设置的很多属性信息,包括窗口样式,在这里我们可以进行修改和添加一些样式。

4显示和更新窗口

 ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

在showwindow的时候,窗口就显示了,以前我看了一个中科院的一个将windows开发的视频教程,那个老师在讲到这个地方的时候呢,没有将明白为什么要又显示,有更新的,为什么不一次性搞定呢,我自己有我自己的理解,showwindow其实是为了完成我们在WM_CREATE消息中我们可能做的修改的一次确认显示,在WM_CREATE的时候,是显示的主窗口,至于控件,我们是在WM_CREATE中创建的,因此,再这个函数的时候,就是讲主窗口和控件一并显示了出来。为什么后来又来了一个updatewindow这个函数呢,因为前面的过程就是显示窗口,还没有显示窗口的客户区的内容,一些文字或是图像,所以当进行到这个函数的时候,就第一次调用了WM_PAINT消息,绘制出客户区的内容。到此,窗口以及窗口的内容才完整的显示出来了。

5消息循环

每个具有窗口界面的线程应该都有一个消息循环,用来从这个线程中获取这个窗口的队列消息,从消息队列中获取消息,发送给窗口过程,然后我们就可以通过处理消息来响应来自用户的操作和系统发送的一些消息。当消息循环退出的时候,线程也就结束了,对于主线程,也就是我们winmain函数中的线程,退出的时候,这个进程也将退出。

hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32TEST));//快捷键表


// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))检查是否是快捷键消息
{
TranslateMessage(&msg);//将虚拟键消息翻译成字符消息
DispatchMessage(&msg);//将消息送到指定的窗口过程函数中
}
}

这里需要特别注意的是我们看到GetMessage这个函数的第二个参数是一个窗口句柄,但是为什么我们这里要设置为NULL呢,因为设置为NULL,表示是接受当前线程所有的消息,之所以要设置为这样,最主要的目的还是为了处理当我们关闭程序的时候,收到WM_QUIT消息退出消息循环的时候,如果我们设置了这个窗口句柄,那么当我们收到WM_QUIT消息的时候,窗口已经被销毁了,窗口句柄无效了,那么这个这个时候,GetMessage的返回值就是-1,非0代表真,就不能退出消息循环,这时候的情况就是窗口已经没有了,但是在任务管理器中,仍然看的见进程还在,程序还在运行,但是又没有了窗口,所以就必须设置为NULL,那么当收到WM_QUIT消息的时候,GetMessage的返回值就是0,则退出消息循环,winmain返回。程序结束,winmain函数的返回值可以随意,但是推荐使用return (int) msg.wParam;,因为这个值就是我们PostQuitMessage的时候,发送的退出代码。一般这个对程序无大的影响,只是由的情况,我们要通过判断返回值来判断程序是否正常结束。

6窗口过程函数

窗口过程函数是非常重要的一个函数,我们对程序功能的开发主要也是在这个里面。一方面响应用户的操作,一方面接收来自系统的消息,进行处理。在窗口过程函数中有几个消息,是我们必须处理的:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;


switch (message)
{
case WM_CREATE:
{
///创建窗口上额外的控件,修改窗口样式等别的初始化操作(如初始化GDI+)
}
break;
case WM_COMMAND:
wmId    = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
SetBkMode(hdc,TRANSPARENT);
TextOut(hdc,0,0,L"MFC_序幕",6);
SetBkMode(hdc,OPAQUE);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
DeleteObject (bitmap1);
PostQuitMessage(0);
break;
case WM_ERASEBKGND ://这个函数不是必须处理的,这个是我改变窗口背景的消息处理
{
HDC m_hdc=(HDC)wParam;
HDC m_chdc=CreateCompatibleDC(m_hdc);
HGDIOBJ old=SelectObject(m_chdc,bitmap1);
BitBlt(m_hdc,0,0,200,200,m_chdc,0,0,SRCCOPY);
SelectObject(m_chdc,old);
DeleteObject (m_chdc);
return 1;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);///默认窗口过程函数
}
return 0;
}

必须处理的几个消息:首先是paint消息,为什么必须要处理呢,因为当我们改变窗口大小的时候,或是最大化,最小化的时候,会使窗口产生无效区,我们必须对无效区进行绘制,非客户去我们不用管,系统负责,客户区就是我们负责,我们必须也进行绘制,就是要处理WM_PAINT消息,在这个消息的BeginPaint和EndPaint消息就可以完成无效区的绘制,完成之后,系统就不会发送WM_PAINT消息,只要有无效区,系统就会不断的发送WM_PAINT消息,直到没有无效区为止,那么如果我们不处理WM_PAINT消息,那么就会收到无数的WM_PAINT消息,所以Paint消息时我们必须处理的。第二个必须处理的消息就是WM_DESTROY,我们通过处理这个函数,调用函数PostQuitMessage,这个函数有一个参数,就是退出码,也就是线程退出的返回值,如果按照上面我推荐的winmain的返回的方式,那么进程的返回值也是这个退出码。为什么呢,因为当我们收到WM_DESTROY的时候,窗口已经销毁了,那么我们必须退出线程和进程了,所以调用了PostQuitMessage就会发送一个WM_QUIT消息。最后是默认窗口过程,默认窗口过程,太重要了,首先,它可以处理我们不处理的消息,由它来进行默认的处理,另外它还可以实现一些窗口功能,如我们在标题栏按住鼠标左键就可以移动窗口,也是默认窗口过程的功劳,还有,当我们的窗口上有个子窗口,子窗口上又有一个控件,那么当我们点击这个控件的时候,首先接到消息的是控件本身,因为控件也是一个窗口,传给了默认窗口过程,窗口过程又传给了它的父窗口,这样一层层的,知道被处理位置,如果到了最顶层的窗口,仍然没有处理,最后默认处理。所以默认窗口过程是非常重要的。

/

以上基本就简要的描述了一个窗口程序的基本的结构,大家也许注意到了,从头到尾基本没有提到MFC这个词的,但是标题又是MFC,因为呢,在底层,MFC就是按照这个结构来的,在后面讲解MFC的文章中,会无处不在的有这个结构,它是理解MFC的基本。只要熟悉了这个结构,对于你灵活使用MFC就是一个开始了。当然上面这个对于对话框窗口来说,有点不同,如果是非模式对话框,那基本一样的,只有稍微的差别,但是如果是模式对话框,差别就有点大了,就只剩下三个步骤,1资源编辑器添加一个对话框资源,2用创建的资源创建对话框,3处理窗口过程函数。就不用我们写消息循环了,系统为我们维护,而且模式对话框程序退出的方式也不一样,并不是DestroyWindow的方法销毁窗口,而是我们要处理WM_CLOSE消息,调用EndDialog。更多详细的信息,自己查阅MSDN,做更加深入的学习,了解。

本文章代码:vs2010,win7下:http://download.csdn.net/detail/xinzhiyounizhiyouni/6491965

你可能感兴趣的:(win32,mfc,序幕)