0xAA55 发表于 2014-1-29 23:39:06

【API】设计Windows图形界面程序的方法

Windows图形界面程序(Win32 GUI Application)就是我们平时看到的、没有黑色控制台窗口的程序,我们平时用的浏览器、记事本、Word等软件就是这种类型。现在我来讲讲这种类型的程序是怎么个运行原理,以及编程的方法。
进行Windows开发必备的帮助文档是MSDN,还没有的点这里进去下载。

事实上设计Windows图形界面程序的方法和编程语言的关系不大,主要是看你对API的使用。只要API用对了,什么编程语言都可以实现Windows图形界面程序的设计。看下面三个链接。
C\C++编写Win32图形界面程序的方法
http://www.0xaa55.com/forum.php?mod=viewthread&tid=107&fromuid=1
NASM汇编编写Win32图形界面程序的方法
http://www.0xaa55.com/forum.php?mod=viewthread&tid=106&fromuid=1
VB用API编写Win32图形界面程序的方法(VB本身就已经集成了这些API,所以事实上VB不需要这样编程。这样反而很费劲)
http://www.0xaa55.com/forum.php?mod=viewthread&tid=110&fromuid=1

1、程序的运行原理
一个Win32 GUI程序的原理是先创建窗口,然后根据检测用户对这个窗口的操作(比如按键、点鼠标等)来判断自己该干什么,作出一些反应。
创建窗口的时候系统会从你提交的结构体中得知你的消息处理回调函数的地址,从而对其进行调用,你只需要对回调函数进行编程就能得到已有的消息。

2、程序的主要运行流程
第一步:注册窗口类。窗口类是个结构体,指定了你的窗口的风格和“类名”。“类名”用于标识你的窗体。
第二步:创建窗口和控件。控件其实就是窗口,创建控件就是给你的窗口创建子窗口,所以创建控件的函数仍然是CreateWindow、CreateWindowEx。
第三步:进入消息循环。消息循环,就是通过循环不断取得Windows发给你的程序的消息。通过处理消息来回应用户的操作。

