wxPython入门中文版 (Getting Started with wxPython)

本文翻译自http://wiki.wxpython.org/Getting%20Started
首先声明:本人还是个菜鸟,翻译只是为了学习,就当作记笔记了。水平有限,错误和疏漏在所难免,希望各路高手能够给予指导。而且简单查了一下,好像中文世界目前还没有完整的翻译 Getting Started with wxPython 的。

wxPython入门

第一个应用程序:”Hello, World!”

按惯例,我们先来写一个 “Hello, World!” 小程序。这是代码:

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx

app = wx.App(False) #创建1个APP,禁用stdout/stderr重定向
frame = wx.Frame(None, wx.ID_ANY, "Hello, World!")  #这是一个顶层的window
frame.Show(True)    #显示这个frame
app.MainLoop()

解释:

代码 说明
app = wx.App(False) 每一个 wxPython 应用程序都是一个 wx.App 实例。对于大多数的简单程序,直接实例化 wx.App 即可。但如果你希望创建一个复杂的应用程序,那么可以对 wx.App class 做一些扩展。”False” 参数意味着“不要把 stdout 和 stderr 信息重定向到窗口”,当然也可以不加 “False” 参数。
frame = wx.Frame(None, wx.ID_ANY, “Hello, World!”) 完整的语法是 x.Frame(Parent, Id, Title)。在本例中,我们使用 “None” 来表示这个frame是顶层的框架,没有父框架;使用 “wx.ID_ANY” 让 wxWidgets 来给我们挑选一个ID。
frame.Show(True) 显示这个Frame
app.MainLoop() 运行这个应用程序

Note1: 你还可以用 -1 来替代wx.ID_ANY-1 就是默认值的意思。另外 wxWidgets 还提供了其它的标准 ID(v2.8)。 你也可以自定义一个ID,但 Getting Started with wxPython 认为,没有理由那样做,用标准ID更好。
Note2: 实际上,wx.Frame的完整语法是(详细的参数介绍):

wx.Frame(Parent, ID, Title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="frame")

最后运行程序,我们可以看到类似这样的窗口:
wxPython入门中文版 (Getting Started with wxPython)_第1张图片

Windows 还是 Frames?

当人们谈论GUI的时候,他们通常指的是windows,menus和icons。那么自然地,你可能会认为应该用wx.Window来代表屏幕上的一个window。但实际上不是这样的。wx.Window 是一个基础的class,所有的可视化元素,例如buttons, menus等等,都起源于wx.Window 类。而程序窗口则是一个wx.Frame 。新手经常把这2个概念搞混,需要特别留心。

创建一个简单的记事本

现在我们来写一个简单的记事本。在这个例子中,我们会用到几个组件,来理解一些特性或功能,例如事件(events)和回调(callbacks)。

第1步

首先,我们需要创建1个frame,并且这个frame包含1个可编辑的文本框(text box)。文本框需要用wx.TextCtrl 来创建。默认情况下,文本框只能编辑1行文字——无论文字有多长,都不会换行。所以,我们需要用wx.TE_MULTILINE 参数来允许多行编辑。

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title = title, size = (200, 100))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.Show(True)

app = wx.App(False)
frame = MainWindow(None, 'Small editor')
app.MainLoop()

在这个例子中,我们生成一个wx.Frame 的子类,并重写它的__init__ 方法。我们用wx.TextCtrl 来声明一个简单的文本编辑器。注意,因为在MyFrame.__init__ 中已经运行了self.Show() ,所以在创建MyFrame的实例之后,就不用再调用frame.Show() 了。

添加一个菜单栏MenuBar

所有的应用程序都会有一个菜单栏,和一个状态栏。让我们来给这个记事本程序添加一个:

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title = title, size = (200, 100))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.CreateStatusBar()    #创建位于窗口的底部的状态栏

        #设置菜单
        filemenu = wx.Menu()

        #wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的标准ID
        filemenu.Append(wx.ID_ABOUT, u"关于", u"关于程序的信息")
        filemenu.AppendSeparator()
        filemenu.Append(wx.ID_EXIT, u"退出", u"终止应用程序")

        #创建菜单栏
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, u"文件")
        self.SetMenuBar(menuBar)
        self.Show(True)

app = wx.App(False)
frame = MainWindow(None, title = u"记事本")
app.MainLoop()

