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

本文翻译自http://wiki.wxpython.org/Getting%20Started
首先声明:本人仍是个菜鸟,翻译只是为了学习,就看成记笔记了。水平有限,错误和疏漏在所不免,但愿各路高手可以给予指导。并且简单查了一下,好像中文世界目前尚未完整的翻译 Getting Started with wxPython 的。html

wxPython入门

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

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

# -*- 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()

解释:web

代码 说明
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")

最后运行程序,咱们能够看到相似这样的窗口:
Hello Worldwindows

Windows 仍是 Frames?

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

建立一个简单的记事本

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

第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

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

# -*- 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的对话框:

这是省略wx.OK的对话框:
这里写图片描述

对话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 对象,就像这样:
这里写图片描述

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

  • 能够手工的为每个元素指定它在父窗口中的像素坐标,可是不一样平台的显示效果可能会有差异,例如字体的大小会不同,因此不推荐此方法
  • 可使用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 里面,就像这样:
这里写图片描述

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 控件来显示其它控件产生的事件:
这里写图片描述

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 就是合并单元格以后的wxGridSizerGridBagSizer介绍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 相关的内容只是列出了标题,却没有介绍。若是要编写小游戏,这部份内容是很关键的,太遗憾了。

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