Windows界面编程 - 灵活界面库的技术原理

 

 

     最近,见识了一两个非常不错的界面库。很漂亮,而且使用方便。在程序最开始部分初始化一下,设置一下皮肤,程序的所有窗口的外观就全都变化了。

      了解一下大致的实现原理,把自己学到的表达一下。

      简单来说,Windows下的窗口程序都是由消息驱动的,当收到消息的时候进行处理,没有消息的时候就把CPU资源让出来。每个窗口都是有一个消息队列的,那由谁来处理这个消息呢,就是窗口过程,或者叫窗口处理函数。

      回顾一下纯SDK的时候创建窗口的过程:定义窗口类、注册窗口类、创建窗口、更新和现实窗口。(这里的类不是C++中的类,Windows操作系统是基于C实现了,这里的窗口类其实是一个维护了窗口信息的结构体)。

定义一个窗口类:

WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wndclass.hCursor = LoadCursor( NULL, IDC_ARROW );
wndclass.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = TEXT("BaseWndClass");

      这里的 wndclass.lpfnWndProc = WndProc 就为该类窗口指明了一个窗口过程,当这个类型的窗口收到消息的时候,就用这个窗口过程进行处理。

      对于Windows来说,存在一个默认的窗口过程函数:DefWindowProc,一般情况下面,我们自己的窗口过程不需要处理所有的消息,我们只要截获我们需要的消息,然后把我们不关心的消息交给默认的窗口过程DefWindowProc处理就可以了。

      Windows下,窗口的整个生命周期都伴随着消息,包括窗口创建的事件和窗口销毁,当然也包括窗口的绘制过程。所以,如果我们把窗口绘制的相关信息都由自己的窗口过程来处理,不采用默认的窗口过程,我们就可以自己绘制窗口(想怎么画就怎么画)。

和窗口绘制有关的消息:

WM_NCPAINT
WM_PAINT
WM_ERASEBKGND


      这三个消息是和绘制直接相关的,WM_NCPAINT在窗口框架需要重画的时候被发送,WM_PAINT在客户区需要绘制的时候发送,WM_ERASEBKGND在需要擦除窗口背景的时候发送的。

 

      在我们的窗口过程中处理WM_NCPAINTWM_PAINTWM_ERASEBKGND,按自己的方式绘制非客户区,客户区,就可以改变窗口的外观了。

 

      是不是太简单了?这就够了吗? --- 不够,因为,窗口还有很多状态变化,包括大小改变,激活和非激活,这些情况下,需要窗口过程进程处理,适当的重画窗口。比如这些消息:

WM_NCACTIVATE
WM_WINDOWPOSCHANGING
WM_NCCALCSIZE
WM_SIZE
WM_SIZING

      还有,注意到鼠标在标题栏上的最小化、最大化等按钮移动时的效果了吧,还有鼠标点击之后的最大化最小化关闭的操作,这些也是在窗口过程内部实现的。对以下的消息进行适当的处理,就可以实现自己的标题栏效果,比如,按钮一点要在标题栏的右边吗?不一定,你可以在适当的位置画按钮,处理WM_NCHITTEST消息来响应鼠标在按钮上移动时按钮的变化,和按钮被点击后的执行的操作。一句话,只要你了解了消息的含义和流程,你可以极大限度的自定义你的窗口。

WM_NCHITTEST
WM_NCMOUSEMOVE
WM_NCLBUTTONDOWN
WM_NCLBUTTONDBLCLK
WM_NCLBUTTONUP
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP

      理解了上面的这些,你就知道了如何通过对在窗口过程函数中写代码来改写窗口的外观了。
但是,这里有一个问题,这种办法只对自己定义的窗口类管用,如果你使用了别人写的库或系统默认的某些控件,他们都有自己的窗口过程函数了,你还能改变他们的外部吗?

      能,每个窗口都有一个句柄,指示一个Windows内核的数据结构,这个数据结构包含了窗口的许多信息(类型、样式、大小位置等等),其中就包括了窗口过程函数的信息,通过Windows API来将窗口过程函数设置成你的窗口过程,就可以对这个窗口的消息进行处理,也就能改变窗口的外观了。当然,你首先得获取到窗口的句柄。

    指定一个窗口的窗口过程函数为MyWinProc

    SetWindowLong( hwnd, GWL_WNDPROC, MyWinProc );

    注意,由于你改变了其他窗口的处理函数,那么,如果一个窗口中有特定的响应代码,就会因为不能收不到消息而不会进行处理,所以,如果仅仅是这些处理,外观是改变了,但是窗口的功能却失效了。所以,我们还应当获取窗口的原始的窗口过程函数,把外观处理以外的信息传递给它,使得他的其他代码能够正常执行。

    获取一个窗口原始的窗口过程函数地址:

    OrigWinProc = GetWindowLong( hwnd, GWL_WNDPROC );

    现在,所有的窗口,只有内获取到了他的句柄,就可以用上面的方法的来改变窗口外观了。不过似乎麻烦了点,也就是,只要有窗口创建,就必须获取他的句柄,然后替换窗口处理过程。有多少个窗口,就得执行多少次。而像Skin++类似的界面库,几个函数就可以了,根本不需要这样。

    对,那是因为Skin++使用了API HookHook就是钩子,Hook有两种,消息Hookapi Hook,简单点说,通过Hook,能在某些消息处理,或者某些API被调用的时候,执行某些我们想要的操作。

    看这两个函数:

    HWND CreateWindow(  LPCTSTR lpClassName,

LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam  );

    HWND CreateWindowEx(  DWORD dwExStyle,

LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam );

 

    Windows下面,任何窗口的创建,包括对话框、子窗口、按钮等控件,都由上面这两个API来创建。如果我们Hook了这两个API,也就是但程序调用这两个函数的时候,将会执行我们自己的某个函数,在这个函数中,调用CreateWindowCreateWindowEx,之后再替换新创建的窗口的过程。这样就可以让当前程序里面的所有窗口都使用自己定义的窗口过程,也就能够实现全部窗口界面的风格的改变。

 

    当然啦,还有一个重要的问题,那就是窗口的类型不同,绘制的方法也不一样,对话框和按钮的绘制不能用同样的代码,所以,必须依据窗口的类型来用不同代码完成绘制,注意上面两个函数的参数lpClassName,这个参数指明了窗口类的名称。

 

    还有一点,除了主框架和窗口的绘制,比如菜单和工具栏的绘制也是一个界面库必须考虑的内容,上面没说到。原理类似。

 

    好像表达不好,思路也有点乱。哎,有机会再整理一下。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值