TIP: wx.ID_ABOUTwx.ID_EXIT 是wxWidgets提供的标准ID(查看全部标准ID)。如果有一个现成的标准ID,最好还是使用它,而不要自定义。因为这样可以让wxWidgets知道,在不同的平台怎样去显示这个组件,使它看起来更美观。

事件处理event handling

我们已经创建了1个记事本,虽然它有菜单,但是什么都做不了。我们希望点击菜单之后,程序能够做出反应,例如退出,或者保存文件。在Python中,点击菜单,点击按钮,输入文本,鼠标移动等等,都被称为事件event,而对event做出反应,则被称为event handling。对不同的event做出不同的响应,这是GUI程序的根本。我们可以使用Bind() 方法,将1个对象Object和1个时间event建立绑定关系。

class MainWindow(wx.Frame):
   def __init__(self, parent, title):
   wx.Frame.__init__(self,parent, title=title, size=(200,100))
   ...
   menuItem = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
   self.Bind(wx.EVT_MENU, self.OnAbout, menuItem)

这段代码意味着:从现在开始,一旦用户点击了菜单中的 “About” 项目,self.OnAbout 就会被执行。

Note: Bind()之后,运行我的程序就提示编码错误,不能再使用中文了,所以下面的代码示例都是全英文的。不知道这是不是python(x,y)独有的问题。谁能帮我解答一下?

wx.EVT_MENU 指代“选择菜单中的项目”这个事件。wxWidgets 提供了很多的事件,可以点这里查看不完整的列表,也可以使用下面的代码打印完整的列表。所有的事件都是wx.Event 的子类。

import wx

for x in dir(wx):
    if x.startswith('EVT_'):
        print x

如果直接运行上面的Bind程序,会提示不存在OnAbout这个attribute。还需要在Class中声明self.OnAbout 方法:

def OnAbout(self, event):
         ...

这里的event参数是wx.Event 的子类的一个实例。

当event发生的时候,method就会被执行。默认情况下,这个method会处理event,并且当callback完成之后,event也会停止。但是在一些结构化的事件处理器event handlers中,我们可以使用event.Skip() 来跳过一个event。例如

def OnButtonClick(self, event):
    if (某种条件):
        做某事()
    else:
        event.Skip()

def OnEvent(self, event):
    ...

当一个点击按钮的事件发生时,OnButtonClick会被调用。如果“某种条件”为真,我们就会“做某事()”。否则我们就会让其它的event handler来处理这个事件。

现在来看看我们的程序:

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title = title, size = (600, 400))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.CreateStatusBar()    # 创建位于窗口的底部的状态栏

        # 设置菜单
        filemenu = wx.Menu()

        # wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的标准ID
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", \
            " Information about this program")    # (ID, 项目名称, 状态栏信息)
        filemenu.AppendSeparator()
        menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", \
            " Terminate the program")    # (ID, 项目名称, 状态栏信息)

        # 创建菜单栏
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")    # 在菜单栏中添加filemenu菜单
        self.SetMenuBar(menuBar)    # 在frame中添加菜单栏

        # 设置events
        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
        self.Bind(wx.EVT_MENU, self.OnExit, menuExit)

        self.Show(True)

    def OnAbout(self, e):
        # 创建一个带"OK"按钮的对话框。wx.OK是wxWidgets提供的标准ID
        dlg = wx.MessageDialog(self, "A small text editor.", \
            "About Sample Editor", wx.OK)    # 语法是(self, 内容, 标题, ID)
        dlg.ShowModal()    # 显示对话框
        dlg.Destroy()    # 当结束之后关闭对话框

    def OnExit(self, e):
        self.Close(True)    # 关闭整个frame


app = wx.App(False)
frame = MainWindow(None, title = "Small editor")
app.MainLoop()

Note1: 上述代码的菜单项目名称”&About”, “E&xit”, “&File” 中的 “&”是做什么用的? “&” 的位置也不一样,分别意味着什么?如果直接print "&About" ,会把 “&” 打印出来。但是在上面的应用程序菜单中看不到 “&”。而且我试过把 “&”去掉,没有任何变化。谁能帮我解答一下?

Note2: 下面代码中的wx.OK 可以省略,此时等于wx.ID_ANY

dlg = wx.MessageDialog(self, "A small text editor.", \
            "About Sample Editor", wx.OK)    # 语法是(self, 内容, 标题, ID)

