wxPyWiki的目的是为了提供软件文档、示例、使用方法等。是为了帮助人们学习、理解和使用wxPython。
注解:想了解更多关于wxPython的内容,可以点击这里。
这里有多种类型的可用信息。当前的有:
学习wxPython:
o
安装——如何安装wxPython。(不要忘记去安装文档和示例!)
o
如何学习wxPython——知识概要和学习策略
o
入门指南——编写Hello World和基本概念
o
Sizer教程——使用不同Sizer的相关教程
o
使用Sizer——使用Sizer的常见误区和陷阱
如何安装wxPython
安装Python
稳定版的wxPython要求安装Python 2.7版本。Python下载可以点击这里
Windows
在Windows环境下进行安装是特别简单的:运行从wxPython得到的安装程序,根据提示进行即可。
Mac OS X
从wxPython可以获取可用的安装包,同时适用于PPC和Intel的Mac电脑
如果你想自定义安装,可以参照这里的指令
如果你得到了“damaged and can't be opened”,你需要更改安全配置设置,将Allow application downloaded from更改为Anywhere
GNU/Linux - Redhat
你可以找到适用于Redhat的RPM(通过Mandrake可以正常的工作),下载地址是wxPython
GNU/Linux - Debian
可以使用apt-get去安装wxPython,调用apt-get install python-wxgtk2.8 或 apt-get install python-wxgtk2.6,这取决于你想得到的版本。
你可能需要使用管理员权限去调用它。wxPython示例都在wx-examples包中。
我们建议去分别安装这些demo,具体描述在Using wxPython Demo Code。
尝试调用:
apt-get install python-wxgtk2.8
请注意,你可能使用本方法已经安装了wx较老的版本,参照InstallingOnUbuntuOrDebian去学习如何使用apt-get得到最新的版本。
GNU/Linux - Gentoo
通过portage去调用emerge wxPython可以安装wxPython(注意大写的P)。在04年11月28日前,正常的使用是emerge wxpython(不是大写的P)。
GNU/Linux - Building from the source
你可能也想利用源码去构建wxPython。那么你需要做以下三个步骤:
Installing wxGTK from source
wxGTK是GTK版的wxWidgets。GTK(Gimp ToolKit)是Gnome所使用的图形类库,所以它可能已经被安装在你的Linux机器中。
你需要去做的就是,从the wxGTK ftp server或wxWidgets website中去下载wxGTK。
通过命令去解压wxGTK:
tar -xvzf wxGTK-2.2.5.tar.gz
进入解压后的文件目录:
cd wxGTK-2.2.5
运行配置脚本:
./configure --with-gtk
如果GTK或GTK的依赖文件没有安装,会提示出错(在Mandrake版中,你应该安装的rpm是gtk+-devel-1.2.8-6mdk.i586.rpm)
运行命令:
make
如果没有安装yacc或lex会提示出错(在Mandrake版中,应该安装 byacc-1.9-7mdk.i586.rpm和flex-2.5.4a-13mdk.i586.rpm)
你现在应该有了编译好的wxGTK。我们现在去安装它,并把它链接到系统中。
进入超级管理员模式:
su
在这里,你需要提供权限密码。
安装wxGTK:
make install
链接库:
ldconfig
退出超级管理员模式:
exit
正常来说,wxGTK已经安装好了,但是可能wxPython可能还会有问题:
当wxPython去寻找库的时候,库并没有安装。(在Mandrake7.2版中,你想将库安装在/usr/lib,但是它会被自动安装在/usr/local/lib中)。
解决方法是,创建一个这个库的符号链接。
进入你想安装的目录中:
cd /usr/lib
创建一个符号链接这个库:
ln -s /usr/local/lib/libwx_gtk.so
利用源码安装wxPython
下载最新发布版的wxPython源码:
wxPython website
解压缩:
cd wxPython-2.2.5
进入目录:
cd wxPython-2.2.5
编辑setup.py,选择你想要安装的部分。建议不要安装OGL和GL_CANVAS。选择如下:
BUILD_GLCANVAS = 0 # If true, build the contrib/glcanvas extension module
BUILD_OGL = 0 # If true, build the contrib/ogl extension module
BUILD_STC = 1 # If true, build the contrib/stc extension module
CORE_ONLY = 0 # if true, don't build any of the above
GL_ONLY = 0 # Only used when making the -gl RPM. See the "b" script
# for the ugly details
USE_SWIG = 0 # Should we actually execute SWIG, or just use the
# files already in the distribution?
IN_CVS_TREE = 0 # Set to true if building in a full wxWidgets CVS
# tree, otherwise will assume all needed files are
# available in the wxPython source distribution
构建python模块:
python setup.py build
进入超级管理员模式:
su
在这里,你需要提供权限密码。
安装模块:
python setup.py install
退出超级管理员模式:
exit
检查模式是否可以工作:
[lucas@b007 wxPython-2.2.5]$ python
Python 1.5.2 (#1, Sep 30 2000, 18:08:36) [GCC 2.95.3 19991030 (prerelease)] on linux-i386
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import wx
>>>
wxPython已经安装好了!
使用PIP安装wxPython-Phoenix
确保你已经最低安装有pip-6.0.8和setuptools-12.0.5
按照README.rst的要求进行安装:
https://github.com/wxWidgets/Phoenix/blob/master/README.rst
快速浏览snapshots的README.txt:
https://wxpython.org/Phoenix/snapshot-builds/README.txt
安装wxPython-Phoenix(Linux):
sudo pip install --upgrade --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython_Phoenix
安装wxPython-Phoenix(Windows,在需要安装的目录下使用命令):
C:\python27\scripts\pip.exe install --upgrade --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython_Phoenix
确认安装:
python2
>>> import wx
>>> wx.VERSION_STRING
'3.0.3.dev1784+18750f4'
>>> wx.version()
'3.0.3.dev1784+18750f4 msw (phoenix)'
如何去学习wxPython
学习Python
如何你没有使用过Python,或者你从来没有进行过编程,那么首先去学习Python是最有意义的,不使用任何的GUI。
这个过程可能让人沮丧,但是很有必要。Python是一种非常强大的编程语言,不管是使用或不使用GUI。
最基本的,你应该理解函数、模块和类。没有这些,学习Python是非常困难的事情。在Python官网对于初学者有非常棒的指导,让你一步步的去学习它。http://www.python.org/about/gettingstarted/
选择一个好的编译器
拥有自动编译、代码提示和一个可交互的窗口,能够让编程更加的方便,避免出错。
点击http://wiki.python.org/moin/PythonEditors查看编译器列表。
安装wxPython
如果你还没有安装wxPython,请看如何安装wxPython。
阅读wxPython教程
Wiki教程:最重要是就是入门指南。然后你可以学习Obstacle Course和Fully Functional Demo。
其他教程:最棒的资源就是Zetcode wxPython tutorial。它拥有了大量的内容,包括:入门指南、菜单/工具栏、sizers、事件、drawing API、widgets、创建自定义的widgets和示例应用”skeleton“
视频教程:ShowMeDo.com中的wxPython tutorial videos特别适合初学者。YouTube中也有大量的视频。
阅读wxPython风格指南
它会告诉你,如何让你的wxPython代码更漂亮,更具有现在化的风格。由于多年来API的变化,和大量的经验,使得wxPython多年来变得更加Python化。
不幸的是,你会发现有很多示例并没有被更新成这种风格。当你刚开始的时候就学习wxPython Style Guide,能够让你使用好看,现代化的风格,这会是一种好的习惯。
阅读示例文件
wxPython自带有很全面的大量示例。进入the download page并找到wxPython-demo-x.x.x文件。示例会展示wxPython基本所有的工作。你可以查看不同功能是如何工作的,并学习源码。
注意:如果示例不能工作,请确保你已经安装了最新版的wxPython。有时候示例中使用的功能并不适用于老的版本。
检查你的wxPython版本,可以运行:
import wx
print wx.version()
wxPython中一种很普遍的工作方式,就是找到一个类似于你想要的功能的示例,然后复制它,并修改添加出你需要的功能。
另一个示例的源码是wxPython Cookbook.
阅读wxWidgets文档
一个非常重要的资源就是wxWidgets文档.
它主要是用C++编写的,但是它有很多针对于wxPython的注释,大多数时候,你可以从c++语法转换为wxPython语法。你并不需要知道C++对于它的作用在哪里。
很多wxPython程序员根本不懂c++,但是他们还是认为wxWidgets文档是很有帮助的。
如果你要阅读这个wxWidgets文档,C++ Guide for Pythoneers对你会有很大的帮助。或者你也可以在mailing list上寻求帮助。
使用wxPython引用(试验)
你可以去尝试automatically generated API reference。它是一个试验性的功能。它不像wxWidgets文档那样完善,但是它是用wxPython语法编写的。
学习其他人的代码
查尔斯·西蒙尼,微软的传奇性程序员,生于匈牙利,只有很少的书本能够让他去学习计算机科学。他就像音乐家使用乐谱学习一样,通过源码的编译和打印输出去学习编程。
在用wxPython编写的代码中,有很多经典之外,这是示例代码所没有的。例如,FrogEdit和它的底层wx.lib.editor模块。
我猜测一个editor会是一个wx.TextCtrl。我非常惊讶的发现,这个text使用DC(device control)绘制到屏幕上,我学习到了一些不错的技巧。
我也”读“了Transana和Task Coach这两个使用Python和wxPython编写的复杂的应用,去学习大型的应用是如何组成的。
在这个wiki中的小例子,和真实项目中的代码有非常大的不同,所以分析大型应用的过程就是学习wxPython的一部分。
提出问题!
wxPython用户邮件列表中的成员,都是友好的,乐于助人的。你可以在这里订阅它:https://wxpython.org/maillist.php
wxPyhon入门指南
第一个应用:”Hello World“
作为一种传统,我们首先编写一个”Hello World“的小应用。代码如下:
#!/usr/bin/env python
import wx
app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window.
frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window.
frame.Show(True) # Show the frame.
app.MainLoop()
说明:
app = wx.App(False) 每一个wxPython App都是wx.App的一个实例。对于大多数简单的应用你都可以直接使用wx.App。当你要开发更为复杂的应用时,你就应该去继承wx.App类了。”False“参数代表”不要将标准输出和标准错误重定向到一个窗口“
wx.Frame(None,wx.ID_ANY,"Hello") 一个wx.Frame是一个最高等级的窗口。它的语法是x.Frame(Parent, Id, Title).大多数的时候,它的构造函数是这样的(a parent object, followed by an Id)。在这个例子中,我们使用None表示了”没有parent“,wx.ID_ANY表示让wxWidgets为我们选择一个ID
frame.Show(True) 我们修改一个frame的可见性,并展示出来。
app.MainLoop() 最后,我们开始一个应用的MainLoop,它是用来处理事件的
注意:你可能总是使用wx.ID_ANY或由wxWidgets提供的standard ID (v2.8)。你也可以去使用你自己的ID,但是这里没有必要去这样做。
运行程序,你可以看到如下的一个窗口:
窗口还是框架?
当人们在讨论GUI的时候,他们通常都是在说窗口,菜单和图标。自然地,你会认为wx.Window应该表示一个在屏幕上的窗口。
事实并非如此。一个wx.Window是一个所有可视化元素推导出来的基类(如按钮,菜单等),我们正常来说的作为一个程序窗口的其实是一个wx.Frame。
这是最让初学者感到困惑的地方。
构建一个简单的文本编辑器
在这个教程中,我们将要去构建一个简单的文本编辑器。在这个过程中,我们将要探索几个部件,并且学习相关的功能,如事件和回调。
第一步
第一步是创建一个带有可编辑文本框的框架。一个文本框可以用wx.TextCtrl部件来创建。默认情况下,一个文本框是单行的,wx.TE_MULTILINE参数可以允许我们输入多行的文本。
#!/usr/bin/env python
import wx
class MyFrame(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 = MyFrame(None, 'Small editor')
app.MainLoop()
在这个例子中,我们取得wx.Frame,并重写了它的__init__函数。这里我们声明了一个新的wx.TextCtrl,是一个简单的文本编辑控件。注意,因为我们在MyFrame的__init__函数中运行了self.Show(),所以我们不需要再去调用frame.Show()了。
添加一个菜单栏
每一个应用都应该有一个菜单栏和一个状态栏。让我们把它们添加到我们应用中:
import wx
class MainWindow(wx.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() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
self.Show(True)
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.MainLoop()
提示:注意wx.ID_ABOUT和wx.ID_EXIT这两个ID。它们是由wxWidgets提供的标准ID(点击这里查看完整的列表)。使用标准ID是一个非常良好的习惯。标准ID能够让wxWidgets知道如何在每个平台中去显示这个部件,能够让部件看起来更加的自然。
事件处理
在wxPython中,对于事件的反馈叫事件处理。一个事件表示在你的应用中发生了某些事情(如点击按钮、文本输入、鼠标点击等)。GUI程序很大一部分都是由事件处理组成的。使用bind()方法将事件绑定到对应的对象上:
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这个函数会被调用。wx.EVT_MENU是“选择菜单项”事件。wxWidgets能够理解更多的其他事件(点击查看完整列表)。这个self.OnAbout函数拥有如下声明:
def OnAbout(self, event):
...
这里event是wx.Event子类的一个实例。例如,一个按钮点击事件——wx.EVT_BUTTON——是wx.Event的一个子类。
当事件发生时,这个函数会被调用。默认情况下,这个函数会处理这个事件,在回调完成后这个事件会终止。但是,你也可以使用event.Skip()去“跳过”一个事件。这会使得这个事件直接通过这个事件处理。例如:
def OnButtonClick(self, event):
if (some_condition):
do_something()
else:
event.Skip()
def OnEvent(self, event):
...
当一个按钮点击事件发生时,OnButtonClick函数被调用。如果some_condition是true,我们会调用do_something(),否则我们会让这个事件直接通过事件处理。现在让我们来看看我们的应用:
import os
import wx
class MainWindow(wx.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() # A StatusBar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
# wx.ID_ABOUT and wx.ID_EXIT are standard ids provided by wxWidgets.
menuAbout = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
# Set events.
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Show(True)
def OnAbout(self,e):
# A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK)
dlg.ShowModal() # Show it
dlg.Destroy() # finally destroy it when finished.
def OnExit(self,e):
self.Close(True) # Close the frame.
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.MainLoop()
注意:可代替的
wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor", wx.OK)
我们可以省略掉这个id。在这种情况下,wxWidget会自动生成一个ID(就像你指定了wx.ID_ANY一样):
wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor")
对话框
当然,如果不能够去保存或打开一个文档,一个编辑器是没有用处的。这个时候普通的对话框就起作用了。普通对话框是由平台底层去提供的,这让你的应用看起来更像是原生应用一样。这里在MainWindow中实现了OnOpen函数:
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”意味着用户不能进行任何操作,除非他点击了确定或取消。
ShowModal的返回值是被点击按钮的ID。如果用户点击了确定,我们就可以读取这个文件。
你现在应该可以添加相应的入口到菜单中,并把它连接到OnOpen函数中。如果你有任何问题,向下滚动到附件中的完整源码。
可能的扩展
当然,这个程序远不能成为一个像样的编辑器。但是添加一些其他的功能部件并不比之前的那些困难。你可能会从wxPython附带包中的那些示例中获得灵感:
Drag and Drop.
MDI
Tab view/multiple files
Find/Replace dialog
Print dialog (Printing)
Macro-commands in python ( using the eval function)
etc ...
在Windows环境下工作
主题:
Frames(框架)
Windows(窗口)
Controls/Widgets(控件/部件)
Sizers(筛分器)
Validators(验证器)
在这一部分,我们将要展示如何使用wxPython去处理窗口和窗口里的内容,包括创建输入表单,使用大量的部件/控件。我们将要去创建一个计算价格的小应用。
如果你是一个有经验的GUI开发者,这对你来说是非常简单的,你可以直接去看高级部分的Boa-Constructor部分。
入门指南
制定视觉元素
在一个框架里,你应该会使用若干的wxWindow子类去充实这个框架的内容。这里有一些普通的元素,可能你会想要放进你的框架中:
wx.MenuBar,在你的框架顶部放置一个菜单栏。
wx.StatusBar,放在你的框架底部区域,用来显示一些状态信息等。
wx.ToolsBar,在你的框架中放置一个工具栏。
wx.Control的子类,这些是呈现用户界面部件的对象(显示数据或处理用户输入的视觉元素)。普通的wx.Control例子包括wx.Button, wx.StaticText, wx.TextCtrl和wx.ComboBox.
wx.Panel,是一个可以持有你不同wx.Control对象的容器。把你的wx.Control放入到wx.Panel中,用户可以从一个UI小部件切换到下一个。
所有的视觉元素(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子类中的一个。
这篇文档会集中介绍使用wxSizers,因为这个是我最熟悉的。
Sizers
一个sizer(它是wxSizer子类中的一个)可以被用来去解决在一个窗口或框架中视觉元素的排列。Sizers能够做:
计算出每个视觉元素合适的大小
根据确定准则去定位所有的元素
当框架调整大小后,动态的重新调整或重新定位每个元素
经常使用的一些sizers包括:
wx.BoxSizer,按照水平或垂直的线性方式去排列视觉元素。
wx.GridSizer,将所有视觉元素安排在格状布局中。
wx.FlexGridSizer,类似于wx.GridSizer,但它能够更灵活的去排列视觉元素。
通过调用sizer.Add(window, options...)或sizer.AddMany(...),sizer将若干wx.Window对象进行排列。sizer只能对那些被赋予的元素进行工作。
Sizers可以进行嵌套。你可以将一个sizer添加到另一个sizer中,例如现在有两行按钮(每行都使用一个行排列的wx.BoxSizer)包含在另一个wx.BoxSizer中,然后让一行按钮在另一行上面。就像这样:
注意:上面的例子中,并不是排列了两行按钮,每行有三个。要这样做,需要使用wx.GridSizer.
在下面的例子中,我们使用了两个嵌套的sizer,其中一个垂直排列的sizer嵌套了一个水平排列的sizer:
import wx
import os
class MainWindow(wx.Frame):
def __init__(self, parent, title):
self.dirname=''
# A "-1" in the size parameter instructs wxWidgets to use the default size.
# In this case, we select 200px width and the default height.
wx.Frame.__init__(self, parent, title=title, size=(200,-1))
self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
menuOpen = filemenu.Append(wx.ID_OPEN, "&Open"," Open a file to edit")
menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
# Events.
self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
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.EXPAND)
# Use some sizers to see layout options
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.control, 1, wx.EXPAND)
self.sizer.Add(self.sizer2, 0, wx.EXPAND)
#Layout sizers
self.SetSizer(self.sizer)
self.SetAutoLayout(1)
self.sizer.Fit(self)
self.Show()
def OnAbout(self,e):
# Create a message dialog box
dlg = wx.MessageDialog(self, " A sample editor \n in wxPython", "About Sample Editor", wx.OK)
dlg.ShowModal() # Shows it
dlg.Destroy() # finally destroy it when finished.
def OnExit(self,e):
self.Close(True) # Close the frame.
def OnOpen(self,e):
""" Open a file"""
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, "Sample editor")
app.MainLoop()
sizer.Add函数有三个参数。第一个指定要被包含在这个sizer中控件。第二个是权重因数,表明这个控件在被调整大小时所占的比例。
例如,你有三个可编辑的控件,你想它们的比例为3:2:1,当添加控件时,你需要指定他们的因数作为参数。0表示这个控件或sizer并不会改变。
第三个参数是正常的wx.GROW(就像wx.EXPAND),表示当需要的时候,这个控件会被调整大小。如果使用了wx.SHAPED,会保持这个控件的纵横比。
如果第二个参数是0,也就是说,这个控件并不会被调整大小。如果这个控件需要水平或垂直居中,则需要使用wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_CENTER_VERTICAL或wx.ALIGN_CENTER(可以都写上),而不是使用wx.GROW或wx.SHAPED作为第三个参数。
您还可以指定组合wx.ALIGN_LEFT, wx.ALIGN_TOP, wx.ALIGN_RIGHT和wx.ALIGN_BOTTOM.默认的情况下是wx.ALIGN_LEFT | wx.ALIGN_TOP.
wx.Sizer和它的子类有一个令人困惑的地方,一个sizer和一个parent window之间是有区别的。当你要创建一个对象添加到sizer中时,你不能让sizer作为对象的父窗口。sizer是排列窗口的一种方式,但是sizer并不等于window.
在上面的例子中,所有六个按钮都被创建,父窗口是包围按钮的框架或窗口——而不是sizer。如果你尝试创建一个视觉元素,而它的父窗口是sizer的话,那么你的程序就会崩溃。
一旦你创建了你的视觉元素,并把它们添加到一个sizer中(或者是一组嵌套的sizer),下一步就是告诉你的窗口或框架去使用这个sizer。你可以这样去做:
window.SetSizer(sizer)
window.SetAutoLayout(True)
sizer.Fit(window)
SetSizer()会告诉你的窗口(或框架)哪个sizer会被使用。SetAutoLayout()告诉你的窗口使用这个sizer去定位并调整你的组件。最后,调用sizer.Fit()告诉这个sizer计算所有元素的初始位置和大小。
如果你使用了sizer,这是您在第一次显示窗口或框架内容之前所要经历的正常过程。
菜单
验证器
当你创建了一个对话框或其他的输入表单时,你可以使用wx.Validator简化将数据加载到表单中的过程,验证输入的数据,并再次从表单中提取数据。wx.Validator也可以用来在输入时,拦截键盘或其他事件。
为了使用验证器,你需要去创建一个你自己的wx.Validator的子类(而不是wxPython中的wx.TextValidator或wx.GenericValidator)。然后调用myInputField.SetValidator(myValidator)把它和输入控件关联。
注意:你的wx.Validator子类必需实现wxValidator.Clone()函数。
一个实际的例子
我们第一个在面板中的标签
让我们开始一个例子。我们的程序会拥有单个的框架,包括一个面板和一个标签:
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()
如果你阅读了Small Editor部分,这个设计应该是清楚的,你不应该有任何问题。注意:这里应该使用sizer,而不是指定每个部件的位置。注意这一行:
self.quote = wx.StaticText(panel, label="Your quote: ", pos=(20, 30))
面板被用来作为我们wxStaticText的父参数。我们的静态文本将会出现在我们之前创建的面板上。wxPoint被用来作为位置参数。也有一个可选参数wxSize,但是在这里是不合理的。
[7]根据wxPython的文档:
“面板是放置控件的窗口。它通常被放置在框架中。它包含了最小的额外功能,超过了它的父类wxWindow;它主要就是类似于对话框的外观和功能,但是拥有任何父窗口的灵活性。”
事实上,它是一个简单的窗口被用来作为其他可处理数据的对象的(灰色)背景。它通常被当作控件或部件。
[8]标签被用来显示文本,不能用来与用户进行交互。
再添加一些控件
在示例和帮助中,你可以发现一系列完整的存在于wxPython中的大量控件,这里我们去展示那些更经常使用到的:
wxButton是最基础的控件:一个按钮展示了一段可以点击的文本。例如,这里有一个“Clear“按钮(去清除一段文件):
clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
wxTextCtrl这个控件可以让用户去输入文本。它自带有两个事件。在文本被改变时会调用EVT_TEXT,无论哪一个按键被点击时都会调用EVT_CHAR。
textField = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnChange, textField)
self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
例如:如果用户点击了”Clear“按钮,然后清除了文本区域,会发生EVT_TEXT事件,而不会发生EVT_CHAR事件。
wxComboBox一个非常类似于wxTextCtrl的下拉框,但是除了wxTextCtrl带有的事件外,wxComboBox还有EVT_COMBOBOX事件。
wxCheckBox复选框控件让用户用来选择true/false。
wxRadioBox让用户可以从一系列选项中选择的单选框
让我们仔细看看Form1的例子:
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))
# A multiline TextCtrl - This is here to show how the events work in this program, don't pay too much attention to it
self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
# A button
self.button =wx.Button(self, label="Save", pos=(200, 325))
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
# the edit control - one line version.
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)
# the combobox Control
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)
self.Bind(wx.EVT_TEXT, self.EvtText,self.edithear)
# Checkbox
self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?", pos=(20,180))
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
# Radio Boxes
radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
rb = wx.RadioBox(self, 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, rb)
def EvtRadioBox(self, event):
self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
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 EvtCheckBox(self, event):
self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
app.MainLoop()
我们的类变得更大了;它现在有很多控件和控件反馈。我们添加了一个指定的wxTextCtrl,用来显示各个控件所发生的事件信息。
记事本
有时候,单个页面填充了一个数据量非常大的表单。wxNoteBook适用于这种情况:它允许用户通过点击相应的选项卡,可以快速的在少量的页面之间就是切换导航。
我们通过放置wxNoteBook代替我们的表单到主框架中,然后使用AddPage去添加我们的Form1到notebook中去实现这个功能。
注意ExamplePanel的parent是NoteBook。这是非常重要的
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()
改善布局——使用Sizer
经常使用绝对位置并让人非常满意:如果窗口不是正常的大小时(或其他理由)导致布局会很难看。wxPython有丰富的对象去排列控件。
wx.BoxSizer是最常用最简单的布局对象,它允许有大量的可能性。它的规则是非常简单的,就是按照一行或一列去排列控件,并在需要的时候去重新排列(当全局大小改变时)。
wx.GridSizer和wx.FlexGridSizer是两个非常重要的布局工具。它们将控件安装在一个表格布局中。
下面是重写了上面的例子,加入了sizer:
class ExamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
grid = wx.GridBagSizer(hgap=5, vgap=5)
hSizer = wx.BoxSizer(wx.HORIZONTAL)
self.quote = wx.StaticText(self, label="Your quote: ")
grid.Add(self.quote, pos=(0,0))
# A multiline TextCtrl - This is here to show how the events work in this program, don't pay too much attention to it
self.logger = wx.TextCtrl(self, size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
# A button
self.button =wx.Button(self, label="Save")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
# the edit control - one line version.
self.lblname = wx.StaticText(self, label="Your name :")
grid.Add(self.lblname, pos=(1,0))
self.editname = wx.TextCtrl(self, value="Enter here your name", 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)
# the combobox Control
self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
self.lblhear = wx.StaticText(self, label="How did you hear from us ?")
grid.Add(self.lblhear, pos=(3,0))
self.edithear = wx.ComboBox(self, 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)
# add a spacer to the sizer
grid.Add((10, 40), pos=(2,0))
# Checkbox
self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?")
grid.Add(self.insure, pos=(4,0), span=(1,2), flag=wx.BOTTOM, border=5)
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
# Radio Boxes
radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
rb = wx.RadioBox(self, label="What color would you like ?", pos=(20, 210), choices=radioList, majorDimension=3,
style=wx.RA_SPECIFY_COLS)
grid.Add(rb, pos=(5,0), span=(1,2))
self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, 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)
self.SetSizerAndFit(mainSizer)
这个例子使用了一个GridBagSizer去摆放它的控件。”pos“参数按照(x,y)的位置,控制了控件在表格中处于什么位置。例如,(0,0)是左上角的位置,(3,5)是在第三行第五列上。span参数允许一个控件去跨越多行或多列。
响应用户操作
主题:
事件
弹出式菜单
入门指南
概念请看这里
一个实际的例子
TODO:示例看这里。
绘制
主题:
设备上下文
字体
颜色
onPaint()函数
入门指南
在这个部分中,我们会介绍如何在一个窗口中进行绘制。我们也会展示如何去创建一个弹出式菜单,当在主窗口右击时。
一个实际的例子
使用wxPython
调试技术
当你的python程序碰到一个不可解决的错误(Bug!)时,程序会带着一个回溯中止运行,它用来帮助你去定位程序错误的位置。wxPython程序也可以做到这样,但会有一些不太一样。
回溯被导向了标准输入输出,在一个独立于程序的GUI框架中被捕获。如果一个错误发生在了一个事件处理中,回溯会显示出来,但你的程序会继续运行下去。但是,当初始化你的程序时出错,回溯会显示出来,然后程序就会中止,带着标准输入输出窗口(和回溯)。
当你继承了wxApp时,你可以通过wxPython提供的可选参数去获取标准输入输出。下面的例子可以很好的说明这个:
class MyApp (wx.App):
#...
#...
#...
myapp = MyApp() # functions normally. Stdio is redirected to its own window
myapp = MyApp(0) #does not redirect stdout. Tracebacks will show up at the console.
myapp = MyApp(1, 'filespec') #redirects stdout to the file 'filespec'
# NOTE: These are named parameters, so you can do this for improved readability:
myapp = MyApp(redirect = 1, filename = 'filespec') # will redirect stdout to 'filespec'
myapp = MyApp(redirect = 0) #stdio will stay at the console...
你也可以使用Widget Inspection Tool去帮助调试布局问题。
PyCrust可交互的shell
wxPython自带有PyCrust shell。使用它,你可以交互式的去测试你的布局。这里有一个简单的例子。
部署你的wxPython应用
下一步
事件
事件处理是wxPython关键功能之一。我们都知道所有的GUI系统,在大量的应用之间都依赖于事件去分发信息。当接受到一个详细的事件的时候,决定如何去做是每一个GUI应用的工作,被称为事件处理。
在以前的面对过程的程序中,处理事件意味着你要有一个”switch“操作,然后决定某个特定类型的事件应该如何去做。对于面向对象程序来说,这不是很简单的吗:有两种方法去处理事件:
一种方法(例如JAVA)依赖去调用事件处理。事件处理和特定的对象联系在一起,必定有一个回调函数。当对象接受到特定类型的事件时,事件处理触发回调函数。
另一种方法是提供预定名字的函数去处理特定的事件。这种方法,如果你要去修改一个特定类对于一个特定事件的响应,你应该派生你的类并重写相关的函数。
wxPython结合了以上两种方法。你可以通过派生类和实现新的行为去定义事件处理。
所以self.Bind(wx.EVT_SOMETHING, ACallable)意味着:
当一个SOMETHING事件传递到这个窗口(self)时,这个事件可以来自任何子窗口或它自己,然后调用ACallable,并且self必须是派生自wxPython窗口的一个类(如,一个按钮,一个对话框,一个框架),"ACallable”可以是任何函数,然后对于程序员来说通常的选择是使它成为上述类中的一个函数。
Scintilla
在wxPython中,Scintilla是wxStyledTextCtrl使用的基础组件,它提供了语法着色。
Boa-Constructor
Boa-Constructor是wxPython的一个RAD IDE
有用的资源
http://wxPython.org/
首先,一个很普通的网站,但是你可以去看看在wxPython页面上的Demo。这些是非常有用的示例,更接近于所有你想要的项目。如何去运行这些Demo:
在Windows环境下,点击开始菜单中的wxPython子菜单,选择这个程序然后运行。
在Linux环境下,找到Demo所在的目录路径,并运行“python demo.py”
http://wxPython.org/
你也可以尝试去wxWidgets网站发现信息。wxPython的文档包括了所有的wxWidget。如果你已经有了wxPython文档,去这个网站就没有意义了。
http://wxpython.org/maillist.php
这是wxPython的邮件列表。这是一个发现特定信息的好地方。在询问任何问题之前,请先寻找相关的文档。
Boa-Constructor是wxPython的一个RAD GUI buiding IDE。
这是一篇为新手准备的优秀文章。
最后但同样重要的,由Mark Hammond和Andy Robinson编写的书“Python Programming on Win32"有一个非常优秀的wxPython章节。
Scintilla是wxPython提供绑定的完整编辑组件(一个控件名为wxStyledTextCtrl2)
http://www.python.org/
这是python社区的相关网站
http://starship.python.net/crew/theller/py2exe/
使用这个工具,你可以将Python(也包括wxPython脚本...)脚本转换为独立的windows程序。这使得你的分发工作变得更加简单。
总之——跟随我们一起去GUI-apps的未来
你现在也学习了wxPython程序的方方面面,应该也可以去编写wxPython程序了。不要再犹豫了,快来订阅wxPython社区,来交流你所遇到的问题。
鸣谢
wxPython社区
Lucas Bruand
Rob CakeBread
Charlie Derr
Robin Dunn
Michael Roberts
Erik Westra
我们也想要去感谢:
Andrew Kuchling的帮助和支持,他并没有对于我无止境的问题和请求感到厌烦。
Robin North, J-P Syed, Armel Guenneugues, Pilar Rodriguez, Matteo Caligaris的大力支持,不开开玩笑的叫我Geek。
附录
Small editor - 完整代码
WxHowtoSmallEditor
Building Forms - 完整代码
WxHowtoBuildingForms
Drawing with wxPython - 完整代码
WxHowtoDrawing
A rudimentary project organizer - 完整代码
WxProject 它也展示了wxPyhton的用法.
常见问答
请看: FAQ
评论
评论示例
在这里可以随意添加任何评论或评论。欢迎和修改内容建议。
Lucas Bruand
OS独立路径连接
下面是代码:
import os
f=open(os.path.join(self.dirname,self.filename),'r')
Windows在这种情况下还是会出现问题,你使用普通的对话框去导航到本机的根目录时。这种情况,普通的对话框函数会返回self.dirname="c:"(你会期望是”c:“,除非你和MS交易了许多年)。不幸的是,当字符串中包括”:“时,os.path.join并不能工作,当打开”c:file.py“时会出现错误,因为这样会依赖于cwd设置(假设”file.py“在”C:\“中但不在cwd中)。
如何解决?我这样做:
f=open(self.dirname+'/'+self.filename,'r')
没有任何问题。我猜windows低级函数如”open“可能会调用os.path.normpath()修复它。这种优势在于,我还可以使用”/“用在目录和URL中,而且这段代码在Windows和Linux中都通用。我知道有一会也会出现错误。一个更好的方法就是:
f=open(os.path.normpath(os.path.join(self.dirname+os.sep,self.filename)),'r')
我认为适合于所有的场景,除非当你开始时self.dirname是'c:\'(在第一个join参数里normpath不会分离多于的分隔符)。或许一个更好的方法是使用os.path.isabs()
Kevin Vap
一步使用Sizer
我总是让设置sizer的三行代码变得更短(最少wxWidgets/wxPython 2.4以上),以前的使用是:
window.SetSizer(sizer)
window.SetAutoLayout(true)
sizer.Fit(window)
可以缩短为:
window.SetSizerAndFit(sizer)
Lukasz Pankowski
如何让标签工作
这对于我来说真的特别困难,在没有对话框的窗口中让标签工作:在整个文档中的唯一的提示就是在wxWindow风格wxTAB_TRAVERSAL的描述,但是它还是描述不太清楚。最后我这样去做:
1.想要所有的标签工作,你放置控件的窗口或独立面板,让它的风格如下:wxTAB_TRAVERSAL ie:
class ContactsPanel(wx.Panel):
def __init__(self, parent,id):
wx.Panel.__init__(self, parent, id, wx.DefaultPosition,wx.DefaultSize,
wx.RAISED_BORDER|wx.TAB_TRAVERSAL)
2.标签的顺序就是你把控件添加到面板或框架中的顺序。(信息来自于Massimiliano Sartor)
3.标签的顺序也可以看作为被创建控件的顺序。我猜这是因为控件ID数字的原因。sizer/panel的顺序貌似对于我没有用。-- Keith Veleba
4.一旦你的控件建立,这里有一些设置标签顺序的小技术:
order = (control1, control2, control3, ...)
for i in xrange(len(order) - 1):
order[i+1].MoveAfterInTabOrder(order[i])
(这个列表包括了真实的控件对象。)这使得更改顺序和添加控件等变得简单。-- Don Dwiggins
Sizer教程
这一系列的教程会描述如何去使用一部分wxPython sizer,包括如何去管理控件的位置和大小。
BoxSizerTutorial——一个只使用BoxSizer的一般教程
GridSizerTutorial——一个使用GridSizer的一般教程
BoxSizerFromTheGroundUp——有实际示例的详细教程
GridBagSizerTutorial——使用GridBagSizer的一般教程
其他的一些关于sizer的好文档:
wxSizer in python
UsingSizers
wxDesigner Sizer Tutorial
BoxSizerTutorial:
在这个教程中,我们会创建一个只使用wx.BoxSizers的示例
例如:简单的数据收集表单,图标和文本沿着右边的冒号垂直对齐。
icon - title
separator
icon - text: - single line input control
icon - text: - single line input control
icon - text: - single line input control
icon - text: - multi-line text/list control that stretches vertically
separator
ok - cancel
首先,我们会倾巢出动一个wx.Frame去包含所有的控件,然后我们会创建一个wx.Panel作为框架的皮肤,让它在所有平台上看起来都很正常。
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title='My Form')
# Add a panel so it looks correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
下一步是去指出如何去创建一个图标。我会使用适用于的wx.ArtProvider,因为它一般的跨平台的位图/图标,我会把这个位置放在wx.StaticBitmap控件上。得出的代码如下:
bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
titleIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
让我们去看看这段代码到底如何工作的。GetBitmap的第一个参数是一个艺术ID,第二个参数是一个载体(比如wx.ART_TOOLBAR或wx.ART_MENU),第三个参数是图标的大小。
StaticBitmap的参数都可以看懂的。在wxPython示例中也可以看到这两个操作。
接下来我们把所有的代码整合到一起,然后把它们放到sizer中。在这个示例中我只使用BoxSizer。
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title='My Form')
# Add a panel so it looks correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
titleIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
title = wx.StaticText(self.panel, wx.ID_ANY, 'My Title')
bmp = wx.ArtProvider.GetBitmap(wx.ART_TIP, wx.ART_OTHER, (16, 16))
inputOneIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelOne = wx.StaticText(self.panel, wx.ID_ANY, 'Input 1')
inputTxtOne = wx.TextCtrl(self.panel, wx.ID_ANY, '')
inputTwoIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelTwo = wx.StaticText(self.panel, wx.ID_ANY, 'Input 2')
inputTxtTwo = wx.TextCtrl(self.panel, wx.ID_ANY,'')
inputThreeIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelThree = wx.StaticText(self.panel, wx.ID_ANY, 'Input 3')
inputTxtThree = wx.TextCtrl(self.panel, wx.ID_ANY, '')
inputFourIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelFour = wx.StaticText(self.panel, wx.ID_ANY, 'Input 4')
inputTxtFour = wx.TextCtrl(self.panel, wx.ID_ANY, '')
okBtn = wx.Button(self.panel, wx.ID_ANY, 'OK')
cancelBtn = wx.Button(self.panel, wx.ID_ANY, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.onOK, okBtn)
self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
topSizer = wx.BoxSizer(wx.VERTICAL)
titleSizer = wx.BoxSizer(wx.HORIZONTAL)
inputOneSizer = wx.BoxSizer(wx.HORIZONTAL)
inputTwoSizer = wx.BoxSizer(wx.HORIZONTAL)
inputThreeSizer = wx.BoxSizer(wx.HORIZONTAL)
inputFourSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
titleSizer.Add(titleIco, 0, wx.ALL, 5)
titleSizer.Add(title, 0, wx.ALL, 5)
inputOneSizer.Add(inputOneIco, 0, wx.ALL, 5)
inputOneSizer.Add(labelOne, 0, wx.ALL, 5)
inputOneSizer.Add(inputTxtOne, 1, wx.ALL|wx.EXPAND, 5)
inputTwoSizer.Add(inputTwoIco, 0, wx.ALL, 5)
inputTwoSizer.Add(labelTwo, 0, wx.ALL, 5)
inputTwoSizer.Add(inputTxtTwo, 1, wx.ALL|wx.EXPAND, 5)
inputThreeSizer.Add(inputThreeIco, 0, wx.ALL, 5)
inputThreeSizer.Add(labelThree, 0, wx.ALL, 5)
inputThreeSizer.Add(inputTxtThree, 1, wx.ALL|wx.EXPAND, 5)
inputFourSizer.Add(inputFourIco, 0, wx.ALL, 5)
inputFourSizer.Add(labelFour, 0, wx.ALL, 5)
inputFourSizer.Add(inputTxtFour, 1, wx.ALL|wx.EXPAND, 5)
btnSizer.Add(okBtn, 0, wx.ALL, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
topSizer.Add(titleSizer, 0, wx.CENTER)
topSizer.Add(wx.StaticLine(self.panel,), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(inputOneSizer, 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(inputTwoSizer, 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(inputThreeSizer, 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(inputFourSizer, 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
self.panel.SetSizer(topSizer)
topSizer.Fit(self)
def onOK(self, event):
# Do something
print 'onOK handler'
def onCancel(self, event):
self.closeProgram()
def closeProgram(self):
self.Close()
# Run the program
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
运行之后,效果如下:
所以,sizer是如何进行工作的呢?让我们来看一看。这是一个最基础的风格:
mySizer.Add(window, proportion, flag(s), border, userData)
第一个参数可以是窗口,控件,sizer或size。第二个参数是一个比例,可以让开发者指定当父窗口调整大小时,一个控件应该被拉伸成多少。
第三个参数是一个标志或一系列的标志,用来控制对齐方式,边界和重定大小。第四个参数是一个边界,表示添加的控件四周的空白区域的像素值。
最后一个是用户数据,从来没有使用过。然而,根据”wxPython in Action“这本书,它用于将额外的数据传递给sizer算法。
第三个参数中的标志,我使用的是wx.ALL, wx.EXPAND和wx.CENTER:wx.ALL用于在控件的所有方向放置x个像素。wx.EXPAND告诉sizer,允许控件在parent绅缩时进行绅缩。wx.CENTER会使控件外于parent的正中间。
如你所见,我为两个或更多的需要水平对齐的控件创建了一个单独的BoxSizer。然后我创建了一个主的垂直方向的BoxSizer,使得它可以把其他的sizer都放在里面。我也创建了两个wx.StaticLine在适当的地方作为分隔符。
最后,我使用SetSizer()把panel和最上层的Sizer关联到了一起。我也调用了sizer的Fit()函数,告诉sizer去计算基于窗口的大小。你也可以使用SetMinSize()函数,使得内部的所有控件处于最小状态。
现在你已经学习了如何使用BoxSizer去建立一个表单。进一步可以阅读:
wx.ArtProvider Docs
Learn Sizers
UsingSizers
GridSizerTutorial:
在我的BoxSizerTutorial中,我在wxPython中创建了一个普通的表单,只使用了wx.BoxSizer去自动调整我的控件大小。这一次,我在我的上一个示例中添加了wx.GridSizer去完成以下功能:
如何右对齐图标和标签
如何水平对齐标签和文件控件
如何保持文本控件和标签对齐,无论标签的长度如何
众所周知,实现上面功能的方法不只一种,只有更好更简单的。任何愚蠢的代码都是我的问题。
现在,是实现功能的时候了!需要注意的第一项就是使用wx.GridSizer。看看下面的代码:
gridSizer = wx.GridSizer(rows=4, cols=2, hgap=5, vgap=5)
这段代码我们,我创建了一个有4行2列,并且水平垂直方向都有5像素空白的控件。它会按照从左往右的方式去添加控件,当你已经添加了等同于列数的控件时,它会自动的切换到下一行。
换句话说,如果我添加了两个控件,然后一行就满了,如果我再添加一个控件,那么它就会自动添加到第二行(依次类推)。
在我们添加任何东西到wx.GridSizer之前,我们需要先看一下我如何改变了我的wx.BoxSizer.在每一个wx.BoxSizer之前都添加了一个间隔,并且设置proportion为1.
这意味着这些间隔使得每个sizer左边都有一些空白。我也在每个BoxSizer中添加了图标和标签,然后把这个wx.BoxSizer作为第一项添加到了wx.GridSizer中。回来说一下这个间隔:
inputOneSizer.Add((20,20), proportion=1)
这个间隔在这种情况下是一个元组。实际上你可以使用任何的大小。元组中的第一个元素是宽度,第二个是高度。如果我没有设置proportion为1,我可以用宽度的大小来调整我的图标和标签。
如果你想用默认的高度,可以设置-1.这样会告诉wxPython应该去使用默认值。
你应该注意到了,当我在wx.BoxSizer中添加标签时,我添加了wx.ALIGN_CENTER_VERTICAL标志。它会强制标签处于垂直方向的中央,这样看起来相对于文件控件它是处于中央的。
在一些真正的项目中,我会让字体变大以达到相似的效果。
现在我们把wx.BoxSizer添加到我的wx>GridSizer中:
gridSizer.Add(inputOneSizer, 0, wx.ALIGN_RIGHT)
这里我设置了wx.ALIGN_RIGHT,使得所有的控件都对准了一道无形的靠近文本控件的墙。当我添加文本控件时,我设置了wx.EXPAND标志,为了使它在窗口变化的时候也跟着变化。
最后,我把wx.GridSizer添加到了我最高层的Sizer中,是一个垂直向的wx.BoxSizer:
topSizer.Add(gridSizer, 0, wx.ALL|wx.EXPAND, 5)
你应该也注意到了,在这里我也设置了wx.EXPAND标志。如果我不这样做,最高层的Sizer会一直保持着它所包含的大小。你可以自己设置那些标志尝试一下。
以下是完整的代码:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title=‘My Form’)
# Add a panel so it looks correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
titleIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
title = wx.StaticText(self.panel, wx.ID_ANY, ‘My Title’)
lblSize = (50, -1)
bmp = wx.ArtProvider.GetBitmap(wx.ART_TIP, wx.ART_OTHER, (16, 16))
inputOneIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelOne = wx.StaticText(self.panel, wx.ID_ANY, ‘Name’)
inputTxtOne = wx.TextCtrl(self.panel, wx.ID_ANY,”)
inputTwoIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelTwo = wx.StaticText(self.panel, wx.ID_ANY, ‘Address’)
inputTxtTwo = wx.TextCtrl(self.panel, wx.ID_ANY,”)
inputThreeIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelThree = wx.StaticText(self.panel, wx.ID_ANY, ‘Email’)
inputTxtThree = wx.TextCtrl(self.panel, wx.ID_ANY, ”)
inputFourIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelFour = wx.StaticText(self.panel, wx.ID_ANY, ‘Phone’)
inputTxtFour = wx.TextCtrl(self.panel, wx.ID_ANY, ”)
okBtn = wx.Button(self.panel, wx.ID_ANY, ‘OK’)
cancelBtn = wx.Button(self.panel, wx.ID_ANY, ‘Cancel’)
self.Bind(wx.EVT_BUTTON, self.onOK, okBtn)
self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
topSizer = wx.BoxSizer(wx.VERTICAL)
titleSizer = wx.BoxSizer(wx.HORIZONTAL)
gridSizer = wx.GridSizer(rows=4, cols=2, hgap=5, vgap=5)
inputOneSizer = wx.BoxSizer(wx.HORIZONTAL)
inputTwoSizer = wx.BoxSizer(wx.HORIZONTAL)
inputThreeSizer = wx.BoxSizer(wx.HORIZONTAL)
inputFourSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
titleSizer.Add(titleIco, 0, wx.ALL, 5)
titleSizer.Add(title, 0, wx.ALL, 5)
# each input sizer will contain 3 items
# A spacer (proportion=1),
# A bitmap (proportion=0),
# and a label (proportion=0)
inputOneSizer.Add((20,-1), proportion=1) # this is a spacer
inputOneSizer.Add(inputOneIco, 0, wx.ALL, 5)
inputOneSizer.Add(labelOne, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
inputTwoSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
inputTwoSizer.Add(inputTwoIco, 0, wx.ALL, 5)
inputTwoSizer.Add(labelTwo, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
inputThreeSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
inputThreeSizer.Add(inputThreeIco, 0, wx.ALL, 5)
inputThreeSizer.Add(labelThree, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
inputFourSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
inputFourSizer.Add(inputFourIco, 0, wx.ALL, 5)
inputFourSizer.Add(labelFour, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
# Add the 3-item sizer to the gridsizer and
# Right align the labels and icons
gridSizer.Add(inputOneSizer, 0, wx.ALIGN_RIGHT)
# Set the TextCtrl to expand on resize
gridSizer.Add(inputTxtOne, 0, wx.EXPAND)
gridSizer.Add(inputTwoSizer, 0, wx.ALIGN_RIGHT)
gridSizer.Add(inputTxtTwo, 0, wx.EXPAND)
gridSizer.Add(inputThreeSizer, 0, wx.ALIGN_RIGHT)
gridSizer.Add(inputTxtThree, 0, wx.EXPAND)
gridSizer.Add(inputFourSizer, 0, wx.ALIGN_RIGHT)
gridSizer.Add(inputTxtFour, 0, wx.EXPAND)
btnSizer.Add(okBtn, 0, wx.ALL, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
topSizer.Add(titleSizer, 0, wx.CENTER)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(gridSizer, 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
# SetSizeHints(minW, minH, maxW, maxH)
self.SetSizeHints(250,300,500,400)
self.panel.SetSizer(topSizer)
topSizer.Fit(self)
def onOK(self, event):
# Do something
print ‘onOK handler’
def onCancel(self, event):
self.closeProgram()
def closeProgram(self):
self.Close()
# Run the program
if __name__ == ‘__main__’:
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
BoxSizerFromTheGroundUp:
所有的关联wiki都在这里:
BoxSizerFromTheGroundUp
BoxSizerFromTheGroundUp/ControlsPadding
BoxSizerFromTheGroundUp/ControlsResizing
BoxSizerFromTheGroundUp/NestedBoxSizers
BoxSizerFromTheGroundUp/NestedControlGroups
BoxSizerFromTheGroundUp/DivideAndConquer
本页的内容包括:
1.术语
2.wx.BoxSizer
a.为什么是另一个Sizer教程
b.什么是Sizers?什么是BoxSizer?
c.flag=参数
d.短轴定位标志
术语
术语”control“等同于”widget“,也可以称为”wxWidgets“。Control/Widgets是可显示的二维wxPython图形对象。只有很少的wxPython对象是不可见的:wx.DC位图操作工具和wx.Sizer工具。在这里,一般的术语“窗口”从来没有使用过,以避免与wx.Window控件概念混淆。
wx.BoxSizer
为什么是另一个Sizer教程
有太多太多的BoxSizer教程和示例应用,但是没有一个拥有完整的文档,完整的可以详述所有参数,还有其他方面。sizer的使用在API设计方面是让人痛苦的,它要求逻辑上有不同的flag=常量,但这看起来并没有什么效果。BoxSizer似乎总是正确地执行,并且没有任何已知的错误。
什么是Sizers?什么是BoxSizer?
Sizers是wx控制自动定位算法的。它们是工具,不是控件/部件。它们的学术术语”几何管理“工具,可以根据规则去自动去调整控件的大小和位置。
BoxSizer是最基础的Sizer,大概也是被使用最多的。第一次使用并不是特别的困难,所有flag常量的意思都是很容易理解的。使用这个sizer可能是复杂的,然而它更容易让我们去掌握更高级的sizer的原理。
理解这个BoxSizer会是一个比较长的如何去学习其他sizer的学习之路。
BoxSizer可以被命名的更好一点,比如”StackSizer“。BoxSizer按照顺序去排列控件,选择水平或垂直方向,从上往下或从左向右的进行排列。
使用BoxSizer的时候,第一个需要决定的就是方向,是wx.VERTICAL还是wx.HORIZONTAL。只能去指定一个方向去进行控件的排列。实际一个BoxSizer如下:
bxSzrName = wx.BoxSizer( wx.VERTICAL ) # There are no other arguments. Sizers can't have parents because they are NOT controls.
方括号[]在下面的伪代码片段中代表着可选参数。大括号{}代表着和参数有关的值。虽然最后的三个参数是可选的,很有可能最少有一个或多少是需要的,为了去产生一个可见的外观。不管任何类型的控件都可以使用sizer去定位。Add()函数为了让它们的位置被自动控制。基本的Add()语法是:
bxSzrName.Add( { control, sizer spacer or another sizer },
[ proportion=] { positive integer },
[ flag=] { a logical "OR" phrase of border padding and minor axis positioning flags },
[ border=] { a positive integer specifying border width in pixels } )
第一步:Designing the Parent Container Widget Before Any Coding Begins
一个控件容器可是以不同类型的控件,如wx.Frame,wx.Window,wx.Panel,wx.Dialog,记事本等,它们可以持有(包含和显示)其他的控件。有不同类型的sizer可以去定位管理它们的子控件。事实上,如果在一个控件容器中使用了一个sizer,那么在这个容器中所有的子控件都应该被这个sizer所管理。所有的子控件没有通过sizer或绝对定位进行定位,可能最后会被堆放在其父窗口的左上角区域。
第一个实验就是去摆放一个框架,它由一个StaticText和一个TextCtrl组成。对于这个应用来说,最重要的就是位置要求,我的观点是:
StaticText用作标题,放在TextCtrl上方的正中间
两个控件都相对于父容器进行水平居中
两个控件都有相同值的四边空白
那么代码应该去做:
1.创建两个控件(意味着去实例化可见的wxPython对象)
2.然后创建一个BoxSizer
3.使用sizer对控件的位置进行管理
然后代码如下:
# The caption wx.StaticText is placed directly above a wx.TextCtrl.
# Note that no "pos=" positioning parameter is needed because the BoxSizer
# will determine its positioning.
# If a position were to be specified it would be ignored and overridden by the sizer.
#
caption_stTxt = wx.StaticText( self, -1, 'My TextCtrl Caption' )
self.listing_txtCtrl = wx.TextCtrl( self, -1, style=wx.TE_MULTILINE, size=(200, 150) )
allCtrls_vertSizer = wx.BoxSizer( wx.VERTICAL )
allCtrls_vertSizer.Add( caption_stTxt, proportion=0 )
allCtrls_vertSizer.Add( self.listing_txtCtrl, proportion=0 )
self.SetSizer( allCtrls_vertSizer ) # "self" must be a control with a client area
self.Layout() # Tell this control to tell its associated sizer to arrange all the visible controls.
p.s.因为sizer不是一个控件,它必须与一个控件绑定在一起,这个控件的类型必须能够在它的内容区域内可以持有显示其他控件,如框架,面板,窗口,SplitterWindow等。在上面的代码段中使用的是这种情况:
self.SetSizer( allCtrls_vertSizer )
self是诸如框架,面板,窗口,SplitterWindow等中的一个
例子中sizer已经正确的在父容器中按照垂直方向从上往下,贴着左边摆放控件。下一步最重要的就是将两个控件安排在父工作区的水平居中。这样去做的话,flag=参数会被用到。
wx.Frame是控件中少有的拥有decorations的。decorations代表性地拥有标题和位于控件顶部的按钮。控件的边界由独立的控件组成,可以提供用户的调整大小事件处理。标题栏下面边界内的区域被称为工作区。这里可以摆放子控件。在大多数的基础控件中,工作区是由控件的区域组成。
在父控件中摆放子控件,使用(0,0)为开始进行摆放。框架(和对话框)使用绝对的屏幕坐标进行摆放。相对坐标的起点是控件工作区的左上角。所以总共两个坐标系统:屏幕坐标和工作区坐标。
flag= 参数
这个参数提供了对于BoxSizer来说,相对于另一个控件和工作区如何摆放一个控件的额外说明。预先定义的常量被用来作为值,三组中的每一个都会影响控件的位置和大小。
p.s.事实上,三组几乎独立的flag=常量必须使用逻辑“或”在一起,这对于新手来说是一个很大的障碍。公认的Python化方法是,使用独立的参数进行函数调用或类实际化,以便于去描述他们各自的目的。
但是,flag参数可以分配三种大小和定位常量组中所有的值。因此,flag参数不提供任何关于它被使用的特定目的的信息。
短轴定位标志
如果一个BoxSizer声明了wx.VERTICAL,例如,使用wx.ALIGN_LEFT或wx.ALIGN_RIGHT中的一个,然后沿着sizer短轴的边界的方向定位控件。wx.ALIGN_CENTER会将控件沿着短轴居中,不管短轴的方向如何。长轴会被默认忽略掉。
wx.ALIGN_LEFT # 为了起作用,sizer必须声明wx.VERTICAL
wx.ALIGN_RIGHT # 只适用于声明了wx.VERTICAL的sizer
wx.ALIGN_TOP # 只适用于声明了wx.HORIZONTAL的sizer
wx.ALIGN_BOTTOM # 只适用于声明了wx.HORIZONTAL的sizer
wx.ALIGN_CENTER = wx.ALIGN_CENTRE # 不论短轴进行居中处理
wx.CENTER_VERTICAL # 只适用于声明了wx.HORIZONTAL的sizer. 它比使用wx.ALIGN_CENTER/RE或wx.CENTER/RE还简单.
wx.CENTER_HORIZONTAL # 只适用于声明了wx.VERTICAL的sizer. 它比使用wx.ALIGN_CENTER/RE或wx.CENTER/RE来简单.
wx.EXPAND # 使用proportion= 参数会在下面的教程中介绍
下面的两条.Add()语句,可以很简单的去沿着短轴居中控件:
allCtrls_vertSizer.Add( cap_stTxt,flag=wx.ALIGN_CENTER ) # May substitute '''wx.ALIGN_CENTRE'''
allCtrls_vertSizer.Add( self.myListing_txtCtrl, flag=wx.ALIGN_CENTER )
完整版本的示例程序:SIMPLE_SINGLE_SIZER_2_A.PY
剩下要做的事,就是在控件和工作区顶部之间插入一些空间。有两种方法可以紧挨着控件放空间,为了可以将它和任何紧邻的控件分开:
A)在.Add()语句中使用flag=一个常量值,例如wx.Top,也可以提供一个相关联的border=参数,使用一个整型的像素空间值。
在StaticText的.Add()之前或之后直接使用BoxSizer三种中的一种
Style B 更方便使用和阅读。Style A会在之后讲到。
Style B 有三个不同的类型:
1)sizerName.AddSpacer(int)
会添加一个大小为(int,int)的矩形空间
2)sizerName.Add((w,h))
会添加一个大小为(w,h)的矩形空间。当其中一个值为0时,sizer会添加一个沿着轴线的空间,因此为创建一个一维空间。
3)sizerName.AddStretchSpacer(prop=int)
会添加一个一维自适应的可伸缩的空间。这个空间有一个可伸缩的属性去限制工作区的大小,并不是由固定大小的控件和空间声明的。如果多种AddStretchSpacer是parent,sizer会根据prop=参数的值去分割可使用区域。注意.Add()有一个参数叫proportion,不是prop。
AddStretchSpacer示例:
假设创建了一个垂直方向的BoxSizer,并且垂直方向的工作区(Y轴长度)大小为100像素。这里有一个控件,在之前和之后都使用了.AddStretchSpacer()的独立控件。
self.SetClientSize( (123, 100) )
...
self.listing_txtCtrl = wx.TextCtrl( self, size=(83, 70), style=wx.TE_MULTILINE ) # The X axis dimension, 83, is not important.
allCtrls_vertSizer = wx.BoxSizer( wx.VERTICAL )
allCtrls_vertSizer.AddStretchSpacer( prop=3 )
allCtrls_vertSizer.Add( self.listing_txtCtrl, flag=wx.CENTRE )
allCtrls_vertSizer.AddStretchSpacer( prop=7 )
self.SetSizer( allCtrls_vertSizer ) # Indicate that the container control is to use this sizer.
self.Layout() # Invoke the sizer on every wx.EVT_SIZE event. This event first happens on a Frame's .Show()
这个TextCtrl的高度为70像素。还有在垂直方向上剩余的30像素被sizer分配给了两个StretchSpacer。因为prop=参数值分别是3和7,这30像素会按照3:7的比例分别分配给第一个和第二个StretchSpacer。
顶部的StretchSpacer大小=(3/(3+7))*30像素=9像素
底部的StretchSpacer大小=(7/(3+7))*30像素=21像素
proportion参数在这个例子中,只是用来解释一个BoxSizer是如何计算每个StretchSpacer的大小。StretchSpacer经常被用来使得一个控件或一组控件的两端有相同的间隔,所以容器的边界和控件的两端的间隔是相等的(把两个StretchSpacer都设置prop=1)。这是这个App需要去做的,满足在本节开头列出的三个定位要求。
allCtrls_vertSizer = wx.BoxSizer( wx.VERTICAL )
allCtrls_vertSizer.AddStretchSpacer( prop=1 ) # for major axis (vertical) centering
allCtrls_vertSizer.Add( caption_stTxt, flag=wx.ALIGN_CENTRE ) # for minor axis (horizontally for allCtrls_vertSizer) centering
allCtrls_vertSizer.Add( self.listing_txtCtrl, flag=wx.ALIGN_CENTER )
allCtrls_vertSizer.AddStretchSpacer( prop=1 ) # for major axis (vertical) centering of the 2 controls
self.SetSizer( allCtrls_vertSizer )
self.Layout() # Invoke the sizer on every wx.EVT_SIZE event. This event happens on .Show()
这是一个更聪明的功能,当用户或程序将容器调整大小时,所有的内部控件都可以自动调整大小。在下面显示的框架中,我作为用户手动的调整了框架的大小。通过两个StretchSpacer将控件进行了垂直(sizer主轴)居中。通过两个flag=参数进行了水平(sizer副轴)居中。
我们讲解了BoxSizer对齐flag参数和三种spacer中的两个。在下一个部分中,我们会探索实现单个控件四周的边界间隔,而不是在一对控件中插入spacer。
用于填充控件边框间隔的flag
我们已经看过对齐flag,现在我们看看边框间隔flag。这些flag总是和对齐flag没有任何关系,但是它们总是用逻辑“或”使得flag=参数总是和对齐flag在一起。这些flag总是被用来指定控件的边,间距值大小为border=参数的值的边。这些flag中的任何组合都被会使用在flag=参数中,但是他们总是和border=的间距值联系在一起。
边框间距Flag包括:
wx.TOP
wx.BOTTOM
wx.LEFT
wx.RIGHT
wx.ALL
wx.ALL flag是一个很便利的定义,指定控件的四边都有边框。
这里是示例程序的当前状态:
我想要去放一个小的间隔,在StaticText和TextCtrl之间。我们已经看过了如何去使用.AddSpacer()函数放置一个正方形空间,使用.Add()函数放置一个矩形空间。这里我们使用另一种方法,使用flag=和border=参数。我们可以选择放在TextCtrl的顶部或StaticText的底部,甚至可以两者都放。我希望你现在可以看到,为什么在控件之间添加一个空间是更简单的。至现在为止,TextCtrl的顶部会被填充。那样需要去做的就是,去修改TextCtrl的.Add()语句,写入7像素的边框填充。
之前:
allCtrls_vertSizer.Add( self.myListing_txtCtrl, flag=wx.ALIGN_CENTER )
之后:
allCtrls_vertSizer.Add( self.myListing_txtCtrl, flag=wx.ALIGN_CENTER | wx.TOP, border=7 )
注意,wx.ALIGN_CENTER的效果与wx.TOP的效果完全没有关系,除了它们都适用于相同的控制。我们现在可以看到的是:
现在的外观有点好看,而且很容易去做。但是,它更直接简单的就是去插入这一行:
allCtrls_vertSizer.Add( (0, 7) ) # A one-dimensional vertical spacer. The best option, overall.
或者:
allCtrls_vertSizer.AddSpacer( 7 ) # A square spacer. It's minor axis dimension wont't interfere in this case.
当提到spacer时,“达到目的的方法不止一种”。然而,别人试图去指出你的代码使用了wx.TOP参数已经努力实现了,wx.ALIGN_CENTER参数和wx.TOP参数没有任何关系,即使它们会被写入到同一个flag=参数中。
边框填充flag使得使用和修改变得困难,特别是当相信的两个控件都使用了它。使用分开的语句去插入一个spacer能够使得编写和理解你的代码变得更简单。做这种完全独立的空间效果并正确的适用它,它必须不能有任何的无意义边去影响任何的相邻控件。据此考虑,使用.Add((w,0))或.Add((0,h))spacer插入语句更好,而不是.AddSpacer(n)语句。一个正方形spacer副轴的尺寸无意中干扰了其他的相邻控件或sizer。使用一维spacer中的一个保证这种情况不会发生。但是,尺寸值必须放置在给定(x,y)坐标的适当位置。
使用这个简单的方法,可以完全不用去考虑BoxSizer的主轴和副轴:
def AddLinearSpacer( boxsizer, pixelSpacing ) :
""" A one-dimensional spacer along only the major axis for any BoxSizer """
orientation = boxsizer.GetOrientation()
if (orientation == wx.HORIZONTAL) :
boxsizer.Add( (pixelSpacing, 0) )
elif (orientation == wx.VERTICAL) :
boxsizer.Add( (0, pixelSpacing) )
#end if
#end def
这个算法是非常简单的,但让我困惑的是,为什么使用一个还没有创建的wx.BoxSizer作为一个参数。我猜这只是BoxSizer的“Design By Committee”的另一个示例。使用这个方法会更简单:
AddLinearSpacer( allCtrls_vertSizer, 7 ) # This method determines the sizer's orientation by itself.
上面就是我们要讲的控件间距。现在我们去看看BoxSizer怎样自动调整一个控件大小,当parent调整大小时。
调整控件大小
BoxSizer可以用来去扩展一个控件,“将这一项分配的空间填满”。看http://www.wxpython.org/docs/api/wx.Sizer-class.html。它很可能像人类一样的能说会道。正确的来说,分配给项的空间到底会有多少?
在WxWidget的文档中,并没有去写关于任何控件扩展后占满了工作区的所有区域,不包括固定大小控件和固定大小spacer的空间。如果有多个控件或spacer设置成了wx.EXPAND,那么没有被声明分配的空间会分配给他们。
wx.EXPAND和wx.GROW是同义词。
为了能够证明一个控件可以扩充满一个框架,那么就创建两个相邻的wx.Panel。为了区别,将两个Panel设置成为了不同的颜色。这两个Panel会被.Add()到一个水平方向的BoxSizer中。
#!/usr/bin/python
# wxEXPAND_DEMO_A_1.PY
import wx
#------------------------------------------------------------------------------
class AppFrame( wx.Frame ) :
def __init__( self ) :
#----- Configure the Frame.
wx.Frame.__init__( self, parent=None, title='wxEXPAND_DEMO_A_1' )
self.Position = (200, 0)
self.ClientSize = (300, 200)
self.BackgoundColour = 'white'
# A panel is needed for tab-traversal and platform background color consistency.
# The first control instantiated in a Frame automatically expands
# to the size Frame.ClientSize. This is unique to Frames.
frm_pnl = wx.Panel( self )
frm_pnl.BackgroundColour = (255, 255, 230) # Contrast with panels below.
#----- Create the panel's controls
left_pnl = wx.Panel( frm_pnl )
left_pnl.BackgroundColour = (200, 240, 200) # light green
right_pnl = wx.Panel( frm_pnl )
right_pnl.BackgroundColour = (200, 230, 250) # light blue
#----- Create the sizer and add the controls to it.
frmPnl_horzSizer = wx.BoxSizer( wx.HORIZONTAL )
frmPnl_horzSizer.Add( left_pnl )
frmPnl_horzSizer.Add( right_pnl )
frm_pnl.SetSizer( frmPnl_horzSizer )
frm_pnl.Layout()
#end __init__ def
#end AppFrame class
#==============================================================================
myApp = wx.App( redirect=False )
AppFrame().Show()
myApp.MainLoop()
没有大小或位置参数,是因为这是sizer的工作。flag=wx.EXPAND参数被故意忽略掉后效果如下:
这绝对不是我们想要的。但是,两个Panel仅仅是可见的,而且右边的蓝色不是在左边绿色的上面,可能会出现一个或两个Panel没有被添加到sizer中。大多数的控件并没有最小的大小。很少部分的控件,例如wx.Button和wx.StaticText会有合适的自身大小,可以说。。。在MS Windows平台,一个控件例如wx.Panel没有明确定义大小时,会变成一个20像素的正方形。这对于调试目的来说,比变成0像素大小的不可见的正方形好多了。
现在可以在.Add()语句中设置wx.EXPAND(wx.GROW)参数了。注意wx.EXPAND只能影响到副轴方向,sizer是wx.HORIZONTAL。
我们成功了!好。。。但并不完美。Panel只是wx.EXPAND了sizer的副轴。但是,如何才能使得主轴也可以产生效果呢?proportion=参数之前我们已经见过了。因为Panel假设都是相同的大小,proportion=参数就需要设置成相同大小的正数值。但是应该选择多大的值呢?任何>=1的值都可以。
然而,为了促进总体编码清晰,应该使用值1,以便任何人,包括你自己,都能够在以后看代码的时候,可以快速的理解这个参数的值。
这样好多了!尝试去调整框架的水平大小,看看sizer是如何不断的维护着Panel的宽度比例为1:1的。
现在是把我们所学到的所有东西应用到上面的single-sizer/two-panel的时候了。边框和spacer会用来完成下面的外观:
你可以将下面的屏幕快照中添加的注释与实际的示例代码相匹配。
为了精确的显示BoxSizer如何分配水平空间的比例,上面的例子已经被修改去计算由工作区宽度和三个固定的spacer提供的两个Panel的宽度。这些计算结果遵循公式:
已经分配的空间=2*左边和右边的spacer-中间的spacer
可分配的空间=工作区宽度-已经分配的空间
左边控件的宽度=可分配的空间* (winLeft_horzProp / (winLeft_horzProp + winRight_horzProp))
右边控件的宽度=可分配的空间* (winRight_horzProp / (winLeft_horzProp + winRight_horzProp))
这里有一些使用示例的sizer和控件的测试计算输出。sizer的真实宽度设置了panel永远在测试程序计算宽度的1个像素内。这证明了sizer做了适当地分配比例大小。
更多复杂的布局
很多页面的布局会比这个更加的复杂。当需要有一个复杂的显示时,它们会变得非常非常的复杂。这会要求使用更多的sizer.
多样的BoxSizer:嵌套
在下面的布局中,让一个单独的BoxSizer包含四个控件是不可能的,因为它们形成了一个二维模式。BoxSizer只能使用一个轴。这个平面模板是常客,当一个StaticText和一个TextCtrl可被复写的时候。这是很容易去做的,通过实例化一个容器控件,例如一个wx.Panel,在一个TextCtrl上面定位一个StaticText。
在你头脑中在做思考的过程时,注释上面的示意图是非常有帮助的,确定所有必要的控件组和spacer。
你自己的手绘图并不需要像这个一样完美,但是我想要让你理解我想要怎么做。这个画可以分解成两个sizer。你能够去通过看图纸就可以理解如何去写出这个sizer代码。填充的矩形是一种或另一种的spacer,并且空余的空矩形代表BoxSizer——蓝色是wx.VERTIACL(认为“蓝色皮肤”:从上往下),绿色代表wx.HORIZONTAL(认为“绿色”:从左往右)
现在我们很明白的去详细编写控件嵌套和复写。第一个任务就是去创建一个StaticText凌驾于一个TextCtrl之上的Panel类。注意我们想要去重写这个Panel,它会设计成没有硬编码的文本。意思就是,StaticText的字符值会使用一个参数代替。
class TxtCtrlPanel( wx.Panel ) :
def __init__( self, parent, caption_txt ) :
wx.Panel.__init__( self, parent=parent, id=-1 )
#-----
self.caption_stTxt = wx.StaticText( self, -1, caption_txt )
self.caption_stTxt.BackgroundColour = ( 'wHIte' )
self.list_txtCtrl = wx.TextCtrl( self, -1, style=wx.TE_MULTILINE, size=(200, 150) )
#-----
panel_vertSizer = wx.BoxSizer( wx.VERTICAL )
panel_vertSizer.Add( self.caption_stTxt, proportion=0, flag=wx.CENTER )
als.AddLinearSpacer( panel_vertSizer, 2 )
panel_vertSizer.Add( self.list_txtCtrl, proportion=0, flag=wx.ALIGN_CENTER )
self.SetSizer( panel_vertSizer ) # Invoke the sizer.
self.Fit() # Make "self" (the panel) shrink to the minimum size required by the controls.
#end __init__
def GetControls( self ) :
return self.caption_stTxt, self.list_txtCtrl
#end TxtCtrlPanel class
两个控件都用.Add()带上了flag=wx.CENTER参数,确保两个控件都被sizer居中。更宽的控件不需要这个参数,但是两个控件中的哪一个将会更宽,一般不可能提前知道。
当文本插入到TextCtrl中时,在下面代码中Line#16检索了实例化的控件为了之后使用。甚至StaticText的字符都可以修改,如果需要的话。StaticText中的Static意味着这个控件不能被交互式使用,例如TextCtrl,Button或CheckBox。
class MainFrame( wx.Frame ) :
""" A very basic single BoxSizer demo """
def __init__( self, parent ) :
wx.Frame.__init__ ( self, parent, -1, title='Simple Single Sizer Demo',
size=(400, 300), style=wx.DEFAULT_FRAME_STYLE )
self.Position = (100, 0)
self.SetBackgroundColour( (215, 150, 250) ) # yellow
sacrificial_frmCtrl = wx.Panel( self ).Hide()
#-----
txtCtl_panel_1 = TxtCtrlPanel( self, 'My TextCtrl Caption #1' )
stTxt_1, txtCtl_1 = txtCtl_panel_1.GetControls()
#-----
allCtrls_vertSizer = wx.BoxSizer( wx.HORIZONTAL )
self.SetSizer( allCtrls_vertSizer )
self.Layout() # self.Fit() # always use one or the other
#end __init__
#end MainFrame class
#==========================================================
if __name__ == '__main__' :
app = wx.PySimpleApp( redirect=False )
appFrame = MainFrame( None ).Show()
app.MainLoop()
#end if
现在这个示例可以去修改添加第二个TextCtrlPanel,这个类到目前为止,证明可以正常工作。去水平对齐两个Panel,需要一个水平方向的sizer添加到框架内并去持有它们。三个spacer也要被添加去相互分离Panel和页面的边界。另外,整体的页面需要通过一个完整的垂直方向的sizer处理。
第二个Panel的标题被更改,在Panel已经被创建带着一个已经初始的标题字符。在框架代码中,它通过重新获取StaticText对象进行的修改。也要注意,当左边标题很窄的同时,右边StaticText的宽度大于它的TextCtrl。这就说明了,为什么参数wx.CENTER或三个它的同义词中的一个需要出现在每一个.Add()语句中,是为了能让每一个控件都能够在sizer中被居中。
class MainFrame( wx.Frame ) :
""" A 2-control class with a BoxSizer demo """
def __init__( self ) :
#----- Configure the Frame.
wx.Frame.__init__ ( self, None, title='DUAL_CONTROL_CLASS_2.PY',
size=(600, 300), style=wx.DEFAULT_FRAME_STYLE )
self.Position = (100, 0)
# A panel is needed for tab-traversal and platform background color capabilities.
# The first control instantiated in a Frame automatically expands
# to the Frame's client size. This is unique to Frames.
frm_pnl = wx.Panel( self )
frm_pnl.BackgroundColour = (250, 250, 150) # yellow
#----- Create the controls
# Create all the page's controls either individually or using classes.
ctrlsPanel_1 = TxtCtrlPanel( frm_pnl, 'My TextCtrl Caption #1' )
stTxt_1, txtCtl_1 = ctrlsPanel_1.GetControls() # Retrieve the control objects.
ctrlsPanel_2 = TxtCtrlPanel( frm_pnl, 'My TextCtrl Caption #2' )
stTxt_2, txtCtl_2 = ctrlsPanel_2.GetControls()
# Let's change the original caption "after the fact".
stTxt_2.Label = 'Caption Redefined After Panel Class Instantiation'
ctrlsPanel_2.LayoutAgain()
#----- Create the sizers and add the controls to it.
panelCtrls_horzSizer = wx.BoxSizer( wx.HORIZONTAL )
als.AddLinearSpacer( panelCtrls_horzSizer, 35 )
panelCtrls_horzSizer.Add( ctrlsPanel_1 )
als.AddLinearSpacer( panelCtrls_horzSizer, 15 )
panelCtrls_horzSizer.Add( ctrlsPanel_2 )
als.AddLinearSpacer( panelCtrls_horzSizer, 35 )
frmPnl_vertSizer = wx.BoxSizer( wx.VERTICAL )
als.AddLinearSpacer( frmPnl_vertSizer, 35 )
frmPnl_vertSizer.Add( panelCtrls_horzSizer )
als.AddLinearSpacer( frmPnl_vertSizer, 35 )
#----- Create the sizer and add the controls to it.
# "There can be only one.", Duncan MacLeod from the clan MacLeod.
# That is, a page can have exactly 1 or none top level sizer.
# Sizer nesting can effectively have infinite levels.
frm_pnl.SetSizer( frmPnl_vertSizer )
frm_pnl.Fit()
#end __init__
#end MainFrame class
#==============================================================================
if __name__ == '__main__' :
app = wx.PySimpleApp( redirect=False )
appFrame = MainFrame().Show()
app.MainLoop()
#end if
#-----
lightbulb_image = wx.Image( 'LIGHTBULB.64x64.PNG', wx.BITMAP_TYPE_ANY)
lightbulb_bmap = lightbulb_image.ConvertToBitmap()
lightbulb_horzSizer = wx.BoxSizer( wx.HORIZONTAL )
for idx in xrange( 8 ) :
aLightbulb_stBmap = wx.StaticBitmap( self, -1, lightbulb_bmap )
lightbulb_horzSizer.Add( aLightbulb_stBmap, flag=wx.CENTER )
page_vertSizer.Add( lightbulb_horzSizer, flag=wx.ALIGN_CENTRE ) # note synonym
#-----
为了欢乐,我将放出一些基于文件的图形图像。
创建更复杂的布局
当计划好的时候,在页面sizer中添加控件是非常简单的,特别是当控件类为来更多样的分组控件被应用时。我让为你现在可以明白摆放页面可以很快的变得复杂。下一个话题我们来关注一个技术去解决更复杂的情况。
这里的GUI代码是由包含了很多控件的Panel组成的。每个Panel都设置了唯一的颜色,以便于能够很容易的区分彼此。在每个Panel的四周都有边框间距,以便于能够和相邻的Panel分离开。在大部分真实的GUI中,Panel会有相应的背景色,并且不会用它们自己的类名标记。
每个Panel都会包含一个单个的控件,但不一定要有一个StaticText去用来作为标签,去详细的去说明被关联控件的用途。一些控件如RadioBox已经含有标签。按钮自然也有一个自己的标签,而且不需要去包含在一个Panel中。
当然只有BoxSizer被用来去排列所有事情。
分隔线和Conquer
具有多个控件的嵌套sizer所需的所有基础知识我们已经讲完了。让我们去学习更加复杂的。这里有一个来自于Creative Labs的面向MS Windows的音乐播放器。现在的任务就是使用wxPython去重新创建下面的GUI。忽略渐变,阴影,自定义图像等。
一旦GUI创建好了,传真机就会很简单的被添加进去。
首先,这次的项目看起来让人很气馁。这里有非常基本和有用的工程原理叫Divide and Conquer可以有效的运用。简单的说,它意味着是一个很大的任务,应该分解成更小的,更容易管理的子任务。实现和测试每一个子任务,确保它能够像你想的那样工作。然后把它们这些低等级的结构整合到一起成为更高一级的结构,并测试他们。最后把它们都结合到一起成为一个单独的可工作的解决方法。
听起来很简单,是吧?当你使用Dividing and Conquering时,你也可以以分层的形式很自然的组织项目。不仅仅是让项目更简单的完成,也是为了更加容易理解。
由于这一任务的结果是图形化,所以分析它的最佳工具也是图形化的,这类作品是由真实的铅笔和纸制成的。你可以使用图形软件去代替,例如MS Visio,Adobe Photoshop,HyperSnap-DX或其他相同的项目去创建图纸。下面的是真实控件简化而来的控件简略图:
这个更简单的图纸让控件面板更容易去分析,分解成集群的控件。这个图纸并不是100%的正确,但是它对于分析的目的是足够的了。下面是使用简单的视觉检查编译的所有控件的列表:
2 wx.StaticBitmap (logo图像)
22 wx.BitmapButton (提供了自定义,但是统一的按钮外观)
2 wx.Gauge (播放进度滑块;音量滑块)
1 wx.StaticText (标题)
3 wx.StaticText (正确地显示状态信息)
此外,通过简单的观察所有控件组可以选择由特定的sizer处理:
蓝色方块代表着垂直方向的BoxSizer,绿色的代表水平方向的。