首页 > 代码库 > 创建窗口的应用程序

创建窗口的应用程序

通过前面的基础知识的学习,已经对ctypes库的基本功能学会了使用,为了加强这种知识训练,以及运用刚学习的知识的快乐,我们来创建一个稍稍复杂一点的应用程序。这个应用程序是所有后面应用程序的基础,只有学会这个程序的基本原理,才可以更进一步学习WIN32API程序开发。更何况后面所有程序都会在这个程序上修改而成,这个就相当你的爬向高楼顶层的第一个台阶。在所有工程技术里,都会从一个小小的演示程序开始,逐渐地增加功能,慢慢地变成一个强大的应用程序。如果发现这些API函数不清楚,可以参考我写的另外一本书《Windows API一日一练》,在那本书里有完整的介绍和例子,不过这些例子使用C++来编写的。

下面就来开始浏览整个程序的功能吧,第一步是先把ctypes库导入,这样才可以使用它的功能,并且还导入ctypes.wintypes,它有对应WIN32的类型,这样使用起来更方便,比如消息结构MSG。这段代码如下:

#windows应用程序

from ctypes import *

from ctypes.wintypes import *

接着下来定义一个Windows的窗口回调函数的原型,因为Windows指定了每个窗口都需要有一个窗口消息函数来处理窗口所有发生的事件消息,这样你写的应用程序才有机会去响应窗口所发生的事件或操作。这行代码如下:

WNDPROCTYPE = WINFUNCTYPE(c_int, HWND, c_uint, WPARAM, LPARAM)

由于WIN32的定义常量全部是在C语言的头文件里定义,要想在Python中直接使用是不行的。目前只有两种方法,一个是使用一个转换程序,把所有SDK里提供的头文件进行转换为Python定义的形式的模块文件;另外一个就是自己使用到那些,就定义那些常量。本例子里,采用的是最后一种方法,使用到那些就定义就定义那些常量,这样做是为了这个例子保持简单,这些常量定义如下:

WS_EX_APPWINDOW = 0x40000

WS_OVERLAPPEDWINDOW = 0xcf0000

WS_CAPTION = 0xc00000

SW_SHOWNORMAL = 1

SW_SHOW = 5

CS_HREDRAW = 2

CS_VREDRAW = 1

CW_USEDEFAULT = 0x80000000

WM_DESTROY = 2

WHITE_BRUSH = 0

在这些常量主要是窗口的格式、样式、以及显示方式,还有一个消息定义和画笔。由于每个新创建的窗口都需要在Windows里注册唯一的名称,以便创建时从系统内存里找到相应的窗口类,所有其它窗口可以以这个窗口为基本模板,复制N个相同的出来。每个窗口还有一个主题名称,又叫做窗口名称,这两个定义常量如下:

#窗口类的名称

wclassName = u‘ShenzhenCai‘

wname = u‘Hello World‘ 

第一个常量窗口类名称,第二个常量是窗口显示的标题。

窗口的格式、样式通过定义一个结构的方式传递给WIN32API函数,在这里定义窗口类的结构体如下:

#定义窗口类结构

class WNDCLASSEX(Structure):

    _fields_ = [("cbSize", c_uint),

                ("style", c_uint),

                ("lpfnWndProc", WNDPROCTYPE),

                ("cbClsExtra", c_int),

                ("cbWndExtra", c_int),

                ("hInstance", HANDLE),

                ("hIcon", HANDLE),

                ("hCursor", HANDLE),

                ("hBrush", HANDLE),

                ("lpszMenuName", LPCWSTR),

                ("lpszClassName", LPCWSTR),

                ("hIconSm", HANDLE)]

这个窗口类的结构体与C语言里描述的结构体是一样的,通过这样定义,就可以给窗口说明窗口的类型了。具体每项的值,请参考《Windows API一日一练》。接着下来,查看窗口回调函数,这里定义了处理窗口收到消息。主要处理了当窗口关闭(WM_DESTROY)时就退出程序。调用API函数PostQuitMessage发送消息给主程序循环,需要结束应用程序了。其它的消息都是调用API函数DefWindowProcW来做默认的处理。

#窗口消息处理回调函数

def PyWndProc(hWnd, Msg, wParam, lParam):

    if Msg == WM_DESTROY:

        windll.user32.PostQuitMessage(0)

    else:

        return windll.user32.DefWindowProcW(hWnd, Msg, wParam, lParam)

    return 0