3、详细说明。
注册窗口类的时候,你是要填写一个WNDCLASS或WNDCLASSEX结构体。反正我习惯了填写WNDCLASSEX,因为WNDCLASSEX能够设置小图标样式。WNDCLASS用RegisterClass注册,WNDCLASSEX用RegisterClassEx注册。
它们的原型如下:
这个是WNDCLASSEX:typedef struct _WNDCLASSEX {
    UINT       cbSize;
    UINT       style;
    WNDPROC    lpfnWndProc;
    int      cbClsExtra;
    int      cbWndExtra;
    HINSTANCEhInstance;
    HICON      hIcon;
    HCURSOR    hCursor;
    HBRUSH   hbrBackground;
    LPCTSTR    lpszMenuName;
    LPCTSTR    lpszClassName;
    HICON      hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;这个是WNDCLASS:typedef struct _WNDCLASS {
    UINT       style;
    WNDPROC    lpfnWndProc;
    int      cbClsExtra;
    int      cbWndExtra;
    HINSTANCEhInstance;
    HICON      hIcon;
    HCURSOR    hCursor;
    HBRUSH   hbrBackground;
    LPCTSTR    lpszMenuName;
    LPCTSTR    lpszClassName;
} WNDCLASS, *PWNDCLASS;可以看出两个结构体差不多。
这里我来说说WNDCLASSEX这个结构体的填写方法。WNDCLASSEX WCEx;//定义这个结构体。可以将其定义为全局变量。
//下面给WCEx赋值
WCEx.cbSize=sizeof(WNDCLASSEX);//结构体大小,必须为这个值。
WCEx.style=0;//这个不需要的话为0。本来的值是“CS_”开头的一组常数的组合。MSDN有讲(Class Styles,在Platform SDK: Windows User Interface)。
WCEx.lpfnWndProc=(WNDPROC)你的消息处理函数的地址;//这里指定了你的消息回调处理函数的地址。回调函数我会在后面进行说明。
WCEx.cbClsExtra=0;//这里指定了你这个窗口类结构体后面有多少额外字节是打算放在窗口类结构体后面的。一般我们不放这些额外字节。置0。
WCEx.cbWndExtra=0;//这里指定了你要放多少额外字节在自己的窗口实例后面。一般置0。如果一个程序用CreateWindowEx创建一个来自程序资源的对话框,你必须将其指定为DLGWINDOWEXTRA。
WCEx.hInstance=你的实例句柄;//所谓实例句柄就是WinMain给出的hInstance参数。如果你没有用WinMain作为程序入口点,你可以使用GetModuleHandle(NULL)来取得这个值。事实上只要你的程序是个正常的EXE,它的值一般为0x00400000
WCEx.hIcon=你的图标;//图标如果是资源文件的可以用LoadIcon函数取得图标句柄。也可以给hIcon赋值为NULL.如果给hIcon复制为NULL,你的程序图标就会变成Windows默认的图标。
WCEx.hCursor=你的鼠标光标;//同上,如果是资源文件可以用LoadCursor取得句柄。如果你给它赋值为NULL,你的鼠标光标就会变成鼠标移进窗口来的样子。一个让鼠标光标变为系统默认光标的方法是用LoadCursor(NULL,IDI_ARROW)设置一个箭头光标。
WCEx.hbrBackground=背景色;//背景色是一个HBRUSH类型的变量。不过你可以赋值为系统默认的常数。按钮表面的颜色为“(HBRUSH)(COLOR_BTNFACE+1)”。赋值为0会导致你的窗口不会重绘背景。
WCEx.lpszMenuName=你的菜单;//菜单是一个HMENU类型的变量,如果你没有菜单就直接将其赋值为NULL。如果你有菜单,就给它赋值为你的菜单的ID。
WCEx.lpszClassName=你的窗口类名;//这里很重要。这里指定了你要注册的窗口类的名字。窗口类名是一串字符串。
WCEx.hIconSm=小图标样式;//小图标指的是标题栏图标。这个和hIcon差不多。
//赋值完了,注册窗口类
RegisterClassEx(&WCEx);//如果这个函数返回0说明注册失败。
RegisterClassEx的返回值可直接用于CreateWindowEx指定要创建的窗口类。也可以用窗口类名来指定你要创建的窗口类。
CreateWindowEx的原型:HWND CreateWindowEx(
DWORD dwExStyle,      // extended window style
LPCTSTR lpClassName,// registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle,    // window style
int x,      // horizontal position of window
int y,      // vertical position of window
int nWidth,       // window width
int nHeight,      // window height
HWND hWndParent,      // handle to parent or owner window
HMENU hMenu,      // menu handle or child identifier
HINSTANCE hInstance,// handle to application instance
LPVOID lpParam    // window-creation data
);我们通过调用CreateWindowEx来创建窗口。一般我们是这样调用的:HWND hWnd=CreateWindowEx(//创建窗口
0,//dwExStyle参数,值为“WS_EX_”开头的一组常数。指定特殊样式。一般不需要特殊样式,所以赋值为0。所谓特殊样式比如“接收文件拖拽”、“分层窗体样式”等。分层窗体样式详见http://www.0xaa55.com/forum.php?mod=viewthread&tid=50
类名,//lpClassName参数,也就是“窗口类名”。这里可以指定你要创建的窗口的类名。如果是你刚才注册的窗口类,可以直接给RegisterClass(Ex)的返回值。或者给WCEx.lpszClassName也行。如果是创建控件我会在后面给出用法。
TEXT("Hello World!"),//lpWindowName参数,窗口标题栏文本。将来可通过SetWindowText来设置。
WS_OVERLAPPEDWINDOW,//dwStyle参数,窗口的样式。值为“WS_”开头的一组常数。这里指定的WS_OVERLAPPEDWINDOW的效果为:有标题栏,有系统菜单(所谓系统菜单就是“关闭窗口”这个按钮和标题栏点右键出来的菜单),有最大化、最小化按钮,可以拖拉调节窗口大小。
CW_USEDEFAULT,//窗口x坐标。坐标系原点在屏幕左上角。以像素为单位。设置CW_USEDEFAULT之后,系统会自动设置窗口的位置。注意dwStyle为“对话框风格”的窗口不能设置CW_USEDEFAULT,否则你的窗口的x坐标是-2147483648这里。
CW_USEDEFAULT,//窗口y坐标。注意事项和上面一样。
600,//窗口宽度。和上面一样,可以设置CW_USEDEFAULT。
400,//窗口高度
NULL,//hWndParent参数,如果给定了窗口则意味着这个窗口会被创建在别的窗口里面。创建控件的时候必须设置它。否则控件就是飘在屏幕上的(可以用“居无定所”来形容),并且有个标题栏。
NULL,//hMenu参数。注意是HMENU类型的。对于窗口,给NULL则表示没有菜单,否则可以用LoadMenu来载入资源里面的菜单。对于控件,这个参数指定的是控件的ID。所谓ID就是用一个数字来给控件编号,这样以后要控制这个控件的时候可以用这个数字。
hInstance,//hInstance参数,这个hInstance和WNDCLASSEX里面的hInstance是一样的。
NULL);//lpParam参数。一般传NULL。作用是在创建窗体的时候,你的回调函数会收到一个WM_CREATE消息,跟随的lParam指向的结构体里面会原封不动把lpParam参数的值给你。
CreateWindowEx的返回值是一个窗口句柄。我刚才提到如果你要创建控件也是使用这个函数。没错,Windows的控件其实都是“窗口”。创建控件的时候,你只要指定它的“窗口类名”参数(lpClassName参数)为以下的字符串就能创建控件:
"Button"  按钮
"ComboBox" 组合框
"Edit"   文本框
"ListBox"  列表框
"MDIClient" MDI客户窗口
"ScrollBar" 滚动条
"Static"  标签
以上7条只是常用的控件。而对于一些功能更多的、更华丽的控件(公用控件等)我在这里就不说明了。太多了。大家可以去查MSDN。
创建好窗体和控件之后,下一步就是进入消息循环。消息循环有两种,一种是只有在Windows发消息的时候运行,其余时候待机,普通的窗口程序都是这样的,比较节省资源。另一种是无论有没有消息都运行,对于大多数游戏就是这种类型。因为游戏要不停的渲染,计算,所以不能停下来。
消息循环很简单,就是下面这种样子的:
没有消息就待机的:MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}无论是否有消息都执行循环的:MSG msg;
do
{
    if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
}while(msg.message!=WM_QUIT);大家可能会注意到上面出现的MSG结构体、GetMessage函数、PeekMessage函数、TranslateMessage和DispatchMessage函数。它们的原型是什么呢?我来讲讲。typedef struct tagMSG {
HWND   hwnd;
UINT   message;
WPARAM wParam;
LPARAM lParam;
DWORDtime;
POINTpt;
} MSG, *PMSG;BOOL GetMessage(
LPMSG lpMsg,   // message information
HWND hWnd,       // handle to window
UINT wMsgFilterMin,// first message
UINT wMsgFilterMax   // last message
);BOOL PeekMessage(
LPMSG lpMsg,   // message information
HWND hWnd,       // handle to window
UINT wMsgFilterMin,// first message
UINT wMsgFilterMax,// last message
UINT wRemoveMsg      // removal options
);BOOL TranslateMessage(
CONST MSG *lpMsg   // message information
);LRESULT DispatchMessage(
CONST MSG *lpmsg   // message information
);这些是它们的原型。其中TranslateMessage函数将消息中的Windows按键消息的虚拟键消息翻译成字符键消息。DispatchMessage函数调用回调函数进行消息处理。GetMessage会在取得消息的时候返回,没有消息的时候就不返回。取得WM_QUIT消息后会返回0。
PeekMessage则会在取得消息的时候返回非零,没有消息的时候返回0。因此需要判断是否取得了WM_QUIT。如果取得了WM_QUIT则要退出消息循环。
GetMessage和PeekMessage的第3、4个参数都是用来指定消息范围的,最小消息和最大消息。一般我们都处理所有消息,所以不管它。
嘛……Windows的API真是麻烦,各种可选导致各种垃圾选项……
现在来讲重点,那就是消息处理函数的编写。消息处理回调函数(WndProc)用来处理Windows消息。刚才的消息循环只是取得消息。取得了消息,还要处理消息。一般是这样的:LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wp,LPARAM lp)
{
    switch(uMsg)
    {
    case WM_DESTROY://用户要关闭窗口了
      PostQuitMessage(0);//发送WM_QUIT消息
      break;
    case 其它消息:
      处理;
      break;
    default://我们不想处理的消息交给DefWindowProc来处理
      return DefWindowProc(hWnd,uMsg,wp,lp);
    }
    return 0;//默认返回0
}其中消息处理函数的最后两个参数(WPARAM和LPARAM)指定了伴随消息而来的两个参数。这两个参数指定了消息的处理过程。
详细的可以看MSDN。消息一般都是WM_开头的。除了系统默认的这些消息以外,还有用户自定义的消息。用SendMessage可以发送消息。


