最近和一些还在大学的朋友聊天,他们问我一个问题:大一的时候学习C语言,每天不是查询数组,就是排序数组,要不就是输入小星星,然后大二学.NET直接变成窗体,控件。感觉好像大一的C语言的基础和突然跳到窗体了,貌似那些东西和窗体没关系,他们怎么突然就跳到窗体了。这个问题,我不是第一次听到了,虽然问法不同,但是都是疑惑学习的基础的东西,是怎么变成一个窗体的。其实这个中间少了一座桥梁。(注:本文仅讨论Windows平台)
本文核心思想来源于经典著作《Windows程序设计》(第五版),本人仅仅是将前辈的经验从.NET的角度代述,有理解不当,表述错误之处,请指出,以免误人子弟。
一、Windows SDK开发
做开发的都知道SDK是什么东西,所谓SDK就是软件开发工具包,所谓的Windows SDK就是Windows的开发工具包(这句纯属废话),对于很多做.NET开发的新手来说,Windows SDK是很陌生的东西,而且很麻烦,展示一个空白的窗体,都需要好几十行的代码,并且个中细节从绘制到消息队列都需要自己实现。虽然SDK开发不是.NET必备的课程,但是我始终认为做WinForm开发了解SDK很有必要(可以参见经典著作《Windows程序设计》)
二、Windows消息机制与C#事件
众所周知,Windows底层是通过消息驱动的,系统以及各个应用程序使用消息进行通信,也就是说我们的系统和应用程序之间有着我们看不到的消息再流动,尤其对于一些.NET程序员来说,他们看到的是事件驱动,一个事件通过委托挂在一个方法上面,然后执行,对于消息这个东西不属于面向对象的玩意,在.NET这种纯对象语言中被透明化了,但是我觉得还是有必要说明一下,简单举例子,一个功能一个按钮点击,弹出一个框框,代码如下
private void button1_Click(object sender, EventArgs e) {
MessageBox.Show("hello world"); }
如此简单的一个功能,我们的系统和程序,到底做了哪些事情,想了解这些,首先要提到一个东西,叫做消息循环和消息队列,Windows系统本身有一个消息队列,同时Windows会给每一个Windows程序建立一个自己的消息队列,消息循环所做的工作是循环处理这个消息队列把消息发给制定的窗口,再由窗口函数去处理他们。回到我们刚刚的例子,当我们的鼠标点击的时候,硬件驱动会发送出一条鼠标点击的信号,然后系统产生一条鼠标点击消息,这条点击消息被加进消息队列,等待处理,消息循环不断处理队列中的消息,处理到这条鼠标消息的时候,通过消息的参数判断出这个是哪个窗口触发的(通过消息结构的参数判断),然后消息队列发送给指定的窗口函数,窗口函数中有个switch有N多个case然后通过case消息的方式判断出每条消息然后做相关的处理,至此一条消息处理完成。
说道现在有人要迷糊了,这个里面怎么没有我们的button1_click出现,难道消息循环里面写的是case WM_MOUSEDOWN: button1_Click()。其实不然,C#将Windows消息机制进行了封装,然后采用了消息发布订阅模式(又称为观察者模式,模式皇后)将消息与事件相关联。在窗口处理函数中判断鼠标事件的case并没有直接调用Click方法,而是调用了事件Click,在窗体初始化的时候,对于Click事件,使用系统委托关联了方法button1_Click,代码如下:
/// <summary> /// 设计器支持所需的方法 - 不要 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // button1 // this.button1.Location = new System.Drawing.Point(83, 94); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(75, 23); this.button1.TabIndex = 0; this.button1.Text = "button1"; this.button1.UseVisualStyleBackColor = true; this.button1.Click += new System.EventHandler(this.button1_Click);//这里使用了系统定义的委托EventHandler关联了方法 // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(284, 262); this.Controls.Add(this.button1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); }
一个点击功能就是这样在消息的推动下,通过事件模式执行。了解了消息以及事件,那么就很容易解释基础代码变窗体的问题了
三、用API创建一个窗体
.NET类库已经封装了完善的Form类用于窗体的各项应用,此处使用SDK创建窗口是为了更明显的表现出,一个窗体的创建,及运行过程,废话少说,下面开始
首先需要定义一些数据结构,如窗体类
/// <summary> /// 窗口类描述窗口相关属性 /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class WNDCLASS { /// <summary> /// 窗口风格类型(C++中有宏定义表示,此处使用常量) /// </summary> public int style = 0; /// <summary> /// 窗口函数(消息循环处理后调用的函数) /// </summary> public WNDPROC lpfnWndProc = null; public int cbClsExtra = 0; public int cbWndExtra = 0; /// <summary> /// 窗口类句柄(注册后不为0表示注册成功) /// </summary> public IntPtr hInstance = IntPtr.Zero; /// <summary> /// 窗口图标句柄 /// </summary> public IntPtr hIcon = IntPtr.Zero; /// <summary> /// 窗口光标句柄 /// </summary> public IntPtr hCursor = IntPtr.Zero; /// <summary> /// 窗口背景句柄 /// </summary> public IntPtr hbrBackground = IntPtr.Zero; /// <summary> /// 窗口菜单名称 /// </summary> public string lpszMenuName = null; /// <summary> /// 窗口类名称 /// </summary> public string lpszClassName = null; }
还有消息结构
/// <summary> /// 消息结构 /// </summary> [StructLayout(LayoutKind.Sequential)] public struct MSG { /// <summary> /// 消息句柄 /// </summary> public IntPtr hwnd; /// <summary> /// 消息ID /// </summary> public int message; /// <summary> /// 参数1 /// </summary> public IntPtr wParam; /// <summary> /// 参数2 /// </summary> public IntPtr lParam; }
窗口函数委托
/// <summary> /// 窗口函数委托(自定义委托在C++中的函数指针) /// </summary> /// <param name="hWnd"></param> /// <param name="msg"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> public delegate int WNDPROC(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
窗口风格的枚举
/// <summary> /// 窗口风格(我直接从C++的定义文件中复制过来的,具体含义去查MSDN吧) /// </summary> [Flags] public enum WS_STYLE : long { WS_OVERLAPPED = 0x00000000L, WS_CAPTION = 0x00C00000L, WS_SYSMENU = 0x00080000L, WS_THICKFRAME = 0x00040000L, WS_MINIMIZEBOX = 0x00020000L, WS_MAXIMIZEBOX = 0x00010000L, WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX), }
N多用到的API函数以及消息常量
/// <summary> /// 按下鼠标左键 /// </summary> public const int WM_LBUTTONDOWN = 0x0201; /// <summary> /// 窗体销毁 /// </summary> public const int WM_DESTROY = 2; /// <summary> /// 窗体关闭 /// </summary> public const int WM_CLOSE = 0x10; /// <summary> /// 投递消息(处理未完成就返回,相当于异步发送消息) /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); /// <summary> /// 获取一个应用程序或者DLL的模块句柄 /// </summary> /// <returns></returns> [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetModuleHandle(string moduleName); /// <summary> /// 注册窗口类 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr RegisterClass(WNDCLASS wc); /// <summary> /// 创建窗口 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam); /// <summary> /// 显示窗口 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
接下来开始创建窗口,首先需要定义好窗口函数,接收窗口的消息
// 窗口过程 static int SplashWindowProcedure(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_LBUTTONDOWN: MessageBox(hwnd, "您点击了窗口", "这是一个API弹出的消息框", 0); break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
然后,就可以注册窗口类,窗口类的各项属性表示了这个窗口的特征,注册窗口类,就是向系统注册了当前窗口属性,让当前窗口属性对象可用
// 初始化窗口类结构 WNDCLASS wc = new WNDCLASS(); wc.style = 0; wc.lpfnWndProc = SplashWindowProcedure; wc.hInstance = GetModuleHandle(null); wc.hbrBackground = (IntPtr)6; wc.lpszClassName = "CWindowWoNiu"; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hIcon = IntPtr.Zero; wc.hCursor = IntPtr.Zero; wc.lpszMenuName = null; // 注册窗口类 RegisterClass(wc);
窗口类注册成功就可以用注册成功的窗口类创建窗口并显示窗口了
// 创建并显示窗口 IntPtr hwnd; hwnd = CreateWindowEx(0, "CWindowWoNiu", "蜗牛的API窗体", (int)WS_STYLE.WS_OVERLAPPEDWINDOW, -1, -1, 400, 300, IntPtr.Zero, IntPtr.Zero, GetModuleHandle(null), null); ShowWindow(hwnd, 1); UpdateWindow(hwnd);
最后开启消息循环,就可以让它正常工作了
// 进入消息循环 MSG msg = new MSG(); while (GetMessage(ref msg, IntPtr.Zero, 0, 0)) { TranslateMessage(ref msg); DispatchMessage(ref msg); }
完整的代码,稍后附上,直接创建个控制台程序或者Windows程序代码粘贴就可以运行,这个例子详细展现了如何通过Windows的SDK从无到有的创建一个窗口出来,然后开启消息循环并且执行功能(此处多说一句,客户区有什么或者说窗体中有什么,是在窗口函数中的PAINT消息绘制,不在注册窗口的时候展现)
四、写在最后的话
本文其实和.NET关系并不是特别大,最终目的是想要展现一个Windows窗体是如何被创造出来的,创造出来以后,消息又是如何推动它去完成功能的,因为是几个.NET朋友建议我写的,所以我实现的部分采用了C#语言(其实使用C#去调用API创建窗口,除了学习意外就是一个蛋疼的想法,无任何意义),这部分代码可完善的地方有很多,例如和封装,封装以后可以加上事件,委托,模拟.NET本身的事件模式,创建控件(其实控件也是窗口),实现控件等等,没有继续完善有两个原因,第一我懒,第二。。我儿子又哭了。。我要去喂奶,就写到这了。。写的太渣,各位大神多多包涵,多多指教
using System; using System.Runtime.InteropServices; namespace SDKWindows { class program { static void Main() { // 初始化窗口类结构 WNDCLASS wc = new WNDCLASS(); wc.style = 0; wc.lpfnWndProc = SplashWindowProcedure; wc.hInstance = GetModuleHandle(null); wc.hbrBackground = (IntPtr)6; wc.lpszClassName = "CWindowWoNiu"; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hIcon = IntPtr.Zero; wc.hCursor = IntPtr.Zero; wc.lpszMenuName = null; // 注册窗口类 RegisterClass(wc); // 创建并显示窗口 IntPtr hwnd; hwnd = CreateWindowEx(0, "CWindowWoNiu", "蜗牛的API窗体", (int)WS_STYLE.WS_OVERLAPPEDWINDOW, -1, -1, 400, 300, IntPtr.Zero, IntPtr.Zero, GetModuleHandle(null), null); ShowWindow(hwnd, 1); UpdateWindow(hwnd); // 进入消息循环 MSG msg = new MSG(); while (GetMessage(ref msg, IntPtr.Zero, 0, 0)) { TranslateMessage(ref msg); DispatchMessage(ref msg); } } // 窗口过程 static int SplashWindowProcedure(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_LBUTTONDOWN: MessageBox(hwnd, "您点击了窗口", "这是一个API弹出的消息框", 0); break; } return DefWindowProc(hwnd, msg, wParam, lParam); } /// <summary> /// 窗口风格(我直接从C++的定义文件中复制过来的,具体含义去查MSDN吧) /// </summary> [Flags] public enum WS_STYLE : long { WS_OVERLAPPED = 0x00000000L, WS_CAPTION = 0x00C00000L, WS_SYSMENU = 0x00080000L, WS_THICKFRAME = 0x00040000L, WS_MINIMIZEBOX = 0x00020000L, WS_MAXIMIZEBOX = 0x00010000L, WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX), } /// <summary> /// 按下鼠标左键 /// </summary> public const int WM_LBUTTONDOWN = 0x0201; /// <summary> /// 窗体销毁 /// </summary> public const int WM_DESTROY = 2; /// <summary> /// 窗体关闭 /// </summary> public const int WM_CLOSE = 0x10; /// <summary> /// 投递消息(处理未完成就返回,相当于异步发送消息) /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); /// <summary> /// 获取一个应用程序或者DLL的模块句柄 /// </summary> /// <returns></returns> [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetModuleHandle(string moduleName); /// <summary> /// 注册窗口类 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr RegisterClass(WNDCLASS wc); /// <summary> /// 创建窗口 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam); /// <summary> /// 显示窗口 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); /// <summary> /// 更新重绘窗口 /// </summary> /// <param name="hWnd"></param> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern bool UpdateWindow(IntPtr hWnd); /// <summary> /// 从消息队列冲获取一条消息 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern bool GetMessage(ref MSG msg, IntPtr hwnd, int minFilter, int maxFilter); /// <summary> /// 翻译一条线消息(把虚拟按键转换为字符) /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern bool TranslateMessage(ref MSG msg); /// <summary> /// 发送一个消息给指定的窗口函数 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int DispatchMessage(ref MSG msg); /// <summary> /// 退出消息循环 /// </summary> /// <param name="nExitCode"></param> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern void PostQuitMessage(int nExitCode); /// <summary> /// 默认消息的处理函数,不想处理的函数都丢给它 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); /// <summary> /// 弹出一个消息框和MessageBox.Show差不多 /// </summary> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, int uType); /// <summary> /// 窗口类描述窗口相关属性 /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class WNDCLASS { /// <summary> /// 窗口风格类型(C++中有宏定义表示,此处使用常量) /// </summary> public int style = 0; /// <summary> /// 窗口函数(消息循环处理后调用的函数) /// </summary> public WNDPROC lpfnWndProc = null; public int cbClsExtra = 0; public int cbWndExtra = 0; /// <summary> /// 窗口类句柄(注册后不为0表示注册成功) /// </summary> public IntPtr hInstance = IntPtr.Zero; /// <summary> /// 窗口图标句柄 /// </summary> public IntPtr hIcon = IntPtr.Zero; /// <summary> /// 窗口光标句柄 /// </summary> public IntPtr hCursor = IntPtr.Zero; /// <summary> /// 窗口背景句柄 /// </summary> public IntPtr hbrBackground = IntPtr.Zero; /// <summary> /// 窗口菜单名称 /// </summary> public string lpszMenuName = null; /// <summary> /// 窗口类名称 /// </summary> public string lpszClassName = null; } /// <summary> /// 消息结构 /// </summary> [StructLayout(LayoutKind.Sequential)] public struct MSG { /// <summary> /// 消息句柄 /// </summary> public IntPtr hwnd; /// <summary> /// 消息ID /// </summary> public int message; /// <summary> /// 参数1 /// </summary> public IntPtr wParam; /// <summary> /// 参数2 /// </summary> public IntPtr lParam; } /// <summary> /// 窗口函数委托(自定义委托在C++中的函数指针) /// </summary> /// <param name="hWnd"></param> /// <param name="msg"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> public delegate int WNDPROC(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); } }
浙公网安备 33010602011771号