接着下来是设置窗口类的属性,注册窗口的类型,以便后面可以创建这个窗口,这里主要是调用函数RegisterClassExW来实现。同时这里把前面的回调函数WndProc、程序实例句柄hInst、窗口类名称wclassName组合一起了。

#初始化主窗口  

def initwinclass(hInst, WndProc):     

    wndClass = WNDCLASSEX()

    wndClass.cbSize = sizeof(WNDCLASSEX)

    wndClass.style = CS_HREDRAW | CS_VREDRAW

    wndClass.lpfnWndProc = WndProc

    wndClass.cbClsExtra = 0

    wndClass.cbWndExtra = 0

    wndClass.hInstance = hInst

    wndClass.hIcon = 0

    wndClass.hCursor = 0

    wndClass.hBrush = windll.gdi32.GetStockObject(WHITE_BRUSH)

    wndClass.lpszMenuName = 0

    wndClass.lpszClassName = wclassName

    wndClass.hIconSm = 0

    

    return windll.user32.RegisterClassExW(byref(wndClass)) 

窗口的回调函数已经定义,窗口类型又已经注册,接着下来就是创建窗口,并把窗口显示出来了,代码如下:

#主函数入口    

def main():

    hInst = windll.kernel32.GetModuleHandleW(None)

    WndProc = WNDPROCTYPE(PyWndProc) 

    if initwinclass(hInst, WndProc) <= 0:

        return False

    

    hWnd = windll.user32.CreateWindowExW(

        0, wclassName, wname,

        WS_OVERLAPPEDWINDOW | WS_CAPTION,

        CW_USEDEFAULT, CW_USEDEFAULT,

        800, 600, 0, 0, hInst, 0)

    

    if not hWnd:

        print(‘Failed to create window‘)

        exit(0)

        

    windll.user32.ShowWindow(hWnd, SW_SHOW)

    windll.user32.UpdateWindow(hWnd)

    

    msg = MSG()

    lpmsg = pointer(msg)

    print(‘Entering message loop‘)

    while windll.user32.GetMessageW(lpmsg, 0, 0, 0) != 0:

        windll.user32.TranslateMessage(lpmsg)

        windll.user32.DispatchMessageW(lpmsg)

 

    print(‘done.‘)

在这个主函数里,融合前面介绍内容。如果对前面介绍都比较了解,那么在这里就很容易理解它,如果还是不懂,请再查看前面的内容,多练习一下。GetModuleHandleW函数是用来获取当前进程的实例句柄,WNDPROCTYPE是用来转换回调函数,initwinclass函数是用来初始化窗口类型并注册,CreateWindowExW函数是用来创建窗口,ShowWindow函数是用来显示窗口出来,UpdateWindow是用来刷新窗口的显示客户区,pointer函数是用来转换消息指针,GetMessageW函数是获取窗口的消息,TranslateMessage函数是用来转换消息,DispatchMessageW函数是把转换后的消息发送到消息队列。通过创建窗口、显示窗口及更新窗口这三步曲,就完成窗口的显示。后面通过消息循环处理,响应了窗口所有的事件处理。最后还差一个触发这个主函数main的方式,它的实现如下:

if __name__ == "__main__":

    print( "Win32 Application in python" )

    main()

这里使用了Python的一个特性,当本文件最先运行加载时,就会设置当前文件运行的名称为__main__属性,这样就会调用main函数运行了。当你运行这个例子时,最激动人心的时刻到来了,它运行的结果如下:


那个白色的窗口,就是我们创建的窗口。通过使用ctypes库,就可以使用WIN32API函数,并且实现最大化地使用Python方式的代码来书写。当然,这里主要演示是WIN32 API的调用,其实ctypes还可以使用在测试领域的,比如你写了一个C++的动态连接库,这个动态连接库是用来对文件压缩的,你需要对它进行测试,就可以写一个Python的程序来调用这个动态连接库,比写起C++的测试代码要快得多,因为它不需要进行编译的步骤,也没有什么临时文件生成,并且可以随时修改,比较灵活的。要想提高软件的开发效率,在目前这种环境之下,需要掌握多种语言才可行,比如要运行性能高的就使用C++来编写,要开发速度快性能要求不高的就使用Python来编写,像很多测试用例,自动化的工具等。混合编程已经是目前的主流方向,正在学习编程的小伙伴们努力、加油吧。