呵呵,T版说的也有道理,当我们的思维不再为界面所困惑的时候,更多的精力是花在了程序算法和面象对象设计上。
C#可以说把界面搞得更加人性化了,双击某个事件,直接写代码就完了。
每个人都会思考不一样的东西,就如同偶当时学习Win32窗口的时候,想了七点之多。
从程序中看Windows窗口
2010-09-13 12:26:45|
分类: 代码人生 |字号 订阅
一、微软的窗口,呵呵,你认为窗口是个什么东西?从程序的观感上看Windows窗口,从对象的观点看窗口,窗口具有各种各样的属性,程序图标,窗口标题,控制按钮,窗口的样式,边框大小,客户窗口大小,系统菜单等等,还有一些背后的属性如窗口Z位置,窗口过程,窗口消息,句柄。复杂的Windows背后居然隐藏了很多的细节。自从进入GUI图形界面后,Windows再也不像以前的DOS了,如果你认为Windows的那个DOS黑界面还是N早以前的DOS那就大错特错了,我们用程序来验证这个“DOS”不同于以前的DOS,在控制台程序中创建一个窗口。(见后代码)哦,呵呵,黑窗口中除了输出"hello"字样的字符串,居然还有一个自定义的Windows窗口浮现在黑窗口之上,而且可以进行消息循环。不会吧?这种情况直接证明了Win32控制台程序还是窗口程序,可以使用Win32 API函数。
二、窗口过程与多窗口。窗口类可以只声明一个变量,同时通过修改窗口类的值而进行多次注册,对于同一个已注册的窗口类来说,可以通过CreateWindow创建多个窗口,如果窗口过程指向同一个窗口过程回调函数,则创建的多个窗口将共享此同一个过程,就会发生如果不加判断的处理WM_CLOSE或是WM_DESTROY消息发送Quit退出消息,将会造成如果关闭任何一个创建的窗口,那么所有窗口一并被销毁,程序直接退出,这通常有点不合常理。思考,MFC之所以搞个所谓的框架,就单文档应用程序而言,Frame类和View类本质上都是与窗口相关的类(注意,窗口和窗口类是不同的东西,窗口Destroy了,类的对象不一定就销毁了。),View窗口覆盖住了FRAME窗口的客户区,偶后来又测试了一下双窗口过程对应双窗口均为顶层窗口的情况,窗口1做主,窗口2做副,主窗口处理WM_DESTROY消息退出,副窗口只处理WM_CLOSE消息进行窗口销毁(也可以交由系统处理),那么关闭副窗口时主窗口还在运行,如果关闭的是主窗口,则两个窗口同时销毁,程序正常退出,最终保证了程序逻辑也是正确的。
三、为什么按钮也是窗口,只能收到按钮被点击的事件,但却没法在窗口过程中收到按钮的鼠标移动等消息呢?答案和上面有点类似,事实上Windows把按钮类进行了封装,窗口过程是独立的,并预加载了例如"Button"这样的类到系统中,所以你看不到按钮类的窗口注册过程,Windows自定义的按钮窗口过程你也看不到,按钮消息是发在这个按钮的窗口过程中,你的程序注册的窗口过程当然收不到鼠标移动等消息,你窗口程序中收到的BM_CLICKED消息不过是按钮收到三个鼠标事件后发给父窗口的。因此子类化控件的方式就是利用SetWindowLong函数修改按钮的窗口的过程达到控制被Windows定义的黑匣子控件,思路上再扩展一下,Windows,Windows,一个窗口加一个窗口,这就是我们的微软操作系统真正的含义,GUI界面就是Windows窗口界面,每个窗口或是一种控件从微软的程序作法上来讲都应该有一个独立的窗口过程,而不是共享一个窗口过程,而窗口的绘制可以交给GDI函数来完成,因此实现自产漂亮的控件应该是可以实现,只是人们需要大费周章的写代码,而多数人不会去做这样的造轮子的事。经常在Linux程序中看到一些界面很特别的界面,例如窗口标题不像微软只放在左边,可以放在中间,右边,窗口关闭钮还可以放在左边,还可以有多种其它控件在上面,微软为我们程序员做得太多了。个人还是喜欢自由似的GUI,有些Windows的GUI设计感觉到有点莫名其妙,例如程序中只需要最小化和关闭按钮,偏偏MS给你两个按钮中间卡个不可用的最大化按钮,浪费GUI资源,还得编程去掉。
四、多个顶级窗口如果共享一个窗口过程,既创建同一个窗口类的两个Windows,WM_CREATE会被触发多少次呢?经过实验,有多少个窗口被创建,这个消息就会被触发多少次,所以在WM_CREATE里面写初始化代码的时候要注意喽,说不定你的代码被多次执行可能你还没有发现呢,但创建控件窗口是不会触发这个消息的产生,原理和上面所述一样。
五、对程序员而言,最方便的Windows程序设计方式就是事件来了你去调用我写好的代码就行了,注册事件--对应用户代码,从根本上把界面与程序逻辑分离,而不是提供一个回调函数的方式让程序员写一堆的CASE语句或是MFC/ATL,WTL的消息宏映射方式,这样也许程序写起来更简单,当然,你可以说由于Windows是C写的,C本身就不是完美的,事实上,很多人从C#或是Java语言中看到了用反射来实现Windows GUI界面的绝好方式,可惜的是两种语言全是托管代码,C++可以实现,但不是直接支持,如果有某种语言可以直接支持的话,想想将以前的经典系统重新用这种语言重构出来的系统,稳健,高效,代码模块独立,速度更快,不浪费计算机资源是多好呀。为什么我的计算机升级后,运行新的Windows系统还是慢呢?为什么写程序VS2003升级到VS2008或是VS2010慢得要死呢?我的CPU真的不行了?读一读C++ IN Action这本书吧,看看作者解释1G速度的CPU可以在20毫秒内处理多少条消息,你的CPU比它的还要慢??微软虽然构建了一个操作系统帝国,但窃以为,微软正在走上大部分软件所走过的共同的路上,版本越高,体积越大,运行越慢的怪圈中,看着你手中的Windows安装盘,Visual Studio安装盘,你是不是也有同感呢?
六、对话框的本质:理解了上面窗口的本质后,你是否在想,对话框和普通窗口有什么本质的区别?很不好意思的告诉你,比较了WinForm(C#)、普通窗口,非模态对话框和模态对话框后,偶的看法是,没有多少本质的区别,打开一个C#写的WinForm程序,然后开启VC工具的Spy++程序,当你去查找WinForm窗口控件的时候,你会发现只不过是一个注册名很长的普通窗口或是控件,换句话说C#生成的本地最终执行码效果和C/C++写的Windows效果等同。在FormX.Desinger.cs你可以很轻松的看到类似于这样的“this.button1 = new System.Windows.Forms.Button();”语句,“this.Controls.Add(this.button1);”这句也很明显的把new出来的控件对象放进了一个容器里,呵呵,普通窗口就不能创建控件了?相信你也和我一样做过实验,用CreateWindow或是CreateWindowEx函数在窗口上创建了一个一个的控件了,将背景色彩改成RGB(0xEC, 0xE9, 0xD8)(取色软件取得的值),然后再改改控件的字体,一个对话框样式的窗体就出现在眼前了,这么一个窗口拥有多种消息处理,也能方便的分割窗口,感觉是不是很好?资源编辑器本质上就是写了一串对应于CreateWindow函数所需参数的文本,扩展一下思路吧,把这些文本写入到一个XML文件里进行管理,可以以手工或是程序的方式来修改一个已经生成好的程序界面,你会怎么想呢?是不是觉得很棒?因此,编程界里才会出现一种以XML配置方式的GUI界面程序,应用很多,如果再配以Gdi+函数,产生漂亮的用户界面非常轻松。
七、可以在C++中借鉴C#的这种编程方式,定义控件容器来装控件对象,所有的控件的Create()方法均为基类虚方法的实现版,这样可以很方便的在RegisterClass后,CreateWindow创建主窗口前,来一个循环,就可以完成遍历容器创建所有的控件,而不用把初始化代码放在WM_Create消息里,而且也可以在循环前,还可以调用一下给用户初始化控件的Init()方法,让用户在这里new一下新的控件,模拟一下C#,呵呵。程序的写法多种多样,不要忘了还有前人总结的23种设计模式,更不要被MFC一棵树给吊死了,离开MFC就不能用C或是C++写Windows程序?以前常听别人说,微软的程序员自己都不用VS、MFC写程序,但自从翻看了一下SDK中的微软示例程序,网上下载了一些CABSDK,Ramdisk这些小软件的源程序后,基本上可以看出,好像传说是真的,有些源程序甚至只提供了makefile文件,如果不懂命令行进行编译的人,只有自建工程文件进行添加编译了。真是搞笑,国内的人还真的把MFC当回事儿了,搞了很多MFC式的VC编程书籍,好在近些年来,虽然微软一直没有放弃MFC,但在国内的热度远不如前几年了,大家都去追捧热门的C#、Java去了,通过这小段时间的研究,的确感到,如果学完C的Windows编程后,再看C#等这些东西,感觉上是有本质上的理解顿悟,虽然研究C++可能真的是在浪费生命,感悟编程的感觉真好。
附代码:
#include <iostream>
#include <windows.h>
typedef HINSTANCE _h;//简化一下代码输入而矣,不必在意在句
LRESULT CALLBACK winproc(HWND, UINT, WPARAM, LPARAM);
int main()
{
std::cout << "hello" << std::endl;//输出到黑窗口中
_h hInst = ::GetModuleHandle(NULL);//获取程序模块实例句柄
MSG msg;
HWND hwnd;
WNDCLASS _class;
memset(& _class, 0, sizeof(_class));
_class.hbrBackground = (HBRUSH) 1;//背景
_class.hInstance = hInst;
_class.lpszClassName = "WinApp";//窗口类名
_class.style = CS_HREDRAW | CS_VREDRAW;//窗口样式风格
_class.lpfnWndProc = winproc;//窗口过程
::RegisterClass(& _class);//注册窗口类,没有进行过多的判断,避免代码理解干忧
hwnd = ::CreateWindow("WinApp",
"Hello",
WS_OVERLAPPEDWINDOW,
50,
50,
300,
200,
0,
0,
hInst,
0
);//创建窗口
::ShowWindow(hwnd, SW_SHOW);//显示窗口
::UpdateWindow(hwnd);//更新窗口内容显示
//进入消息循环
while(::GetMessage(&msg, NULL, 0, 0) > 0)
{
::DispatchMessage(&msg);
}
return 0;
}
//窗口过程函数
LRESULT CALLBACK winproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
return FALSE;
}