原文:
1. 第一个应用程序:Hello World
按照“国际惯例”,我们先写一个“Hello World”的应用程序,下面是代码:
1 #!/usr/bin/env python2 import wx3 4 app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window.5 frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window.6 frame.Show(True) # Show the frame.7 app.MainLoop()
[批注:
1. 在期望运行该程序前,需要安装 Python 和 wxPython,否则,我想它不能正常运行
2. 如果需要在代码的添加中文,需要在代码前面添加编码格式,否则,我想它也不能正常运行。
范例代码如下:
1 #coding=utf-82 #!/usr/bin/env python3 import wx4 5 # 这是一个简单的 "Hello World" 应用程序6 app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window.7 frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window.8 frame.Show(True) # Show the frame.9 app.MainLoop()
]
说明:
+-------------------------------------------------------------------------------------------------------------------
app = wx.App(False)
# 该 app 是 wx.App 的一个实例,对于大多数简单的应用程序就是一个 wx.App 对象,
# 在需要创建复杂的应用程序时需要使用继承 wx.App 类,参数 “False”表示不将标准输出和标准错误重定向到窗口。
+-------------------------------------------------------------------------------------------------------------------
frame = wx.Frame(None, wx.ID_ANY, "Hello World")
# 一个 wx.Frame 是一个最顶层的窗口,语法是:wx.Frame(Parent, Id, Title)。
# 大多数的使用传递的参数是(一个父对象,一个ID号)。
# 在该例子中,“None” 代表没有父对象,“wx.ID_ANY” 表示由 wxWidgets 自动为我们选择一个 ID 号。
+-------------------------------------------------------------------------------------------------------------------
frame.Show(True)
# 使用 Show 方法,使窗口生效并显示(True)或隐藏(False)
+-------------------------------------------------------------------------------------------------------------------
app.MainLoop()
# 最后,我们启动应用程序的主循环来处理事件
+-------------------------------------------------------------------------------------------------------------------
注意:你大多数时候都希望使用 wx.ID_ANY 或者其他的标准 ID 值(参见:)。你可以自己定义 ID 值,但是没有必要。
执行程序,然后你将看到类似下面的一个窗口:
1.1. “是胡不是霍,是霍躲不过” [Windows 还是 Frames] ?
当大家讨论界面 GUI 时,经常会说:窗口windows、菜单memus、图标icons。自然而然,就会认为 wx.Window 表示
屏幕上的窗口。不幸的是,却不是这么回事。一个 wx.Window 是一个基本的类对象,用户衍生出可见的原始对象(如:按钮、
菜单),而且一个程序的窗口是一个 wx.Frame 实例。对新手来说,这写不一致可能导致一些困惑。
2. 创建一个简单的文本编辑器
我们将在这部分教程中学习创建一个简单的编辑器。在这个过程中,我们将研究几个配件(widgets,如:按钮,编辑框)
的使用,并且会学到它们的一些特性,比如:事件以及事件的回调处理(events and callbacks)。
2.1 第一步
第一步,我们创建一个带有编辑框的窗口。编辑框通过 wx.TextCtrl 创建。默认情况,创建的编辑框只能单行输入,可以
使用 wx.TE_MULTLINE 来允许输入多行。
1 #!/usr/bin/env python 2 import wx 3 4 class MyFrame(wx.Frame): 5 """ We simply derive a new callss of Frame. """ 6 def __init__(self, parent, title): 7 wx.Frame.__init__(self, parent, title = title) 8 self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE) 9 self.Show(True)10 11 app = wx.App(False)12 frame = MyFrame(None, 'Small Editor')13 app.MainLoop()
在这个例子中,我继承了 wx.Frame 并且重写了它的 __init__ 方法。我们使用了 wx.TextCtrl 创建了一个编辑框。因为
我们在 MyFrame 的 __init__ 方法里运行了 self.Show(),所以不需要显式的调用 frame.Show()。
2.2 添加菜单栏
每一个应用程序都应该有一个菜单栏和状态栏。来,我们一起来添加这些功能:
1 import wx 2 3 class MainWindow(wx.Frame): 4 def __init__(self, parent, title): 5 wx.Frame.__init__(self, parent, title=title, size=(200,100)) 6 self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE) 7 self.CreateStatusBar() # A Statusbar in the bottom of the window 8 9 # Setting up the menu.10 filemenu= wx.Menu()11 12 # wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.13 filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")14 filemenu.AppendSeparator()15 filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")16 17 # Creating the menubar.18 menuBar = wx.MenuBar()19 menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar20 self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.21 self.Show(True)22 23 app = wx.App(False)24 frame = MainWindow(None, "Sample editor")25 app.MainLoop()
[批注:
1. 想使用中文?Of course!
两个地方需要修改:
1. 代码开始地方添加代码的编码格式注释
2. 非注释的中文表明传递的是 utf-8 编码
示例代码如下:
1 #coding=utf-8 2 #!/usr/bin/env python 3 4 import wx 5 6 class MainWindow(wx.Frame): 7 def __init__(self, parent, title): 8 wx.Frame.__init__(self, parent, title = title, size = (300, 400)) 9 self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)10 self.CreateStatusBar() # A Statusbar in the botton of the window11 12 # Setting up the menu.13 filemenu = wx.Menu()14 15 # wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.16 filemenu.Append(wx.ID_ABOUT, "&About", u"关于简易编辑器")17 filemenu.AppendSeparator()18 filemenu.Append(wx.ID_EXIT, "E&xist", u"退出应用程序")19 20 # Creating the menubar.21 menuBar = wx.MenuBar()22 menuBar.Append(filemenu, "&File") # Adding the "filemenu" to the MenuBar23 self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.24 self.Show(True)25 26 app = wx.App(False)27 frame = MainWindow(None, "Sample editor")28 app.MainLoop()
2. 参数 "&About","E&xist",符号 "&" 代表可以使用快捷键 "Alt + A" 和 "Alt + X" 的意思。
同理 "&File" 代表可以使用快捷键 "Alt + F" 来激活 "File" 菜单栏
]
小建议:注意 wx.ID_ABOUT 和 wx.ID_EXIT 这个两个 ID 值没!?它们是 wxWidgets 提供的标准 ID 值。使用标准 ID 值
是一个好习惯。对于这些标准 ID 值, wxWidgets 能够根据不同的平台展示出更好的效果。
2.3 添加事件处理
在 wxPython 编程中,对事件作出反应叫做事件处理。一个事件代表某种操作(如:点击按钮、输入文本、移动鼠标)
发生在了你的应用程序上面。大多数界面程序都会有事件处理机制。你可以使用 Bind() 方法将一个对象和一个事件关联起来:
1 class MainWindow(wx.Frame):2 def __init__(self, parent, title):3 wx.Frame.__init__(self,parent, title=title, size=(200,100))4 ...5 menuItem = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")6 self.Bind(wx.EVT_MENU, self.OnAbout, menuItem)
现在,但你选择了 "About" 这个菜单选项,self.OnAbout 将会被执行。wx.EVT_MENU 代表:选择了一个菜单选项。
wxWidgets 支持很多这样的事件(参见:)。self.OnAbout 定义格式如下:
1 def OnAbout(self, event):2 ...
参数 "event" 回一个 wx.Event 的实例。例如:按钮点击事件 - wx.EVT_BUTTON 是 wx.Event 的一个子类。
当事件发生时,函数方法将被调用执行。默认情况下,函数会处理事件,函数执行完毕后事件会停止。然后,你可以使用
event.Skip() 来跳过该层的函数处理。如下:
1 def OnButtonClick(self, event):2 if (some_condition):3 do_something()4 else:5 event.Skip()6 7 def OnEvent(self, event):8 ...
当点击按钮的事件发生时,方法 OnButtonClick 将被调用。如果 "some_condition" 为真,我们就 do_something(),
否则,我们就不用处理该事件,而让较外层的事件处理机制捕获事件并处理。
[批注:
1. 此处描述的 "较外层的事件处理机制捕获事件并处理" 类似 C++/Java 的异常处理机制一样。
例如:某一件事情发生了,需要你第一时间处理,而你使用了 wx.Skip(),就相当于你告知了你所知道的上一级的
部门你不处理该事情,然后该部门会处理这个事件,因为该部门使用了 do_something()。
]
来,让我们来看看我们的编辑器:
1 import os 2 import wx 3 4 5 class MainWindow(wx.Frame): 6 def __init__(self, parent, title): 7 wx.Frame.__init__(self, parent, title=title, size=(200,100)) 8 self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE) 9 self.CreateStatusBar() # A StatusBar in the bottom of the window10 11 # Setting up the menu.12 filemenu= wx.Menu()13 14 # wx.ID_ABOUT and wx.ID_EXIT are standard ids provided by wxWidgets.15 menuAbout = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")16 menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")17 18 # Creating the menubar.19 menuBar = wx.MenuBar()20 menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar21 self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.22 23 # Set events.24 self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)25 self.Bind(wx.EVT_MENU, self.OnExit, menuExit)26 27 self.Show(True)28 29 def OnAbout(self,e):30 # A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.31 dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK)32 dlg.ShowModal() # Show it33 dlg.Destroy() # finally destroy it when finished.34 35 def OnExit(self,e):36 self.Close(True) # Close the frame.37 38 app = wx.App(False)39 frame = MainWindow(None, "Sample editor")40 app.MainLoop()
注意:对于代码
1 dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK)
我们可以省略最后的一个参数。那样的话, wxWidget 会自动产生一个 ID 值。和使用参数 wx.ID_ANY 一样的。
1 dlg = wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor")
2.4. 对话框
当然,一个不能保存或打开文档的编辑器是没有太多用处的。所以引入通用对话框。这些对话框是由底层平台提供,
所以它们看起来会自然。让我们来看看 MainWindow 的 OnOpen 方法:
1 def OnOpen(self, e): 2 """ Open a file """ 3 self.dirname = ''; 4 dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN) 5 if dlg.ShowModal() == wx.ID_OK: 6 self.filename = dlg.GetFilename() 7 self.dirname = dlg.GetDirectory() 8 f = open(os.path.join(self.dirname, self.filename), 'r') 9 self.control.SetValue(f.read())10 f.close()11 dlg.Destroy()
说明:
1. 我们使用文件对话框的构造函数来创建对话框
2. 我们调用 ShowModal,将会创建一个对话框,Modal 表示这个对话框不会做任何事情,直到“OK”或“Cancel”
被点击。
3. ShowModal 的返回值是被点击的按钮的 ID 值。如果用户点击了 "OK" ,我们就读取文件。
现在你可以添加对应的菜单栏,并且和 OnOpen 方法绑定起来。如果你有什么问题,可以参考最后的程序的所有
代码。
[批注:
1. 有的中文文档打不开?有可能是文档的编码格式不是 python 默认采用的解码格式。
可参考如下代码:
1 #encoding=utf-8 2 #!/usr/bin/env python 3 4 import os 5 import wx 6 7 class MainWindow(wx.Frame): 8 def __init__(self, parent, title): 9 wx.Frame.__init__(self, parent, title = title, size = (400, 300))10 self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)11 self.CreateStatusBar()12 13 filemenu = wx.Menu()14 15 menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", "Information about this programe.")16 menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", "Terminate the programe.")17 menuOpenfile = filemenu.Append(wx.ID_OPEN, "&Open", "Open a file")18 19 menuBar = wx.MenuBar()20 menuBar.Append(filemenu, "&File")21 self.SetMenuBar(menuBar)22 23 self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)24 self.Bind(wx.EVT_MENU, self.OnExit, menuExit)25 self.Bind(wx.EVT_MENU, self.OnOpen, menuOpenfile)26 27 self.Show(True)28 29 def OnAbout(self, e):30 dlg = wx.MessageDialog(self, "A small text editor", "About Sample Editor", wx.OK)31 dlg.ShowModal()32 dlg.Destroy()33 34 def OnExit(self, e):35 self.Close()36 37 def OnOpen(self, e):38 """ Open a file """39 self.dirname = '';40 dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)41 if dlg.ShowModal() == wx.ID_OK:42 self.filename = dlg.GetFilename()43 self.dirname = dlg.GetDirectory()44 f = open(os.path.join(self.dirname, self.filename), 'r')45 self.control.SetValue(f.read().decode("utf-8")) # 将读到的数据转码46 f.close()47 dlg.Destroy()48 49 app = wx.App(False)50 frame = MainWindow(None, "Sample Editor")51 app.MainLoop()
]
2.5. 扩展
当然,这个程序和正式的编辑器相比,还差太多。但是,添加其他的功能不会比我们刚学习的困难。也许你
可以从下面这些 wxPyton 的演示程序受到启发:
* Drag and Drop ( 拖放 )
* MDI ( 多文档界面 )
* Tab view/multiple files
* Find/Replase dialog
* Print dialog ( )
* Macro-commands in python ( using the eval function )
[批注:
1. 更多演示程序可以从 wxPython 的官网下载
]
3. 使用窗口
Topics:
* Frame
* Windows
* Controls/Widgets
* Sizers
* Validators
这个部分,我们将展示如何使用 wxPython 的窗口和内容,包括构建输入表单和多种小工具。我们将创造
一个小的计算报价的应用程序。如果你有界面开发方面的经验,这将会非常简单,而且你可能想要使用界面构造
器 - Boa-Constructor 的高级功能。
3.1. 概述
3.1.1. 制作课件的元素
在窗口里,你将使用一系列的 wxWindow 的子类来布局窗口。下面是一些你可能用得上的小工具:
* wx.MenuBar: 它将在窗口的顶部创建一个菜单栏
* wx.StatusBar: 它将在窗口的低部显示一些提示信息
* wx.ToolBar: 它将窗口上创建一个工具栏
* wx.Control 系列: 它是一系列用于接口工具(比如:显示数据、处理输入),经常使用的 wx.Control
系列有:wx.Button、wx.StaticText、wx.TextCtrl、wx.ComboBox。
* wx.Panel: 它是一个用于包含多个 wx.Control 的容器,将 wx.Control 放入 wx.Panel 可以用来
形成 TAB 页。
所有的图形化工具对象(wxWindow 对象以及他们的子类)可以包含多个元素。例如:一个 wx.Frame 包含多个
wx.Panel, 反过来, wx.Panel 包含多个 wx.Button、wx.StaticText、wx.TextCtrl 对象。而对于如何对这些工具
进行布局将有多种方法:
1. 你可以手动设置每一个对象相对于父对象的像素值。不同的系统,可能因为字体大小不一等而效果不一样。一般
不推荐使用这种方法;
2. 你可以使用 wx.LayoutConstraints,但它稍微有一点复杂;
3. 你可以使用 Delphi-like LayoutAnchors,那样可以较容易的使用 wx.LayoutCOnstraints;
4. 你可以使用 wxSizer 的子类
这篇文字将会使用 wxSizer 这种方式,因为这是我最熟悉的一种结构。
3.1.2 Sizer
Sizer 是 wx.Sizer 的一个子类,可以用来处理窗口里的各个工具,Sizer 可以:
* 对每个工具类计算一个合适的大小
* 每一个元素的放置都遵循统一的规则
* 当窗口大小变化时,自动调整工具的大小和位置
一些经常使用的布局工具(sizer):
* wx.BoxSizer:水平或垂直布局
* wx.GridSizer:以网格的方式布局
* wx.FlexGridSizer:和 wx.GridSizer 类似,但更灵活
一个 sizer 是对一系列给定的 wx.Window 对象进行设置规格。可以调用 sizer.Add,或者sizer.AddMany。
Sizer 必须要有给定的对象。Sizers 可以嵌套。
1 import wx 2 import os 3 4 class MainWindow(wx.Frame): 5 def __init__(self, parent, title): 6 self.dirname='' 7 8 # A "-1" in the size parameter instructs wxWidgets to use the default size. 9 # In this case, we select 200px width and the default height.10 wx.Frame.__init__(self, parent, title=title, size=(200,-1))11 self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)12 self.CreateStatusBar() # A Statusbar in the bottom of the window13 14 # Setting up the menu.15 filemenu= wx.Menu()16 menuOpen = filemenu.Append(wx.ID_OPEN, "&Open"," Open a file to edit")17 menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")18 menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")19 20 # Creating the menubar.21 menuBar = wx.MenuBar()22 menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar23 self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.24 25 # Events.26 self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen)27 self.Bind(wx.EVT_MENU, self.OnExit, menuExit)28 self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)29 30 self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)31 self.buttons = []32 for i in range(0, 6):33 self.buttons.append(wx.Button(self, -1, "Button &"+str(i)))34 self.sizer2.Add(self.buttons[i], 1, wx.EXPAND)35 36 # Use some sizers to see layout options37 self.sizer = wx.BoxSizer(wx.VERTICAL)38 self.sizer.Add(self.control, 1, wx.EXPAND)39 self.sizer.Add(self.sizer2, 0, wx.EXPAND)40 41 #Layout sizers42 self.SetSizer(self.sizer)43 self.SetAutoLayout(1)44 self.sizer.Fit(self)45 self.Show()46 47 def OnAbout(self,e):48 # Create a message dialog box49 dlg = wx.MessageDialog(self, " A sample editor \n in wxPython", "About Sample Editor", wx.OK)50 dlg.ShowModal() # Shows it51 dlg.Destroy() # finally destroy it when finished.52 53 def OnExit(self,e):54 self.Close(True) # Close the frame.55 56 def OnOpen(self,e):57 """ Open a file"""58 dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)59 if dlg.ShowModal() == wx.ID_OK:60 self.filename = dlg.GetFilename()61 self.dirname = dlg.GetDirectory()62 f = open(os.path.join(self.dirname, self.filename), 'r')63 self.control.SetValue(f.read())64 f.close()65 dlg.Destroy()66 67 app = wx.App(False)68 frame = MainWindow(None, "Sample editor")69 app.MainLoop()
方法 sizer.Add 有三个参数。第一个参数 control 将被添加到 sizer。第二个参数表示占用的比例因子。比如:
你希望使其成为 3:2:1 的比例关系,第二个参数你需要填写该比例。0 表示不会随着增长而变化。第三个参数通常
为 wx.GROW(等同于 wx.EXPAND),表示如果需要,将被重新设置大小,如果使用 wx.SHAPED,小工具的大
小将保持不变,第二个参数将表示各个小工具的间隔。
如果第二个参数为 0,control 对象不会被重置大小,对于第三个参数,wx.ALIGN_CENTER_HORIZONTAL,
wx.ALIGN_CENTER_VERTICAL,wx.ALIGN_CENTER (both) 分别表示水平居中,垂直居中,水平垂直居中。
可以使用这些值来替代 wx.GROW,wx.SHAPED。
你还可以选择使用 wx.ALIGN_LEFT,wx.ALIGN_TOP,wx.ALIGN_RIGHT,wx.ALIGN_BOTTOM。默认
使用 wx.ALIGN_LEFT | wx.ALIGN_TOP。
你将这些控件添加到 sizer 里后,下一步就是告诉 frame 或 window 使能 sizer,使用如下方式使能:
1 window.SetSizer(sizer)2 window.SetAutoLayout(True)3 sizer.Fit(window)
SetSizer() 调用将告知 window(frame) 使用哪个 sizer,SetAutoLayout() 告知窗口如何放置控件和设置
控件大小,sizer.Fit() 告知 sizer 计算所有控件的位置和大小。如果你使用 sizer 这种方式设置控件位置和大小,
这是一种通用的处理过程。
3.1.3 菜单
我想你会能搞一个菜单出来的,如果你看了前面关于菜单栏的实现。
3.1.4 Validators 校验器
当你创建一个对话框或者其他的输入表格,你可以使用 wx.Validator 来进行简单处理加载数据到你的表格
中,校验输入的数据,从表单中导出数据。wx.Validator 也可以用来处理按键事件和其他一些输入框的事件。
要使用校验器,你必须自己创建 wx.Validator 的子类(wxPython 没有实现类似如 wx.TextValidator、
wx.GenericValidator 的类),子类通过输入框对象 myInputField.SetValidator(myValidator) 的方式将事
件和输入框关联起来。
注意:你的 wx.Validator 子类必须实现 wxValidator.Clone() 方法
3.2 一个例子
3.2.1 尝试在面板 panel 中使用标签 label
来,我们以一个例子开始。我们的程序将会有一个 Frame 窗口,Frame 里面有一个 Panel 面板, Panel 里面
再包含一个 Label 标签。
1 import wx 2 class ExampleFrame(wx.Frame): 3 def __init__(self, parent): 4 wx.Frame.__init__(self, parent) 5 panel = wx.Panel(self) 6 self.quote = wx.StaticText(panel, label="Your quote: ", pos = (20, 30)) 7 self.Show() 8 9 app = wx.App(False)10 ExampleFrame(None)11 app.MainLoop()
这个设计应该是非常的清晰,如果你看了 “简易编辑器” 的实现,你应该不会有任何问题。注意,使用 sizer 比使用
pos 更合适,对于控件来说。
1 self.quote = wx.StaticText(panel, label="Your quote: ", pos = (20, 30))
使用 Panel 作为我们的 wx.StaticText(parent, ...) 的 parent 参数。这样,我们的 StaticText 将显示在我们创建
的 Panel 面板上。wx.Point 用来作为 pos 参数,wx.Size 也是一个可选的参数值,但用在这里有些不合适。
3.2.2 添加更多的控件
在 wxPython 的演示程序和帮助文档里,你会发现一些列的控件,我们将展示经常使用的控件:
· wxButton 可点击的按钮
1 clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")2 self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
· wxTextCtrl 用户输入框,当输入框内容被改变时触发 EVT_TEXT,按键被按下时触发 EVT_CHAR
1 textField = wx.TextCtrl(self)2 self.Bind(wx.EVT_TEXT, self.OnChange, textField)3 self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
例如:点击清除按钮时,将文本清空,将触发 EVT_TEXT。
· wxComboBox 下拉列表框,事件 EVT_COMBOBOX
· wxCheckBox 校验框,有 True/False 连个选择
· wxRadioBox 单选框
来,让我们来看看我们现在的程序是怎样的了:
1 import wx 2 3 class ExamplePanel(wx.Panel): 4 def __init__(self, parent): 5 wx.Panel.__init__(self, parent) 6 7 self.quote = wx.StaticText(self, label = "Your quote : ", pos = (20, 30)) 8 9 # A multiline TextCtrl -10 # This is here to show how the events work in this program, don't pay too much attention to it11 self.logger = wx.TextCtrl(self, pos = (500, 20), size= (250, 350), style = wx.TE_MULTILINE | wx.TE_READONLY)12 13 # A button14 self.button = wx.Button(self, label = "Save", pos = (200, 350))15 self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)16 17 # the edit control - one line version18 self.lblname = wx.StaticText(self, label = "Your name : ", pos = (20, 60))19 self.editname = wx.TextCtrl(self, value = "Enter here your name", pos = (200, 60), size = (140, -1))20 self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)21 self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)22 23 # the combobox control24 self.sampleList = ['friends', 'adverising', 'web search', 'Yellow Pages']25 self.lblhear = wx.StaticText(self, label = "How did you hear from us ?", pos = (20, 90))26 self.edithear = wx.ComboBox(self, pos = (200, 90), size = (95, -1), choices = self.sampleList, style = wx.CB_DROPDOWN)27 self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)28 self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)29 30 # Checkbox31 self.insure = wx.CheckBox(self, label = "Do you want Insured Shipment ?", pos = (20, 180))32 self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)33 34 # Radio Boxes35 radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']36 rb = wx.RadioBox(self, label = "What color would you like ?", pos = (20, 210), choices = radioList, majorDimension = 3,37 style = wx.RA_SPECIFY_COLS)38 self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, rb)39 40 def EvtRadioBox(self, event):41 self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())42 43 def EvtComboBox(self, event):44 self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())45 46 def OnClick(self, event):47 self.logger.AppendText(' Click on object with Id %d\n' % event.GetInt())48 49 def EvtText(self, event):50 self.logger.AppendText('EvtText: %s\n' % event.GetString())51 52 def EvtChar(self, event):53 self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())54 55 def EvtCheckBox(self, event):56 self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())57 58 app = wx.App(False)59 frame = wx.Frame(None, size = (800, 600))60 panel = ExamplePanel(frame)61 frame.Show()62 app.MainLoop()
将看到类似这样的窗口:
我们的类变得越来越充实了,里面有很多的控件和控件事件的处理。我们添加了一个 wxTextCtrl 编辑框控件用来显示
控件接收到的事件。
3.2.3 标签页 notebook
wxNoteBook ,TAB 页
1 app = wx.App(False) 2 frame = wx.Frame(None, title="Demo with Notebook") 3 nb = wx.Notebook(frame) 4 5 6 nb.AddPage(ExamplePanel(nb), "Absolute Positioning") 7 nb.AddPage(ExamplePanel(nb), "Page Two") 8 nb.AddPage(ExamplePanel(nb), "Page Three") 9 frame.Show()10 app.MainLoop()
[批注:该程序添加到上一个程序中的]
3.2.4. 改善布局 - 使用 sizers
使用绝对的位置值经常不能达到满意的效果:如果窗口不是合适的大小,那就非常的丑陋。wxPython 有很丰富的
资料可以用来进行布局。
· wx.BoxSizer 是最常用,也是最简单易用的布局神器,但是它会出现很多种布局组织的可能性。它的主要功能是
大致的将控件组成列或行的方式,也会自动重组(比如窗口大小改变的时候)。
· wx.GridSizer wx.FlexGridSizer 也是两个重要的布局神器,采用表格式的布局。
来看看例子:
1 import wx 2 3 class ExamplePanel(wx.Panel): 4 def __init__(self, parent): 5 wx.Panel.__init__(self, parent) 6 7 # create some sizers 8 mainSizer = wx.BoxSizer(wx.VERTICAL) 9 grid = wx.GridBagSizer(hgap = 5, vgap=5)10 hSizer = wx.BoxSizer(wx.HORIZONTAL)11 12 self.quote = wx.StaticText(self, label = "Your quote: ")13 grid.Add(self.quote, pos = (0, 0))14 15 # A multiline TextCtrl16 self.logger = wx.TextCtrl(self, size = (200, 300), style = wx.TE_MULTILINE | wx.TE_READONLY)17 18 # A button19 self.button = wx.Button(self, label = "Save")20 self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)21 22 # the edit control23 self.lblname = wx.StaticText(self, label = "Your name: ")24 grid.Add(self.lblname, pos = (1, 0))25 self.editname = wx.TextCtrl(self, value = "Enter here your name: ", size = (140, -1))26 grid.Add(self.editname, pos = (1,1))27 self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)28 self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)29 30 # the combobox control31 self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']32 self.lblhear = wx.StaticText(self, label = "How did you hear from us ?")33 grid.Add(self.lblhear, pos = (3,0))34 self.edithear = wx.ComboBox(self, size = (95, -1), choices = self.sampleList, style = wx.CB_DROPDOWN)35 grid.Add(self.edithear, pos = (3, 1))36 self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)37 self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)38 39 # add a spacer to the sizer40 grid.Add((10, 40), pos = (2, 0))41 42 # checkbox43 self.insure = wx.CheckBox(self, label = "Do you want Insured Shipment ?")44 grid.Add(self.insure, pos = (4, 0), span = (1, 2), flag = wx.BOTTOM, border = 5)45 self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)46 47 # radio boxes48 radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']49 rb = wx.RadioBox(self, label = "What color would you link ?", pos = (20, 210), choices = radioList, majorDimension = 3,50 style = wx.RA_SPECIFY_COLS)51 grid.Add(rb, pos = (5, 0), span = (1, 2))52 self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, rb)53 54 hSizer.Add(grid, 0, wx.ALL, 5)55 hSizer.Add(self.logger)56 mainSizer.Add(hSizer, 0, wx.ALL, 5)57 mainSizer.Add(self.button, 0, wx.CENTER)58 self.SetSizerAndFit(mainSizer)59 60 61 def EvtRadioBox(self, event):62 self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())63 64 def EvtComboBox(self, event):65 self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())66 67 def OnClick(self, event):68 self.logger.AppendText(' Click on object with Id %d\n' % event.GetInt())69 70 def EvtText(self, event):71 self.logger.AppendText('EvtText: %s\n' % event.GetString())72 73 def EvtChar(self, event):74 self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())75 76 def EvtCheckBox(self, event):77 self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())78 79 80 app = wx.App(False)81 frame = wx.Frame(None, title = "Layout EX")82 panel = ExamplePanel(frame)83 frame.Show()84 app.MainLoop()
这个例子使用一个 GridBagSizer 来放置控件。参数 'pos' 控制如何布局控件,(x, y) 表示位置。例如:(0, 0)表示
左上角位置,(3, 5) 表示第三行,第五列。参数 'span' 运行控件跨越多行
4. 响应用户动作
我想你现在已经知道如何搞定用户动作的事件响应,如果你看了前面的事件处理章节的内容。
参考: Events, Pop-up Menus
5. 画图
· Device Contexts
· Fonts
· Colours
· onPaint() methos
5.1 概述
在这部分,我们将介绍在窗口里面画画。我们也将会展示如何创建右键弹出式菜单(pop-up menus)。
5.2 例子
[官方暂未写该部分的内容]
6. 使用 wxPython
6.1 调试技术
当一个 python 程序遇到一个未处理的异常( BUG),它会异常退出,并且会有 traceback 调用关系用来定位问
题出现的地方。wxPython 也有这样的机制,但是有一点让人苦恼。taceback 信息被定向标准 IO,好的 GUI 应用程
序都会是独立的。下面有几种方法,设置标准 IO:
1 class MyApp (wx.App): 2 #... 3 #... 4 #... 5 myapp = MyApp() # functions normally. Stdio is redirected to its own window 6 myapp = MyApp(0) #does not redirect stdout. Tracebacks will show up at the console. 7 myapp = MyApp(1, 'filespec') #redirects stdout to the file 'filespec' 8 # NOTE: These are named parameters, so you can do this for improved readability: 9 myapp = MyApp(redirect = 1, filename = 'filespec') # will redirect stdout to 'filespec'10 myapp = MyApp(redirect = 0) #stdio will stay at the console...
你可以使用 来调试大多数的布局事件。
6.2 PyCrust 交互式终端
wxPython 发布了一个 交互式终端。你可以使用它来进行交互式测试你的布局程序。
7. 下一步
7.1 Events
事件处理机制是 wxPython 的一个重要特性。所有的 GUI 系统都是依赖于事件处理机制的。有两种处理机制:
· 定义一个方法来处理事件,将一个控件的事件和方法绑定起来,当事件触发是,回调方法,实现事件的处理
· 使用一个预先设定的方法来处理事件。这样的话,如果你想修改某一个控件的事件处理,需要重写该方法
所以, 'self.Bind(wx.EVT_SOMETHING, ACallable)' 表示:
当 SOMETHING 事件告知了窗口 Window(self),该事件可能来自窗口的任意一个子窗口或自己,然后调用
ACallable, self 必须是一个 wxPython 窗口的的类对象(Button、Dialog、Frame)。
另一个 'self.Bind(wx.EVT_SOMETHING, ACallable, srcWin)' 表示:
当 SOMETHING 事件由 'srcWin' 产生时,通过窗口的层级关系递送到了本窗口 Window(self),然后调用
ACallable。
但是有些事件不会被本窗口捕获(代表着第二种方式将不起作用),所以最好使用第一种方式。这是一种基本的方式
除了菜单栏,因为他们没有 Bind 方法。
7.2 Scintilla
Scintilla 是一个基本的组件被 wxStyledTextCtrl, 提供给我们语法高亮的功能。
参见: 。
7.3 Boa-constructor
Boa-constructor 是 wxPython 的一个 RAD IDE。
7.4 多线程
[官方很懒啊,什么也没留下]
7.5 管理、非管理窗口
[官方很懒啊,什么也没留下]
8 非常有用的链接[可供参考的资料]
·
wxPython 官方网站了,官方提供的资源都在这了。
·
wxWidgets 网站,要使用 wxPython 的控件,这里也有。这个的描述文档在 wxPython 里也有。
·
这个可厉害了,和大神们交流啊,去吧!不过,如果是请教问题的话,最好先整理好问题。
·
python 社区
·
python 脚本(包括wxPython) 转换成 Windows 系统的 exe 程序。
9. 这个...
你已经了解了 wxPython 的主要功能特性,而且能够写出 wxPython 的应用程序了。不要犹豫,加入 wxPython
社区吧,订阅邮件列表,来一起讨论。
10. 感觉贡献者
· wxPhthon 社区
· and 很多人 ...
11. 附录
11.1 小小编辑器
参见:
1 import wx 2 import os.path 3 4 5 class MainWindow(wx.Frame): 6 def __init__(self, filename='noname.txt'): 7 super(MainWindow, self).__init__(None, size=(400,200)) 8 self.filename = filename 9 self.dirname = '.' 10 self.CreateInteriorWindowComponents() 11 self.CreateExteriorWindowComponents() 12 13 def CreateInteriorWindowComponents(self): 14 ''' Create "interior" window components. In this case it is just a 15 simple multiline text control. ''' 16 self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE) 17 18 def CreateExteriorWindowComponents(self): 19 ''' Create "exterior" window components, such as menu and status 20 bar. ''' 21 self.CreateMenu() 22 self.CreateStatusBar() 23 self.SetTitle() 24 25 def CreateMenu(self): 26 fileMenu = wx.Menu() 27 for id, label, helpText, handler in \ 28 [(wx.ID_ABOUT, '&About', 'Information about this program', 29 self.OnAbout), 30 (wx.ID_OPEN, '&Open', 'Open a new file', self.OnOpen), 31 (wx.ID_SAVE, '&Save', 'Save the current file', self.OnSave), 32 (wx.ID_SAVEAS, 'Save &As', 'Save the file under a different name', 33 self.OnSaveAs), 34 (None, None, None, None), 35 (wx.ID_EXIT, 'E&xit', 'Terminate the program', self.OnExit)]: 36 if id == None: 37 fileMenu.AppendSeparator() 38 else: 39 item = fileMenu.Append(id, label, helpText) 40 self.Bind(wx.EVT_MENU, handler, item) 41 42 menuBar = wx.MenuBar() 43 menuBar.Append(fileMenu, '&File') # Add the fileMenu to the MenuBar 44 self.SetMenuBar(menuBar) # Add the menuBar to the Frame 45 46 def SetTitle(self): 47 # MainWindow.SetTitle overrides wx.Frame.SetTitle, so we have to 48 # call it using super: 49 super(MainWindow, self).SetTitle('Editor %s'%self.filename) 50 51 52 # Helper methods: 53 54 def defaultFileDialogOptions(self): 55 ''' Return a dictionary with file dialog options that can be 56 used in both the save file dialog as well as in the open 57 file dialog. ''' 58 return dict(message='Choose a file', defaultDir=self.dirname, 59 wildcard='*.*') 60 61 def askUserForFilename(self, **dialogOptions): 62 dialog = wx.FileDialog(self, **dialogOptions) 63 if dialog.ShowModal() == wx.ID_OK: 64 userProvidedFilename = True 65 self.filename = dialog.GetFilename() 66 self.dirname = dialog.GetDirectory() 67 self.SetTitle() # Update the window title with the new filename 68 else: 69 userProvidedFilename = False 70 dialog.Destroy() 71 return userProvidedFilename 72 73 # Event handlers: 74 75 def OnAbout(self, event): 76 dialog = wx.MessageDialog(self, 'A sample editor\n' 77 'in wxPython', 'About Sample Editor', wx.OK) 78 dialog.ShowModal() 79 dialog.Destroy() 80 81 def OnExit(self, event): 82 self.Close() # Close the main window. 83 84 def OnSave(self, event): 85 textfile = open(os.path.join(self.dirname, self.filename), 'w') 86 textfile.write(self.control.GetValue()) 87 textfile.close() 88 89 def OnOpen(self, event): 90 if self.askUserForFilename(style=wx.OPEN, 91 **self.defaultFileDialogOptions()): 92 textfile = open(os.path.join(self.dirname, self.filename), 'r') 93 self.control.SetValue(textfile.read()) 94 textfile.close() 95 96 def OnSaveAs(self, event): 97 if self.askUserForFilename(defaultFile=self.filename, style=wx.SAVE, 98 **self.defaultFileDialogOptions()): 99 self.OnSave(event)100 101 102 app = wx.App()103 frame = MainWindow()104 frame.Show()105 app.MainLoop()
11.2 表单、TAB 页、Sizer
参考:
1 import wx 2 3 4 class Form(wx.Panel): 5 ''' The Form class is a wx.Panel that creates a bunch of controls 6 and handlers for callbacks. Doing the layout of the controls is 7 the responsibility of subclasses (by means of the doLayout() 8 method). ''' 9 10 def __init__(self, *args, **kwargs): 11 super(Form, self).__init__(*args, **kwargs) 12 self.referrers = ['friends', 'advertising', 'websearch', 'yellowpages'] 13 self.colors = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 14 'navy blue', 'black', 'gray'] 15 self.createControls() 16 self.bindEvents() 17 self.doLayout() 18 19 def createControls(self): 20 self.logger = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY) 21 self.saveButton = wx.Button(self, label="Save") 22 self.nameLabel = wx.StaticText(self, label="Your name:") 23 self.nameTextCtrl = wx.TextCtrl(self, value="Enter here your name") 24 self.referrerLabel = wx.StaticText(self, 25 label="How did you hear from us?") 26 self.referrerComboBox = wx.ComboBox(self, choices=self.referrers, 27 style=wx.CB_DROPDOWN) 28 self.insuranceCheckBox = wx.CheckBox(self, 29 label="Do you want Insured Shipment?") 30 self.colorRadioBox = wx.RadioBox(self, 31 label="What color would you like?", 32 choices=self.colors, majorDimension=3, style=wx.RA_SPECIFY_COLS) 33 34 def bindEvents(self): 35 for control, event, handler in \ 36 [(self.saveButton, wx.EVT_BUTTON, self.onSave), 37 (self.nameTextCtrl, wx.EVT_TEXT, self.onNameEntered), 38 (self.nameTextCtrl, wx.EVT_CHAR, self.onNameChanged), 39 (self.referrerComboBox, wx.EVT_COMBOBOX, self.onReferrerEntered), 40 (self.referrerComboBox, wx.EVT_TEXT, self.onReferrerEntered), 41 (self.insuranceCheckBox, wx.EVT_CHECKBOX, self.onInsuranceChanged), 42 (self.colorRadioBox, wx.EVT_RADIOBOX, self.onColorchanged)]: 43 control.Bind(event, handler) 44 45 def doLayout(self): 46 ''' Layout the controls that were created by createControls(). 47 Form.doLayout() will raise a NotImplementedError because it 48 is the responsibility of subclasses to layout the controls. ''' 49 raise NotImplementedError 50 51 # Callback methods: 52 53 def onColorchanged(self, event): 54 self.__log('User wants color: %s'%self.colors[event.GetInt()]) 55 56 def onReferrerEntered(self, event): 57 self.__log('User entered referrer: %s'%event.GetString()) 58 59 def onSave(self,event): 60 self.__log('User clicked on button with id %d'%event.GetId()) 61 62 def onNameEntered(self, event): 63 self.__log('User entered name: %s'%event.GetString()) 64 65 def onNameChanged(self, event): 66 self.__log('User typed character: %d'%event.GetKeyCode()) 67 event.Skip() 68 69 def onInsuranceChanged(self, event): 70 self.__log('User wants insurance: %s'%bool(event.Checked())) 71 72 # Helper method(s): 73 74 def __log(self, message): 75 ''' Private method to append a string to the logger text 76 control. ''' 77 self.logger.AppendText('%s\n'%message) 78 79 80 class FormWithAbsolutePositioning(Form): 81 def doLayout(self): 82 ''' Layout the controls by means of absolute positioning. ''' 83 for control, x, y, width, height in \ 84 [(self.logger, 300, 20, 200, 300), 85 (self.nameLabel, 20, 60, -1, -1), 86 (self.nameTextCtrl, 150, 60, 150, -1), 87 (self.referrerLabel, 20, 90, -1, -1), 88 (self.referrerComboBox, 150, 90, 95, -1), 89 (self.insuranceCheckBox, 20, 180, -1, -1), 90 (self.colorRadioBox, 20, 210, -1, -1), 91 (self.saveButton, 200, 300, -1, -1)]: 92 control.SetDimensions(x=x, y=y, width=width, height=height) 93 94 95 class FormWithSizer(Form): 96 def doLayout(self): 97 ''' Layout the controls by means of sizers. ''' 98 99 # A horizontal BoxSizer will contain the GridSizer (on the left)100 # and the logger text control (on the right):101 boxSizer = wx.BoxSizer(orient=wx.HORIZONTAL)102 # A GridSizer will contain the other controls:103 gridSizer = wx.FlexGridSizer(rows=5, cols=2, vgap=10, hgap=10)104 105 # Prepare some reusable arguments for calling sizer.Add():106 expandOption = dict(flag=wx.EXPAND)107 noOptions = dict()108 emptySpace = ((0, 0), noOptions)109 110 # Add the controls to the sizers:111 for control, options in \112 [(self.nameLabel, noOptions),113 (self.nameTextCtrl, expandOption),114 (self.referrerLabel, noOptions),115 (self.referrerComboBox, expandOption),116 emptySpace,117 (self.insuranceCheckBox, noOptions),118 emptySpace,119 (self.colorRadioBox, noOptions),120 emptySpace,121 (self.saveButton, dict(flag=wx.ALIGN_CENTER))]:122 gridSizer.Add(control, **options)123 124 for control, options in \125 [(gridSizer, dict(border=5, flag=wx.ALL)),126 (self.logger, dict(border=5, flag=wx.ALL|wx.EXPAND,127 proportion=1))]:128 boxSizer.Add(control, **options)129 130 self.SetSizerAndFit(boxSizer)131 132 133 class FrameWithForms(wx.Frame):134 def __init__(self, *args, **kwargs):135 super(FrameWithForms, self).__init__(*args, **kwargs)136 notebook = wx.Notebook(self)137 form1 = FormWithAbsolutePositioning(notebook)138 form2 = FormWithSizer(notebook)139 notebook.AddPage(form1, 'Absolute Positioning')140 notebook.AddPage(form2, 'Sizers')141 # We just set the frame to the right size manually. This is feasible142 # for the frame since the frame contains just one component. If the143 # frame had contained more than one component, we would use sizers144 # of course, as demonstrated in the FormWithSizer class above.145 self.SetClientSize(notebook.GetBestSize())146 147 148 if __name__ == '__main__':149 app = wx.App(0)150 frame = FrameWithForms(None, title='Demo with Notebook')151 frame.Show()152 app.MainLoop()
11.3 画图
参考:
1 # doodle.py 2 3 ''' 4 This module contains the DoodleWindow class which is a window that you 5 can do simple drawings upon. 6 ''' 7 8 import wx 9 10 11 class DoodleWindow(wx.Window): 12 colours = ['Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple', 13 'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', 'Goldenrod', 14 'Cyan', 'Orange', 'Navy', 'Dark Grey', 'Light Grey'] 15 16 thicknesses = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128] 17 18 def __init__(self, parent): 19 super(DoodleWindow, self).__init__(parent, 20 style=wx.NO_FULL_REPAINT_ON_RESIZE) 21 self.initDrawing() 22 self.makeMenu() 23 self.bindEvents() 24 self.initBuffer() 25 26 def initDrawing(self): 27 self.SetBackgroundColour('WHITE') 28 self.currentThickness = self.thicknesses[0] 29 self.currentColour = self.colours[0] 30 self.lines = [] 31 self.previousPosition = (0, 0) 32 33 def bindEvents(self): 34 for event, handler in [ \ 35 (wx.EVT_LEFT_DOWN, self.onLeftDown), # Start drawing 36 (wx.EVT_LEFT_UP, self.onLeftUp), # Stop drawing 37 (wx.EVT_MOTION, self.onMotion), # Draw 38 (wx.EVT_RIGHT_UP, self.onRightUp), # Popup menu 39 (wx.EVT_SIZE, self.onSize), # Prepare for redraw 40 (wx.EVT_IDLE, self.onIdle), # Redraw 41 (wx.EVT_PAINT, self.onPaint), # Refresh 42 (wx.EVT_WINDOW_DESTROY, self.cleanup)]: 43 self.Bind(event, handler) 44 45 def initBuffer(self): 46 ''' Initialize the bitmap used for buffering the display. ''' 47 size = self.GetClientSize() 48 self.buffer = wx.EmptyBitmap(size.width, size.height) 49 dc = wx.BufferedDC(None, self.buffer) 50 dc.SetBackground(wx.Brush(self.GetBackgroundColour())) 51 dc.Clear() 52 self.drawLines(dc, *self.lines) 53 self.reInitBuffer = False 54 55 def makeMenu(self): 56 ''' Make a menu that can be popped up later. ''' 57 self.menu = wx.Menu() 58 self.idToColourMap = self.addCheckableMenuItems(self.menu, 59 self.colours) 60 self.bindMenuEvents(menuHandler=self.onMenuSetColour, 61 updateUIHandler=self.onCheckMenuColours, 62 ids=self.idToColourMap.keys()) 63 self.menu.Break() # Next menu items go in a new column of the menu 64 self.idToThicknessMap = self.addCheckableMenuItems(self.menu, 65 self.thicknesses) 66 self.bindMenuEvents(menuHandler=self.onMenuSetThickness, 67 updateUIHandler=self.onCheckMenuThickness, 68 ids=self.idToThicknessMap.keys()) 69 70 @staticmethod 71 def addCheckableMenuItems(menu, items): 72 ''' Add a checkable menu entry to menu for each item in items. This 73 method returns a dictionary that maps the menuIds to the 74 items. ''' 75 idToItemMapping = {} 76 for item in items: 77 menuId = wx.NewId() 78 idToItemMapping[menuId] = item 79 menu.Append(menuId, str(item), kind=wx.ITEM_CHECK) 80 return idToItemMapping 81 82 def bindMenuEvents(self, menuHandler, updateUIHandler, ids): 83 ''' Bind the menu id's in the list ids to menuHandler and 84 updateUIHandler. ''' 85 sortedIds = sorted(ids) 86 firstId, lastId = sortedIds[0], sortedIds[-1] 87 for event, handler in \ 88 [(wx.EVT_MENU_RANGE, menuHandler), 89 (wx.EVT_UPDATE_UI_RANGE, updateUIHandler)]: 90 self.Bind(event, handler, id=firstId, id2=lastId) 91 92 # Event handlers: 93 94 def onLeftDown(self, event): 95 ''' Called when the left mouse button is pressed. ''' 96 self.currentLine = [] 97 self.previousPosition = event.GetPositionTuple() 98 self.CaptureMouse() 99 100 def onLeftUp(self, event):101 ''' Called when the left mouse button is released. '''102 if self.HasCapture():103 self.lines.append((self.currentColour, self.currentThickness,104 self.currentLine))105 self.currentLine = []106 self.ReleaseMouse()107 108 def onRightUp(self, event):109 ''' Called when the right mouse button is released, will popup110 the menu. '''111 self.PopupMenu(self.menu)112 113 def onMotion(self, event):114 ''' Called when the mouse is in motion. If the left button is115 dragging then draw a line from the last event position to the116 current one. Save the coordinants for redraws. '''117 if event.Dragging() and event.LeftIsDown():118 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)119 currentPosition = event.GetPositionTuple()120 lineSegment = self.previousPosition + currentPosition121 self.drawLines(dc, (self.currentColour, self.currentThickness,122 [lineSegment]))123 self.currentLine.append(lineSegment)124 self.previousPosition = currentPosition125 126 def onSize(self, event):127 ''' Called when the window is resized. We set a flag so the idle128 handler will resize the buffer. '''129 self.reInitBuffer = True130 131 def onIdle(self, event):132 ''' If the size was changed then resize the bitmap used for double133 buffering to match the window size. We do it in Idle time so134 there is only one refresh after resizing is done, not lots while135 it is happening. '''136 if self.reInitBuffer:137 self.initBuffer()138 self.Refresh(False)139 140 def onPaint(self, event):141 ''' Called when the window is exposed. '''142 # Create a buffered paint DC. It will create the real143 # wx.PaintDC and then blit the bitmap to it when dc is144 # deleted. Since we don't need to draw anything else145 # here that's all there is to it.146 dc = wx.BufferedPaintDC(self, self.buffer)147 148 def cleanup(self, event):149 if hasattr(self, "menu"):150 self.menu.Destroy()151 del self.menu152 153 # These two event handlers are called before the menu is displayed154 # to determine which items should be checked.155 def onCheckMenuColours(self, event):156 colour = self.idToColourMap[event.GetId()]157 event.Check(colour == self.currentColour)158 159 def onCheckMenuThickness(self, event):160 thickness = self.idToThicknessMap[event.GetId()]161 event.Check(thickness == self.currentThickness)162 163 # Event handlers for the popup menu, uses the event ID to determine164 # the colour or the thickness to set.165 def onMenuSetColour(self, event):166 self.currentColour = self.idToColourMap[event.GetId()]167 168 def onMenuSetThickness(self, event):169 self.currentThickness = self.idToThicknessMap[event.GetId()]170 171 # Other methods172 @staticmethod173 def drawLines(dc, *lines):174 ''' drawLines takes a device context (dc) and a list of lines175 as arguments. Each line is a three-tuple: (colour, thickness,176 linesegments). linesegments is a list of coordinates: (x1, y1,177 x2, y2). '''178 dc.BeginDrawing()179 for colour, thickness, lineSegments in lines:180 pen = wx.Pen(wx.NamedColour(colour), thickness, wx.SOLID)181 dc.SetPen(pen)182 for lineSegment in lineSegments:183 dc.DrawLine(*lineSegment)184 dc.EndDrawing()185 186 187 class DoodleFrame(wx.Frame):188 def __init__(self, parent=None):189 super(DoodleFrame, self).__init__(parent, title="Doodle Frame",190 size=(800,600),191 style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)192 doodle = DoodleWindow(self)193 194 195 if __name__ == '__main__':196 app = wx.App()197 frame = DoodleFrame()198 frame.Show()199 app.MainLoop()
11.4 工程管理
参见:
该部分展示了如何使用 wxPython Style Guide。
[批注:在 77 行的地方,笔者的测试环境提示未定义 wx.NO_3D, 故下面的代码笔记将 'wx.NO_3D|' 去除了 ]
1 #!/usr/bin/env python 2 3 """ 4 This sample comes from an IBM developerWorks article at 5 http://www-106.ibm.com/developerworks/library/l-wxpy/index.html 6 7 This small program was adapted to demonstrate the current guide lines 8 on http://wiki.wxpython.org/index.cgi/wxPython_20Style_20Guide. 9 Changes are noted in readme.txt. 10 """ 11 12 import sys, os 13 import wx 14 15 16 # Process the command line. Not much to do; 17 # just get the name of the project file if it's given. Simple. 18 projfile = 'Unnamed' 19 if len(sys.argv) > 1: 20 projfile = sys.argv[1] 21 22 23 def MsgDlg(window, string, caption='wxProject', style=wx.YES_NO|wx.CANCEL): 24 """Common MessageDialog.""" 25 dlg = wx.MessageDialog(window, string, caption, style) 26 result = dlg.ShowModal() 27 dlg.Destroy() 28 return result 29 30 31 class main_window(wx.Frame): 32 """wxProject MainFrame.""" 33 def __init__(self, parent, title): 34 """Create the wxProject MainFrame.""" 35 wx.Frame.__init__(self, parent, title=title, size=(500, 500), 36 style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) 37 38 39 # Set up menu bar for the program. 40 self.mainmenu = wx.MenuBar() # Create menu bar. 41 42 # Make the 'Project' menu. 43 menu = wx.Menu() 44 45 item = menu.Append(wx.ID_OPEN, '&Open', 'Open project') # Append a new menu 46 self.Bind(wx.EVT_MENU, self.OnProjectOpen, item) # Create and assign a menu event. 47 48 item = menu.Append(wx.ID_NEW, '&New', 'New project') 49 self.Bind(wx.EVT_MENU, self.OnProjectNew, item) 50 51 item = menu.Append(wx.ID_EXIT, 'E&xit', 'Exit program') 52 self.Bind(wx.EVT_MENU, self.OnProjectExit, item) 53 54 self.mainmenu.Append(menu, '&Project') # Add the project menu to the menu bar. 55 56 # Make the 'File' menu. 57 menu = wx.Menu() 58 59 item = menu.Append(wx.ID_ANY, '&Add', 'Add file to project') 60 self.Bind(wx.EVT_MENU, self.OnFileAdd, item) 61 62 item = menu.Append(wx.ID_ANY, '&Remove', 'Remove file from project') 63 self.Bind(wx.EVT_MENU, self.OnFileRemove, item) 64 65 item = menu.Append(wx.ID_ANY, '&Open', 'Open file for editing') 66 self.Bind(wx.EVT_MENU, self.OnFileOpen, item) 67 68 item = menu.Append(wx.ID_ANY, '&Save', 'Save file') 69 self.Bind(wx.EVT_MENU, self.OnFileSave, item) 70 71 self.mainmenu.Append(menu, '&File') # Add the file menu to the menu bar. 72 73 # Attach the menu bar to the window. 74 self.SetMenuBar(self.mainmenu) 75 76 # Create the splitter window. 77 splitter = wx.SplitterWindow(self, style=wx.SP_3D) ### Atenttion, I remove 'wx.NO_3D |' 78 splitter.SetMinimumPaneSize(1) 79 80 # Create the tree on the left. 81 self.tree = wx.TreeCtrl(splitter, style=wx.TR_DEFAULT_STYLE) 82 self.tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnTreeLabelEdit) 83 self.tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnTreeLabelEditEnd) 84 self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnTreeItemActivated) 85 86 # Create the editor on the right. 87 self.editor = wx.TextCtrl(splitter, style=wx.TE_MULTILINE) 88 self.editor.Enable(0) 89 90 # Install the tree and the editor. 91 splitter.SplitVertically(self.tree, self.editor) 92 splitter.SetSashPosition(180, True) 93 94 # Some global state variables. 95 self.projectdirty = False 96 self.root = None 97 self.close = False 98 99 self.Bind(wx.EVT_CLOSE, self.OnProjectExit)100 101 self.Show(True)102 103 # ----------------------------------------------------------------------------------------104 # Some nice little handlers.105 # ----------------------------------------------------------------------------------------106 107 def project_open(self, project_file):108 """Open and process a wxProject file."""109 try:110 input = open(project_file, 'r')111 self.tree.DeleteAllItems()112 113 self.project_file = project_file114 name = input.readline().replace ('\n', '')115 self.SetTitle(name)116 117 # create the file elements in the tree control.118 self.root = self.tree.AddRoot(name)119 self.activeitem = self.root120 for line in input:121 self.tree.AppendItem(self.root, line.replace ('\n', ''))122 input.close()123 self.tree.Expand(self.root)124 125 self.editor.Clear()126 self.editor.Enable(False)127 128 self.projectdirty = False129 except IOError:130 pass131 132 def project_save(self):133 """Save a wxProject file."""134 try:135 output = open(self.project_file, 'w+')136 output.write(self.tree.GetItemText(self.root) + '\n')137 count = self.tree.GetChildrenCount(self.root) # collect all file (tree) items.138 iter = 0139 child = ''140 for i in range(count):141 if i == 0:142 child, cookie = self.tree.GetFirstChild(self.root)143 else:144 child, cookie = self.tree.GetNextChild(self.root, cookie)145 output.write(self.tree.GetItemText(child) + '\n')146 output.close()147 self.projectdirty = False148 except IOError:149 MsgDlg(self, 'There was an error saving the new project file.', 'Error!', wx.OK)150 151 def CheckProjectDirty(self):152 """Were the current project changed? If so, save it before."""153 open_it = True154 if self.projectdirty:155 # save the current project file first.156 result = MsgDlg(self, 'The project has been changed. Save?')157 if result == wx.ID_YES:158 self.project_save()159 if result == wx.ID_CANCEL:160 open_it = False161 return open_it162 163 def CheckTreeRootItem(self):164 """Is there any root item?"""165 if not self.root:166 MsgDlg(self, 'Please create or open a project before.', 'Error!', wx.OK)167 return False168 return True169 170 # ----------------------------------------------------------------------------------------171 # Event handlers from here on out.172 # ----------------------------------------------------------------------------------------173 174 def OnProjectOpen(self, event):175 """Open a wxProject file."""176 open_it = self.CheckProjectDirty()177 if open_it:178 dlg = wx.FileDialog(self, 'Choose a project to open', '.', '', '*.wxp', wx.OPEN)179 if dlg.ShowModal() == wx.ID_OK:180 self.project_open(dlg.GetPath())181 dlg.Destroy()182 183 def OnProjectNew(self, event):184 """Create a new wxProject."""185 open_it = self.CheckProjectDirty()186 if open_it:187 dlg = wx.TextEntryDialog(self, 'Name for new project:', 'New Project',188 'New project', wx.OK|wx.CANCEL)189 if dlg.ShowModal() == wx.ID_OK:190 newproj = dlg.GetValue()191 dlg.Destroy()192 dlg = wx.FileDialog(self, 'Place to store new project.', '.', '', '*.wxp', wx.SAVE)193 if dlg.ShowModal() == wx.ID_OK:194 try:195 # save the project file.196 proj = open(dlg.GetPath(), 'w')197 proj.write(newproj + '\n')198 proj.close()199 self.project_open(dlg.GetPath())200 except IOError:201 MsgDlg(self, 'There was an error saving the new project file.', 'Error!', wx.OK)202 dlg.Destroy()203 204 def SaveCurrentFile(self):205 """Check and save current file."""206 go_ahead = True207 if self.root:208 if self.activeitem != self.root:209 if self.editor.IsModified(): # Save modified file before210 result = MsgDlg(self, 'The edited file has changed. Save it?')211 if result == wx.ID_YES:212 self.editor.SaveFile(self.tree.GetItemText(self.activeitem))213 if result == wx.ID_CANCEL:214 go_ahead = False215 if go_ahead:216 self.tree.SetItemBold(self.activeitem, 0)217 return go_ahead218 219 def OnProjectExit(self, event):220 """Quit the program."""221 if not self.close:222 self.close = True223 if not self.SaveCurrentFile():224 self.close = False225 if self.projectdirty and self.close:226 result = MsgDlg(self, 'The project has been changed. Save?')227 if result == wx.ID_YES:228 self.project_save()229 if result == wx.ID_CANCEL:230 self.close = False231 if self.close:232 self.Close()233 else:234 event.Skip()235 236 def OnFileAdd(self, event):237 """Adds a file to the current project."""238 if not self.CheckTreeRootItem():239 return240 241 dlg = wx.FileDialog(self, 'Choose a file to add.', '.', '', '*.*', wx.OPEN)242 if dlg.ShowModal() == wx.ID_OK:243 path = os.path.split(dlg.GetPath())244 self.tree.AppendItem(self.root, path[1])245 self.tree.Expand(self.root)246 self.project_save()247 248 def OnFileRemove(self, event):249 """Removes a file to the current project."""250 if not self.CheckTreeRootItem():251 return252 item = self.tree.GetSelection()253 if item != self.root:254 self.tree.Delete(item)255 self.project_save()256 257 def OnFileOpen(self, event):258 """Opens current selected file."""259 if self.root:260 item = self.tree.GetSelection()261 if item != self.root:262 self.OnTreeItemActivated(None, item)263 return264 MsgDlg(self, 'There is no file to load.', 'Error!', wx.OK)265 266 def OnFileSave(self, event):267 """Saves current selected file."""268 if self.root:269 if self.activeitem != self.root:270 self.editor.SaveFile(self.tree.GetItemText(self.activeitem))271 return272 MsgDlg(self, 'There is no file to save.', 'Error!', wx.OK)273 274 275 def OnTreeLabelEdit(self, event):276 """Edit tree label (only root label can be edited)."""277 item = event.GetItem()278 if item != self.root:279 event.Veto()280 281 def OnTreeLabelEditEnd(self, event):282 """End editing the tree label."""283 self.projectdirty = True284 285 286 def OnTreeItemActivated(self, event, item=None):287 """Tree item was activated: try to open this file."""288 go_ahead = self.SaveCurrentFile()289 290 if go_ahead:291 if event:292 item = event.GetItem()293 self.activeitem = item294 if item != self.root:295 # load the current selected file296 self.tree.SetItemBold(item, 1)297 self.editor.Enable(1)298 self.editor.LoadFile(self.tree.GetItemText(item))299 self.editor.SetInsertionPoint(0)300 self.editor.SetFocus()301 else:302 self.editor.Clear()303 self.editor.Enable(0)304 305 306 class App(wx.App):307 """wxProject Application."""308 def OnInit(self):309 """Create the wxProject Application."""310 frame = main_window(None, 'wxProject - ' + projfile)311 if projfile != 'Unnamed':312 frame.project_open(projfile)313 return True314 315 316 if __name__ == '__main__':317 app = App(0)318 app.MainLoop()
11.5 FAQ
参见:
12. 备注
12.1 '国际惯例'
欢迎评论和发表意见
12.2 不同系统的路径问题
代码:
1 import os2 f=open(os.path.join(self.dirname,self.filename),'r')
在 Windows 系统上可能存在问题,如果你的目录是在驱动盘的根目录下。在这种情况下,普通的对话框会返回
self.dirname = 'c:' (但你期望的是 'c:\',除非你使用 MS many years)。不幸的是,os.path.join 不会自动添
加期望的符号导致打开一个文件为 'c:file.py' 而出错。一个更好的解决方法是:
1 f=open(os.path.normpath(os.path.join(self.dirname+os.sep,self.filename)),'r')
我想大多数情况下是正常的,除非 self.dirname 是 'c:\',因为 normpath 不会去除第一个参数的分隔符。或
着一个更优雅的解决方法,使用 os.path.isbas() 。
12.3 使用 sizer 的步骤
上面我们使用的是这种方式:
1 window.SetSizer(sizer)2 window.SetAutoLayout(true)3 sizer.Fit(window)
我们也可以使用下面的方式:
1 window.SetSizerAndFit(sizer)
12.4 怎样使能 tab 的切换功能
对于一个没有对话框的窗口来讲,使能 TAB 按键功能真的比较难:仅有的线索是整个窗口使能 style wxTAB_TRAVERSAL,
但是却不是很清晰。我最后得到的答案是:
1)需要使能 TAB 功能的控件或窗口,开启 style wxTAB_TRAVERSAL,例如:
1 class ContactsPanel(wx.Panel):2 def __init__(self, parent,id):3 wx.Panel.__init__(self, parent, id, wx.DefaultPosition,wx.DefaultSize,4 wx.RAISED_BORDER|wx.TAB_TRAVERSAL)
2)TAB 的顺序是根据你添加控件的顺序来的(来源于:Massimiliano Sartor)
3)TAB 的顺序也有可能和创建的顺序有关。我猜测是由于控件的 ID 号。使用 sizer、panel 没有太多帮助
(来源于:Keith Veleba)
4)有一种惯用方法来设置 TAB 的顺序:
1 order = (control1, control2, control3, ...)2 for i in xrange(len(order) - 1):3 order[i+1].MoveAfterInTabOrder(order[i])
list 列表里是真正的控件的 TAB 顺序,对于添加新控件,这样能够很容易的改变顺序(来源于: Don Dwiggins)