这是带wx.OK的对话框:
wxPython入门中文版 (Getting Started with wxPython)_第2张图片

这是省略wx.OK的对话框:
wxPython入门中文版 (Getting Started with wxPython)_第3张图片

对话Dialogs

当然,一个文本编辑器不能够没有打开或保存文档的功能——这些功能是由对话来实现的。一般对话由底层平台提供,这样你的应用程序看上去就像是一个原生程序。在本例中,对话由 MainWindowOnOpen 方法来实施:

     def OnOpen(self,e):
         """ Open a file"""
         self.dirname = ''
         dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
         if dlg.ShowModal() == wx.ID_OK:
             self.filename = dlg.GetFilename()
             self.dirname = dlg.GetDirectory()
             f = open(os.path.join(self.dirname, self.filename), 'r')
             self.control.SetValue(f.read())
             f.close()
         dlg.Destroy()

解释:

  • 首先,我们通过调用适当的构造函数来创建对话
  • 然后,我们调用ShowModal 打开对话框 - “Modal” 的意思是,在用户点击 OK 或 Cancel 之前,不能做任何的操作。
  • ShowModal 的返回值是一个被点击按钮的 ID, 如果用户点击了 OK 按钮,程序就读取文件

现在,你可以向菜单中添加相应的条目,并把它链接到OnOpen 方法。如果你遇到了问题,请向下滚动页面,查阅下文的完整代码。

扩展功能

当然,目前这个程序还远不是一个合格的文本编辑器。但是,添加其它的功能并不比我们刚才所完成的内容更难,你可以从 wxPython 提供的 Demo 获取灵感(点此下载Demo,选择版本后,下载 wxPython-demo-x.x.x 文件):

  • Drag and Drop.
  • MDI
  • Tab view/multiple files
  • Find/Replace dialog
  • Print dialog (Printing)
  • Macro-commands in python ( using the eval function)
  • etc …

操作窗口

主题:

  • Frames
  • Windows
  • Controls/Widgets
  • Sizers
  • Validators

在这个章节,我们将会讲解 wxPython 处理窗口和窗口内容的方法,包括创建输入组件,使用各种工具和控件 widgets/controls。 我们将会创建一个计算股票价格的小程序。如果你已经是个有经验的 GUI 开发者,这部分的内容对你来说太简单了,你可以直接阅读下文的 Boa-Constructor 章节。

概述

可见元素的布局

在 frame 里面,你可以使用若干个 wxWindow 子类来充实 frame 的内容,常用的元素有以下几种:

  • wx.MenuBar, 在 frame 的顶部填加菜单栏
  • wx.StatusBar, 在 frame 的底部填加状态栏,显示状态信息
  • wx.ToolBar, 在 frame 中添加工具栏
  • wx.Control 的子类,它们代表用户接口的widgets (例如显示数据 and/or 处理用户输入的可见元素). 常见的wx.Control 对象包括 wx.Button, wx.StaticText, wx.TextCtrlwx.ComboBox.
  • wx.Panel, 它是容纳各种wx.Control 对象的容器。把wx.Control 对象放入wx.Panel, 用户就可以操作它们。

所有的可见元素 (wxWindow 对象和它们的子类) 都能够容纳子元素。例如,一个wx.Frame 可以容纳若干个wx.Panel 对象,而这些wx.Panel 又可以容纳若干wx.Button, wx.StaticTextwx.TextCtrl 对象,就像这样:
wxPython入门中文版 (Getting Started with wxPython)_第4张图片

注意,这仅仅是描述可见元素的相关性,而不是描述应该怎样布局它们。如果要处理元素的布局,有以下几种选择:

  • 可以手工的为每一个元素指定它在父窗口中的像素坐标,但是不同平台的显示效果可能会有差别,例如字体的大小会不一样,所以不推荐此方法
  • 可以使用wx.LayoutConstraints, 但是很复杂
  • 可以使用 Delphi-like LayoutAnchors, 比wx.LayoutConstraints 简单些
  • 使用 wxSizer 的子类,这也是本文将要讲解的。

Sizers

作为wx.Sizer 的子类,Sizer 能够被用来在 frame 或 window 中布置可见元素。它的作用包括:

  • 为每个可见元素计算合适的尺寸
  • 参照一定的尺度为元素定位
  • 当 frame 的尺寸变化时,动态的对元素的尺寸和(或)位置做出调整