那么讲了这么多,我还是给一个实例吧。有实例才能更好的帮助大家理解Windows的图形界面窗体是怎么样的。以下代码将创建出一个最简单的窗体。你只需要弄一个C文件,把下面的代码粘贴进去,然后使用VC、MinGW或Cygwin等编译器的其中之一进行编译就能得到EXE,运行能看到一个空的窗口。
我再说一遍……没有MSDN,玩API编程等于玩蛋。#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

HWND      g_hWnd;
WNDCLASSEX    g_WCEx=
{
    sizeof(g_WCEx),
    0,
    (WNDPROC)WndProc,
    0,
    0,
    NULL,
    NULL,
    NULL,
    (HBRUSH)(COLOR_BTNFACE+1),
    NULL,
    TEXT("WIN32APP_WINDOW"),
    NULL
};

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR szCmdLine,int nShowCmd)
{
    g_WCEx.hInstance=hInstance;
    g_WCEx.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    g_WCEx.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
    g_WCEx.hCursor=LoadCursor(NULL,IDC_ARROW);
    if(!RegisterClassEx(&g_WCEx))
    {
      MessageBox(NULL,TEXT("无法注册窗口类!"),NULL,MB_ICONERROR);
      return -1;
    }
    g_hWnd=CreateWindowEx(0,g_WCEx.lpszClassName,TEXT("主窗口"),WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
      NULL,NULL,hInstance,NULL);
    if(!g_hWnd)
    {
      MessageBox(NULL,TEXT("无法创建窗口!"),NULL,MB_ICONERROR);
      UnregisterClass(g_WCEx.lpszClassName,hInstance);
      return -1;
    }
    ShowWindow(g_hWnd,nShowCmd);
    UpdateWindow(g_hWnd);
    MSG msg;
    while(GetMessage(&msg,NULL,0,0))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    UnregisterClass(g_WCEx.lpszClassName,hInstance);
    return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wp,LPARAM lp)
{
    switch(uMsg)
    {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return DefWindowProc(hWnd,uMsg,wp,lp);
    }
    return 0;
}
源码的下载地址:(含EXE)http://pan.baidu.com/s/1hq835x6
其实Windows编程并不限制你用的编程语言。无论你用什么编程语言,都可以对齐进行编程开发,只要按照上面的规律调用API就可以了。纯汇编都是可以的哦。我也试过用VB6完全调用API(我承认我是疯了),很成功,窗口出来了。只是调试两次IDE就挂了。
相关下载:
【开发环境】编程开发环境IDE下载(MSDN也有)
http://www.0xaa55.com/forum.php?mod=viewthread&tid=98&fromuid=1
【编译器】编译器下载(C、C++、汇编)
http://www.0xaa55.com/forum.php?mod=viewthread&tid=99&fromuid=1
HelloWorld各种语言版本:
http://www.0xaa55.com/forum.php?mod=viewthread&tid=104&fromuid=1

水货中~~ 发表于 2014-3-13 16:42:33

顶,水货学习中~~~

13*0217 发表于 2014-5-28 15:28:35


/* 经典语录
   1.事实上设计Windows图形界面程序的方法和编程语言的关系不大,主要是看你对API的使用。
   2.Win32 GUI程序的原理是先创建窗口,然后根据检测用户对这个窗口的操作(比如按键、点鼠标等)来判断自己该干什么,作出一些反应。
   3.Windows的控件其实都是“窗口”。
   4.我再说一遍……没有MSDN,玩API编程等于玩蛋。
*/
/* 有几个疑问
   1.如此繁多窗口消息和消息处理函数,你是如何记忆和应用它们的?
   2.“创建窗口的时候系统会从你提交的结构体中得知你的消息处理回调函数的地址,从而对其进行调用,你只需要对回调函数进行编程就能得到已有的消息。”这句话的最后一句不能理解?
*/

0xAA55 发表于 2014-5-28 16:06:15

13*0217 发表于 2014-5-28 07:28
/* 经典语录
   1.事实上设计Windows图形界面程序的方法和编程语言的关系不大,主要 ...
//1.如此繁多窗口消息和消息处理函数,你是如何记忆和应用它们的?
//    我压根儿就没有专门去记住它。我只是需要用的时候查一下MSDN。时间长了就都记住了。
//“你只需要对回调函数进行编程就能得到已有的消息。”
//    说白了就是WndProc函数的UINT uMsg参数是消息,Windows通过调用你的WndProc来传递消息给你。

plugin 发表于 2017-10-11 22:23:15

【API】设计Windows图形界面程序的方法

(⊙o⊙) 发表于 2017-11-1 08:39:32

学习了,

(⊙o⊙) 发表于 2017-11-6 07:40:05

程序猿都很忙
页: [1]
查看完整版本: 【API】设计Windows图形界面程序的方法