一些常见的 Sizer 包括:

  • wx.BoxSizer, 基于水平线或垂直线布置可见元素
  • wx.GridSizer, 按照网格结构来布置元素
  • wx.FlexGridSizer, 与wx.GridSizer 类似,但更加灵活

通过调用sizer.Add(window, options...) 或者 sizer.AddMany(...) 来给出一个wx.Window 对象的列表,sizer 就能够布置它们. Sizer 还能够嵌套,你可以把 1 个 sizer 放进另 1 个 sizer 里面,例如把 2 个按水平线布置按钮的wx.BoxSizer 放进另 1 个按垂直线布置元素的wx.BoxSizer 里面,就像这样:
wxPython入门中文版 (Getting Started with wxPython)_第5张图片

NOTE: 在上面的例子中,6 个按钮并不是按照 2 行 3 列来做阵列式布局的,如果要那样做,你必须使用wx.GridSizer

接下来,我们给我们的文本编辑器增加 2 个嵌套的 sizer,把 1 个水平布局的 sizer 嵌入到 1 个垂直布局的 sizer 里面:

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
import os

class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        self.dirname = ''

        # "-1"这个尺寸参数意味着通知wxWidget使用默认的尺寸
        # 在这个例子中,我们使用200像素的宽度,和默认的高度
        wx.Frame.__init__(self, parent, title = title, size = (200, -1))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.CreateStatusBar()    # 创建位于窗口的底部的状态栏

        # 设置菜单
        filemenu = wx.Menu()

        # wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的标准ID
        menuOpen = filemenu.Append(wx.ID_OPEN, "&Open", " Open a file")
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", \
            " Information about this program")    # (ID, 项目名称, 状态栏信息)
        filemenu.AppendSeparator()
        menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", \
            " Terminate the program")    # (ID, 项目名称, 状态栏信息)

        # 创建菜单栏
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")    # 在菜单栏中添加filemenu菜单
        self.SetMenuBar(menuBar)    # 在frame中添加菜单栏

        # 设置events
        self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen)
        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
        self.Bind(wx.EVT_MENU, self.OnExit, menuExit)

        # 设置sizers
        self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        self.buttons = []
        for i in range(0, 6):
            self.buttons.append(wx.Button(self, -1, "Button &" + str(i)))
            self.sizer2.Add(self.buttons[i], 1, wx.SHAPED)    

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.control, 1, wx.EXPAND)    
        self.sizer.Add(self.sizer2, 0, wx.GROW)    

        # 激活sizer
        self.SetSizer(self.sizer)
        self.SetAutoLayout(True)
        self.sizer.Fit(self)     
        self.Show(True)

    def OnAbout(self, e):
        # 创建一个带"OK"按钮的对话框。wx.OK是wxWidgets提供的标准ID
        dlg = wx.MessageDialog(self, "A small text editor.", \
            "About Sample Editor", wx.OK)    # 语法是(self, 内容, 标题, ID)
        dlg.ShowModal()    # 显示对话框
        dlg.Destroy()    # 当结束之后关闭对话框

    def OnExit(self, e):
        self.Close(True)    # 关闭整个frame

    def OnOpen(self, e):
        """ open a file. """
        # wx.FileDialog语法:(self, parent, message, defaultDir, defaultFile, 
        #                    wildcard, style, pos)
        dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*",
                            wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            f = open(os.path.join(self.dirname, self.filename), 'r') # 暂时只读
            self.control.SetValue(f.read())
            f.close()
        dlg.Destroy()

app = wx.App(False)
frame = MainWindow(None, title = "Small editor")
app.MainLoop()

sizer.Add 方法有 3 个参数(语法):

  • 第 1 个参数把 1 个控件添加到 sizer 里面
  • 第 2 个参数是 proportion 权重因子,代表着这个控件相对于其它控件所占有的空间比例。例如,如果你有 3 个编辑控件,你希望它们的空间比例是 3:2:1,那么在把它们加到 sizer 里面的时候,就按照这个比例数值来指定权重因子。如果权重因子为 “0”,意味着这个控件或者 sizer,在它的父 sizer 的布局方向上的尺寸,不会随着 frame 的增大(缩小)而增大(缩小)。在上面的例子中,
  • 第 3 个参数 flag 通常用wx.GROW 或者wx.EXPAND, 它们的作用是一样的,这意味着控件可以调整自己的尺寸以适应 frame 尺寸的变化。如果使用wx.SHAPED 来充当第 3 个参数,那么控件的尺寸虽然可以变化,但是形状会保持不变。

在上面的例子中,self.sizer.Add(self.sizer2, 0, wx.GROW) 权重因子是 0,所以我们可以看到无论 frame 的形状怎么变,self.sizer2 的高度是一直不变的,因为它的父 sizer self.sizer 是按照垂直线来布置元素的。而self.sizer2 的宽度可以变,因为第 3 个参数是wx.GROW

另外,self.sizer2.Add(self.buttons[i], 1, wx.SHAPED) 第 3 个参数是wx.SHAPED,所以无论 frame 的形状和尺寸怎样变化,按钮的形状都不会变,长度和宽度一直保持着相同的比率。

flag 参数也可以使用wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_CENTER_VERTICAL, 或wx.ALIGN_CENTER (for both) 来设置元素的居中方式,还可以使用wx.ALIGN_LEFT, wx.ALIGN_TOP, wx.ALIGN_RIGHT, wx.ALIGN_BOTTOM 中的 1 个或 2 个组合,来设置元素的对齐方式。默认的对齐方式是wx.ALIGN_LEFT | wx.ALIGN_TOP.

wx.Sizer 和它的子类有一个可能会让人感到困惑的地方,就是 sizer 和父窗口之间的区别。当你把一个对象添加到 sizer 里面时,不需要指定这个对象的父窗口。sizer 只是对窗口布局的方式,它本身并不是窗口。但是在创建对象的时候就需要指定父窗口。在上面的例子中,使用wx.Button (语法)创建按钮的时候就需要指定 frame 或 window 作为按钮的父窗口,而不是指定 sizer 来当父窗口。

一旦你完成可见元素的设置,并把它们加入到 sizer(或者嵌套的 sizer),下一步就是告诉 frame 或 window 来使用 sizer。用以下 3 个必要的步骤来完成这项工作:

window.SetSizer(sizer)
window.SetAutoLayout(True)
sizer.Fit(window)
  • SetSizer() 告诉你的 window (or frame) 应该使用哪个sizer。
  • SetAutoLayout() 告诉你的 window 使用 sizer 来布局组件
  • sizer.Fit() 告诉 sizer 计算它所容纳的元素的初始化尺寸和位置

菜单Menus

我们已经在上文讲解过菜单的设置和使用方法,不再累述。

验证器Validators

当你创建一个对话框或者输入控件的时候,可以使用wx.Validator 来简化控件加载数据的进程,对输入的数据进行验证,或从中摘录数据。wx.Validator 还可以被用来截取控件域内发生的一些事件,例如敲击键盘的动作。要使用验证器,你必须先定义一个wx.Validator 的子类 (既不是wx.TextValidator 也不是wx.GenericValidator),然后再调用myInputField.SetValidator (myValidator) 把它关联到你的控件域。

NOTE1: 你定义的wx.Validator 子类必须覆盖wxValidator.Clone() 方法。
NOTE2: 原文并没有进一步的讲解 Validators 的设置和使用方法,不过你可以参考这个 bing.com 的网页快照

实例讲解

在 Panel 中创建第 1 个 Label

现在我们来写一个小程序,这个程序很简单,frame 中只有一个包含有一个标签label[7] 的面板panel[8]:

# -*- coding: utf-8 -*-
"""
Created on Sun Dec 20 20:46:32 2015

@author: chenghit
"""
import wx
class ExampleFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        panel = wx.Panel(self)
        self.quote = wx.StaticText(panel, label="Your quote:", pos=(20, 30))
        self.Show()

app = wx.App(False)
ExampleFrame(None)
app.MainLoop()

如果你读完前面怎样编写文本编辑器的部分,你一定会觉得这个程序非常简单。但需要注意的是,在这里应该用一个 sizer 来布置组件,而不应该手工的一一指定它们的位置。注意这行代码:

self.quote = wx.StaticText(panel, label="Your quote:", pos=(20, 30))

wxStaticText 的 parent 参数是一个 panel。我们的静态文本将陈列在我们刚刚创建的 panel 上面,并使用了wxPoint 参数来定义位置。根据wx.StaticText 的语法,还可以定义一个wxSize 参数,但是在这个例子中并没有采用。

[7] 根据 wxPython 的文档:

Panel 就是放置组件的窗口,它通常被放置在 frame 里面。在继承它的父类 wxWindow 的基础上,Panel 还含有一些额外的,细微的功能性。Panel 的主要目的是在功能性和外观上和对话框相似,但是又有作为父窗口的灵活性。

事实上, 对于那些处理文字录入的对象(通常被称作控件或组件)来说,Panel 就是个灰色的背景。

[8] label 的作用仅仅是显示文本,并不和用户进行交互。

添加更多的控件

你可以在 wxPython 的 demo 和 docs 中种类繁多的控件,但是本文将只会讲解其中最常用的几种:

  • wxButton 是最基本的控件: 它是一个你可以点击的按钮,并带有文字。下面是一个 “Clear” 按钮的例子(比方说,你点击之后会清空文字):
clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
  • wxTextCtrl 这个控件可以让用户输入文字,它产生 2 种主要的事件:如果文字被改变了,它会调用 EVT_TEXT ;如果键盘被按下,它会调用 EVT_CHAR。根据下面的例子,如果你按下了 “Clear” 按钮,将只会产生一个 EVT_TEXT 事件,而不会产生 EVT_CHAR 事件。
textField = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnChange, textField)
self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
  • wxComboBox 下拉菜单,和 wxTextCtrl 很像,但是除了 EVT_TEXT 和 EVT_CHAR 之外,wxComboBox 还能够生成 EVT_COMBOBOX 事件. ComboBox 可以是 “下拉菜单+复选框” , 可以是 “下拉菜单+表格”…可以点击这里查看 ComboBox 的示例,虽然是 C# 写的,但 ComboBox 的概念是相同的。
  • wxCheckBox 复选框,可以让用户做出 true/false 的选择
  • wxRadioBox 单选框,可以让用户从一个列表中做出选择

现在让我们来丰富我们的程序:

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""

import wx
class ExamplePanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.quote = wx.StaticText(self, label='Your quote:', pos=(20, 30))

        # 这个多行的文本框只是用来记录并显示events,不要纠结之
        self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), 
                                  style=wx.TE_MULTILINE | wx.TE_READONLY)

        # 一个按钮                          
        self.button = wx.Button(self, label='Save', pos=(200, 325))
        self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)

        # 仅有1行的编辑控件
        self.lblname = wx.StaticText(self, label='Your name:', pos=(20, 60))
        self.editname = wx.TextCtrl(self, value='Enter here your name:',
                                    pos=(150, 60), size=(140, -1))
        self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
        self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)

        # 一个ComboBox控件(下拉菜单)
        self.sampleList = ['friends', 'advertising', 'web search', \
                           'Yellow Pages']
        self.lblhear = wx.StaticText(self, label="How did you hear from us ?", 
                                     pos=(20, 90))
        self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1), 
                                    choices=self.sampleList, 
                                    style=wx.CB_DROPDOWN)
        self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
        # 注意ComboBox也绑定了EVT_TEXT事件
        self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)

        # 复选框
        self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?",
                                  pos=(20,180))
        self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)

        # 单选框
        radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', \
                     'navy blue', 'black', 'gray']
        self.rb = wx.RadioBox(label="What color would you like ?", 
                              pos=(20, 210), choices=radioList, \
                              majorDimension=3, style=wx.RA_SPECIFY_COLS)
        self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, self.rb)

    def OnClick(self, event):
        self.logger.AppendText('Click on object with Id %d\n' % \
                               event.GetId())
    def EvtText(self, event):
        self.logger.AppendText(self, 'EvtText: %s\n' % event.GetString())
    def EvtChar(self, event):
        self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
        event.Skip()
    def EvtComboBox(self, event):
        self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
    def EvtCheckBox(self, event):
        self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
    def EvtRadioBox(self, event):
        self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())


app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
app.MainLoop()

现在我们的 class 变得很复杂,我们添加了很多的控件,并且它们是可以交互的。我们还添加了一个 wxTextCtrl 控件来显示其它控件产生的事件:
wxPython入门中文版 (Getting Started with wxPython)_第6张图片

The notebook

有时候,一个表单(form)太大了,无法在一页内完整的显示。这时候就要用到wxNoteBook,它允许用户通过点击标签在几个页面之间快速的浏览。我们先把wxNoteBook 放进 frame,然后再用AddPage 把上面的 Panel 放进wxNoteBook:

app = wx.App(False)
frame = wx.Frame(None, title="Demo with Notebook")
nb = wx.Notebook(frame)

# 这里把ExamplePanel重复3次放进Notebook
nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
nb.AddPage(ExamplePanel(nb), "Page Two")
nb.AddPage(ExamplePanel(nb), "Page Three")

frame.Show()
app.MainLoop()

NOTE: 现在 ExamplePanel 的父窗口是 Notebook 了,这很关键。

使用 sizer 布局元素

严格的定义每个元素的位置并不会带来理想的显示效果,因为总是有很多原因导致 frame 的尺寸并不是我们希望的那样的大小。上文我们已经讲解过wx.BoxSizer, wx.GridSizer, 和wx.FlexGridSizer, 现在我们再介绍一种:wx.GridBagSizer. 你一定用过Excel,一定做过“合并单元格”的操作吧?对了,wxGridBagSizer 就是合并单元格之后的wxGridSizer。 GridBagSizer介绍,GridBadSizer教程

在下面采用了 GridBagSizer 的例子中,”pos” 参数控制组件放置的坐标位置,(0, 0) 意味着组件紧贴在左上角,而 (3, 5) 则意味着组件要再向下 3 行,再向右 5 列。”span” 就是合并单元格的参数:

# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""

import wx
class ExamplePanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        # 创建一些Sizer
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        grid = wx.GridBagSizer(hgap=5, vgap=5)    # 行和列的间距是5像素
        hSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.quote = wx.StaticText(self, label='Your quote:', pos=(20, 30))
        grid.Add(self.quote, pos=(0,0))    # 加入GridBagSizer

        self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)

        self.button = wx.Button(self, label='Save', pos=(200, 325))
        self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)

        self.lblname = wx.StaticText(self, label='Your name:', pos=(20, 60))
        grid.Add(self.lblname, pos=(1,0))
        self.editname = wx.TextCtrl(self, value='Enter here your name:', pos=(150, 60), size=(140, -1))
        grid.Add(self.editname, pos=(1,1))
        self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
        self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)

        # 向GridBagSizer中填充空白的空间
        grid.Add((10, 40), pos=(2,0))


        self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
        self.lblhear = wx.StaticText(self, label="How did you hear from us ?", pos=(20, 90))
        grid.Add(self.lblhear, pos=(3,0))
        self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1), choices=self.sampleList, style=wx.CB_DROPDOWN)
        grid.Add(self.edithear, pos=(3,1))
        self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
        self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)

        self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?", pos=(20,180))
        # 加入Sizer的同时,设置对齐方式和边距
        grid.Add(self.insure, pos=(4,0), span=(1,2), flag=wx.BOTTOM, border=5)
        self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)

        radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
        self.rb = wx.RadioBox(self, label="What color would you like ?", pos=(20, 210), choices=radioList, 
                              majorDimension=3, style=wx.RA_SPECIFY_COLS)
        grid.Add(self.rb, pos=(5,0), span=(1,2))    # 合并了1行2列的单元格
        self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, self.rb)

        hSizer.Add(grid, 0, wx.ALL, 5)
        hSizer.Add(self.logger)
        mainSizer.Add(hSizer, 0, wx.ALL, 5)
        mainSizer.Add(self.button, 0, wx.CENTER)
        # 可以把SetSizer()和sizer.Fit()合并成一条SetSizerAndFit()语句
        self.SetSizerAndFit(mainSizer)

    def OnClick(self, event):
        self.logger.AppendText('Click on object with Id %d\n' % event.GetId())
    def EvtText(self, event):
        self.logger.AppendText('EvtText: %s\n' % event.GetString())
    def EvtChar(self, event):
        self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
        event.Skip()
    def EvtComboBox(self, event):
        self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
    def EvtCheckBox(self, event):
        self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
    def EvtRadioBox(self, event):
        self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())


app = wx.App(False)
frame = wx.Frame(None, title="Demo with Notebook")
nb = wx.Notebook(frame)

nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
nb.AddPage(ExamplePanel(nb), "Page Two")
nb.AddPage(ExamplePanel(nb), "Page Three")

frame.Show()
app.MainLoop()

对用户的动作进行回馈

原文基本上到这里就结束了,后面的 Drawing 相关的内容只是列出了标题,却没有介绍。如果要编写小游戏,这部分内容是很关键的,太遗憾了。

先发出来,附加的内容再慢慢补充。

你可能感兴趣的:(python)