第一个应用程序:“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应用程序都是wx.App这个类的一个实例。对于大多数简单的应用程序来说,你可以直接使用wx.App这个类。当你需要更复杂的功能的时候,你也许就需要去扩展wx.App类。参数“False”,意味着不重定向标准输出和错误输出到窗口上。
wx.Frame(None, wx.ID_ANY, “Hello”)
wx.Frame类是一个顶层窗口。它的用法是wx.Frame(Parent, Id, Title)。对于大对数的构造函数来说,都有这种通用的形式(一个父窗口名,后面紧随着它的Id)。在这个例子当中,我们使用None ,来说明没用父窗口,并且使用ID_ANY,来拥有一个wxWidgets分配给我们的ID 号。
frame.Show(True)
我们使一个窗口可见,通过这个函数。如果将参数改为False,你会发现程序真的在运行,但是我们看不到。
app.MainLoop()
最后,我们开始应用程序的MainLoop函数,它用来处理各种事件。
Note:你应该使用wx.ID_ANY或者wxWidgets提供的其他标准ID。你也可以使用自己创建的ID,但是没有理由那么做。
运行程序,然后你应该看到一个类似与这样的一个窗口:
(在不同的系统平台下,这个窗口的样子可能差距很大)
窗口 还是 框架?
当人们谈论GUI 的时候,他们通常会说窗口,菜单和图标。然后自然而然地,你期望应该使用wx.Window来表示一个窗口。不幸的是,情况并不是这样的。wx.Window是一个基类,所有的可视化控件都是从这个类派生出来的(比如说按钮,菜单等等)。我们平时想到的程序窗口是一个wx.Frame类。对于许多初学者来说,这是一个不幸的矛盾。
构建一个简单的文本编辑器
在这个教程中,我们将要建立一个简单的文本编辑器。在这个过程中,我们将会探索许多的widgets知识,并且学习到关于事件处理和回调函数的一些知识。
第一步:
第一步先编写一个简单的框架,里面包含一个可编辑的文本框。文本框可以通过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类运行了self.Show()在它的__init__方法中,所以我们不需要再显式地调用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。它们是wxWidgets提供的标准ID(查看全部的ID列表)。使用标准ID是一个好的习惯,如果它存在的话。这有助于让wxWidgets在不同的平台上使每一个控件的ID都看起来更加自然。
事件处理
在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是真,我们就执行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()
Note:
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绑定的样例代码中得到灵感。
拖放
MDI
选项卡视图/多文件
查找/替换对话框
打印对话框(印刷)
Python中的宏命令(使用eval函数)
等等...
在窗口中工作
标题:
框架窗口
控件/工具
布局管理
验证器
在这段中,我们将学习如何使用wxPython处理窗口和它们的组件,包括构建输入框和使用各种各种的控件。我们将创建一个小的应用程序,用来显示一个标签。如果你是一个有GUI编程经验的开发者,这将是很简单的,你可以直接查看后面高级章节的Boa-Constructor子段落。
总览
可视化控件的布局
在一个框架当中,你将会使用很多wxPython的子类去充实框架里面的内容。这里是一些你将会在你的框架中使用到的常见的控件。
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中,意味着用户可以直接将一对控件从一个可视化器件移动到另一个可视化器件上。
所有的可视化控件(wx.Window对象和它们的子类)可以包含许多子控件。比如说,wx.Frame可以包含很多个wx.Panel对象,wx.Panel对象中又可以包含多个wx.Button,wx.StaticText和wx.TextCtrl对象,给大家一个完整的控件层次结构:注意:这个仅仅只是描述了可视化控件之间的相关关系,不是说明它们在框架中的布局情况。处理框架中的控件布局,你有以下几个选择:
- 你可以手动的指定每一个控件在父窗口中的像素坐标。由于字体大小的不同等等的问题,如果想要程序在不同的平台之间可以通用的话,这个选择一般不被考虑。
- 你可以使用wx.LayoutContains,它一般用起来很复杂。
- 你可以使用像Delphi一样的LayoutAnchors,它比wx.LayoutContains更加简单一点。
- 你可以使用一个wx.Sizer的子类。
在这个文档中将会集中使用wx.Sizers,因为是大家最常用的解决方案。
Sizer
Sizer用来解决在窗口和框架中可视化控件的放置问题。Sizer可以做下列事情:
为每个控件计算合适的大小。
通过一些包含规则放置每一个控件。
当一个框架的大小被改变后,自动的重新计算每个控件的大小并且改变其坐标。
一些常见的sizers类包括:
wx.BoxSizer,以水平或垂直的方式将控件放置在一条线上。
wx.GridSizer,将控件以网状结构放置。
wx.FlexGridSizer,它和wx.GridSizer相似,但是它允许以更加灵活的方式放置可视化控件。
可以向Sizer添加一组wx.Window对象,可以通过调用sizer.Add(window, options...),或者调用sizer.AddMany(...)这两个方法向sizer中添加控件。每一个Sizer只处理那些被添加进来的控件。Sizer可以被嵌套。也就是说,你可以将一个Sizer添加到另一个Sizer当中。举个例子来说有两排按钮(每一排按钮通过一个水平的wx.BoxSizer进行布局),它可以包含在另一个wx.BoxSizer中,将一排按钮放在另一排按钮的上面,就像这样:
注意:注意上面那个例子不是将6个按钮布局成2排3列,如果要那么做的话,那就应该使用wx.GridSizer。
在下面这个例子当中,我们使用2个嵌套的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个编辑框并且你希望它们的大小比例是3:2:1,那么当你添加这些控件的时候,你就可以将这些比例指定为这个参数。0意味着这个控件或者这个sizer将不会发生大小的变化。第3个参数通常使用wx.GROW(和wx.EXPAND一样),它意味着这个控件当在需要的时候可以改变大小。如果你使用wx.SHAPED来替代wx.GROW,那么控件的纵横比将不会发生变化。
如果第二个参数是0,即控件将不会改变大小,第三个参数如果使用wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_CENTER_VERICAL这两个参数替代wx.GROW或者wx.SHARPED,那么控件将会被定位在垂直或者水平方向的中间位置。如果使用wx.ALIGN_CENTER那么控件将会被定位在程序窗口的正中间。
你可以在wx.ALIGN_LEFT,wx.ALIGN_TOP,wx.ALIGN_RIGHT和wx.ALIGN_BOTTOM中选择几个作为一个组合。默认的行为是wx.ALIGN_LEFT | wx.ALIGN_TOP。
大家可能对wx.Sizer和它的子类存在关于sizer和父窗口之间的差别的困惑。当你创建了一个对象放到了sizer里面,你并没有指定sizer的父窗口。Sizer只是一种窗口布局的方式,它本身并不是一个窗口,所以不需要指定sizer的父窗口。在上面的例子当中,六个按钮被创建的时候指定的父窗口是框架--不是sizer。如果你尝试创建一个可视化控件并且用sizer作为它的父窗口,那么你的程序将会崩溃。
当你建立了你的可视化控件之后并将它们添加到sizer(或者嵌套的sizer)里面,下一步就是告诉你的框架或者窗口去调用这个sizer,你可以完成这个,用以下三步:
- window.SetSizer(sizer)
- window.SetAutoLayout(True)
- sizer.Fit(window)
调用SetSizer()方法告诉你的窗口或框架去使用这个sizer。调用SetAutoLayout()方法告诉你的窗口使用sizer去为你的控件计算位置和大小。最后,调用sizer.Fit()告诉sizer去计算所有控件的初始位置和大小。如果你使用sizer进行布局,在第一次显示你窗口中所有的控件之前,这都是一个常见的步骤。
Validator/验证器
当你创建了一个对话框或者一个其他的输入窗体,你可以使用wx.Validator来载入数据到你的输入窗体,验证输入的数据,再次从窗体中提取数据。wx.Validator也可以被用来截获键盘按键和其他的发生在输入区域框中的事件。如果要使用一个Validator,你应该创建一个你自己的wx.Validator的子类(wx.TextValidator和wx.GenericValidator都没有被wxPython实现)。通过调用myInputField.SetValidator(myValidator)来使你的这个子类和你的文字输入框联系起来。
注意:你的wx.Validator子类必须实现wx.Validator.Clone()方法。
一个可以运行的例子
我们的第一个程序包含一个panel(面板),panel中包含一个label(标签)
让我们开始一个例子。我们的程序将有一个框架,它有一个panel包含一个label:
- import wx
- class ExampleFrame(wx.Frame):
- def __init__(self, parent):
- wx.Frame.__init__(self, parent)
- panel = wx.Panel(self)
- self.quote = wx.StaticText(panel, label="Your quote: ", pos=(20, 30))
- self.Show()
- app = wx.App(False)
- ExampleFrame(None)
- app.MainLoop()
这段代码应该是很清晰的,并且应该没有任何问题。
注意:这里应该使用sizer,来替代指定控件坐标的部分。
注意这一行:
self.quote = wx.StaticText(panel, label="Your quote: ", pos=(20, 30))
使用了panel作为我们的wx.StaticText的父窗口参数。我们的静态文本将出现在我们刚刚创建的面板上。wx.Point被用来做位置参数。还有一个可选参数wx.Size但是如果它在这里使用,将不会被对齐。
根据wxPython文档中的描述:
“面板(panel)是一个窗口,可以将其他的控件放到它的上面。它通常放置在框架中。在它父类(wx.Window)的基础上,它包含了最小的额外的功能。它的主要是用在功能和外观相似的对话框上,它要比任何的父窗口有更强的灵活性。”,事实上,它是一个简单的窗口,被用来做其他对象的背景。它作为一个控件,这些一般都是被大家所知道的。
标签(label)用来显示一些不能被用户所改变的文本。
添加更多的控件
你可以在wxPython的样例代码和帮助文档中发现全部控件的列表,但是我们在这里只展示几个最常用的控件:
wx.Button,最基础的控件:一个按钮上面显示文本,你可以点击它。比如说这里有一个“Clear”按钮:
- clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
- self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
wx.TextCtrl,文本框,这个控件可以让用户输入文本。它触发2个主要的事件。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事件。
wx.ComboBox, 组合框和文本框很相似,但是它除了包含wx.TextCtrl会触发的2个事件,它还有EVT_COMBOBOX事件。
wx.CheckBox, 复选框是提供给用户选择真假的控件。
wx.RadioBox, 单选框可以让用户从一组选项中选择一个。
现在让我们看看这个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()
我们的类现在变得更大了;它现在有了许多控件并且这些控件都是有响应的。我们添加了一个特别的wx.TextCtrl控件去显示控件发出的各种各样的事件。
标签页
有时候一个窗体变得太大了,以至于不能在一个单个的页面中显示。wx.NoteBook就是用来解决这个问题的:它允许用户通过点击相关的标签页,在多个页面中快速浏览。我们实现了这个,通过将窗体放入wx.NoteBook,而不是放入主框架中,并且通过使用AddPage方法将Form1添加到标签页中。
注意ExamplePanel的父窗口是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是两个非常重要的布局工具。它们安排控件以表格的形式布局。
这里将上面的代码简单的重写了一下:
- 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去放置控件。Grid对象的“pos”参数决定控件在grid中的位置。在这个实例中(0,0)是左上角,(3,5)指的是第三排第五列,span参数允许控件被强行扩展成多行多列。
用户行为的响应
标题:
事件
弹出菜单
总览
概念
一个可以运行的例子
例子
绘图
标题
设备环境字体
颜色
onPaint()方法
总览
在这段中,我们将介绍在窗口中画图的方法。我们也将会展示如何在主窗口中按下鼠标右键,出现弹出菜单。
一个可以运行的例子
使用wxPython
调试技术
当在你的程序中遇到一个不能被处理的异常时(bug!),程序被异常终止,那么使用追踪技术去定位问题代码是很有用的。wxPython程序也是一样的,但是它是扭曲的(twist)。wxPython中的追踪是重新定向标准输入输出。它是独立于你程序的GUI窗口的。如果一个异常发生在事件处理阶段,追踪信息会被显示出来,并且你的程序会尽力地继续执行。但是,如果异常发生在你程序的初始化阶段,追踪信息会被显示出来,然后你的程序会被异常终止,查看标准输入输出窗口(或者你重定向的位置),可以让读者尽快知道问题所在。你可以截获标准输入输出,通过wxPython提供的两个可选参数,在你实例化你的wx.App对象的时候,可以指定这两个参数。这个例子可以很好的说明:
- 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交互控制台
wxPython发布一个漂亮的PyCrust控制台。使用它,你可以以交互的方式测试你的布局。这里有一段简单的代码。
部署你的wxPython应用
下一步
事件
事件处理是wxPython很关键的一部分。我们知道所有的GUI系统都是依赖于事件去在各种应用程序之间分发消息。GUI程序的任务是当接收到一个特定的消息的时候,自己决定去做什么,并且调用事件处理。在面向对象编程以前,想要处理事件就意味着必须有一个“switch”操作,根据事件的类型来决定去做什么事。现在在面向对象编程中这就不再是那么简单的事情了。现在有2种事件处理的方式:
一种方法(像java)是依赖于事件处理器。事件处理器和一个特定的对象链接并且和一个回调函数/方法绑定。当对象接收到一个特定类型的事件,事件处理器就会触发回调函数。
另一种方法是预先给一个方法起一个名字,假定用这个方法处理某一种特定的事件。使用这种方法的话,如果你想要改变某个类对某个事件的响应,那么你就必须在原有的类的基础上进行派生,并且重载响应的方法。
wxPython结合了这两种方法。你可以定义事件处理器与派生类去实现新的行为。
所以“self.Bind(wx.EVT_SOMETHING,ACallable)”的意思是:当EVT_SOMETHING事件被发送到窗口的时候,它将会调用ACallable。ACallable可以是任何函数,通常程序员的选择是让它实现上一个类的功能。
第二个版本是“self.Bind(wx.EVT_SOMETHING,ACallable,srcWin)”的意思是:当源窗口发出SOMETHING事件,这个事件到达窗口,然后调用ACallable。
但是很多事件只能被发出事件的窗口所捕获(这意味着第二个方式不能做任何事情),所以最好无论在什么情况下都使用第一种方法,第一种方法可以用在任何地方,除了菜单项,因为它没有Bind()方法。
在这里:创建一个自定义的事件处理程序
二,wxPython实现画板.
class DoodleWindow(wx.Window): colours = [‘Black‘, ‘Yellow‘, ‘Red‘, ‘Green‘, ‘Blue‘, ‘Purple‘, ‘Brown‘, ‘Aquamarine‘, ‘Forest Green‘, ‘Light Blue‘, ‘Goldenrod‘, ‘Cyan‘, ‘Orange‘, ‘Navy‘, ‘Dark Grey‘, ‘Light Grey‘] thicknesses = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128] def __init__(self, parent): super(DoodleWindow, self).__init__(parent, style=wx.NO_FULL_REPAINT_ON_RESIZE) self.initDrawing() self.makeMenu() self.bindEvents() self.initBuffer() def initDrawing(self): self.SetBackgroundColour(‘WHITE‘) self.currentThickness = self.thicknesses[0] self.currentColour = self.colours[0] self.lines = [] self.previousPosition = (0, 0) def bindEvents(self): for event, handler in [ (wx.EVT_LEFT_DOWN, self.onLeftDown), # Start drawing (wx.EVT_LEFT_UP, self.onLeftUp), # Stop drawing (wx.EVT_MOTION, self.onMotion), # Draw (wx.EVT_RIGHT_UP, self.onRightUp), # Popup menu (wx.EVT_SIZE, self.onSize), # Prepare for redraw (wx.EVT_IDLE, self.onIdle), # Redraw (wx.EVT_PAINT, self.onPaint), # Refresh (wx.EVT_WINDOW_DESTROY, self.cleanup)]: self.Bind(event, handler) def initBuffer(self): ‘‘‘ Initialize the bitmap used for buffering the display. ‘‘‘ size = self.GetClientSize() self.buffer = wx.EmptyBitmap(size.width, size.height) dc = wx.BufferedDC(None, self.buffer) dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() self.drawLines(dc, *self.lines) self.reInitBuffer = False def makeMenu(self): ‘‘‘ Make a menu that can be popped up later. ‘‘‘ self.menu = wx.Menu() self.idToColourMap = self.addCheckableMenuItems(self.menu, self.colours) self.bindMenuEvents(menuHandler=self.onMenuSetColour, updateUIHandler=self.onCheckMenuColours, ids=self.idToColourMap.keys()) self.menu.Break() # Next menu items go in a new column of the menu self.idToThicknessMap = self.addCheckableMenuItems(self.menu, self.thicknesses) self.bindMenuEvents(menuHandler=self.onMenuSetThickness, updateUIHandler=self.onCheckMenuThickness, ids=self.idToThicknessMap.keys()) @staticmethod def addCheckableMenuItems(menu, items): ‘‘‘ Add a checkable menu entry to menu for each item in items. This method returns a dictionary that maps the menuIds to the items. ‘‘‘ idToItemMapping = {} for item in items: menuId = wx.NewId() idToItemMapping[menuId] = item menu.Append(menuId, str(item), kind=wx.ITEM_CHECK) return idToItemMapping def bindMenuEvents(self, menuHandler, updateUIHandler, ids): ‘‘‘ Bind the menu id‘s in the list ids to menuHandler and updateUIHandler. ‘‘‘ sortedIds = sorted(ids) firstId, lastId = sortedIds[0], sortedIds[-1] for event, handler in [(wx.EVT_MENU_RANGE, menuHandler), (wx.EVT_UPDATE_UI_RANGE, updateUIHandler)]: self.Bind(event, handler, id=firstId, id2=lastId) # Event handlers: def onLeftDown(self, event): ‘‘‘ Called when the left mouse button is pressed. ‘‘‘ self.currentLine = [] self.previousPosition = event.GetPositionTuple() self.CaptureMouse() def onLeftUp(self, event): ‘‘‘ Called when the left mouse button is released. ‘‘‘ if self.HasCapture(): self.lines.append((self.currentColour, self.currentThickness, self.currentLine)) self.currentLine = [] self.ReleaseMouse() def onRightUp(self, event): ‘‘‘ Called when the right mouse button is released, will popup the menu. ‘‘‘ self.PopupMenu(self.menu) def onMotion(self, event): ‘‘‘ Called when the mouse is in motion. If the left button is dragging then draw a line from the last event position to the current one. Save the coordinants for redraws. ‘‘‘ if event.Dragging() and event.LeftIsDown(): dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) currentPosition = event.GetPositionTuple() lineSegment = self.previousPosition + currentPosition self.drawLines(dc, (self.currentColour, self.currentThickness, [lineSegment])) self.currentLine.append(lineSegment) self.previousPosition = currentPosition def onSize(self, event): ‘‘‘ Called when the window is resized. We set a flag so the idle handler will resize the buffer. ‘‘‘ self.reInitBuffer = True def onIdle(self, event): ‘‘‘ If the size was changed then resize the bitmap used for double buffering to match the window size. We do it in Idle time so there is only one refresh after resizing is done, not lots while it is happening. ‘‘‘ if self.reInitBuffer: self.initBuffer() self.Refresh(False) def onPaint(self, event): ‘‘‘ Called when the window is exposed. ‘‘‘ # Create a buffered paint DC. It will create the real # wx.PaintDC and then blit the bitmap to it when dc is # deleted. Since we don‘t need to draw anything else # here that‘s all there is to it. dc = wx.BufferedPaintDC(self, self.buffer) def cleanup(self, event): if hasattr(self, "menu"): self.menu.Destroy() del self.menu # These two event handlers are called before the menu is displayed # to determine which items should be checked. def onCheckMenuColours(self, event): colour = self.idToColourMap[event.GetId()] event.Check(colour == self.currentColour) def onCheckMenuThickness(self, event): thickness = self.idToThicknessMap[event.GetId()] event.Check(thickness == self.currentThickness) # Event handlers for the popup menu, uses the event ID to determine # the colour or the thickness to set. def onMenuSetColour(self, event): self.currentColour = self.idToColourMap[event.GetId()] def onMenuSetThickness(self, event): self.currentThickness = self.idToThicknessMap[event.GetId()] # Other methods @staticmethod def drawLines(dc, *lines): ‘‘‘ drawLines takes a device context (dc) and a list of lines as arguments. Each line is a three-tuple: (colour, thickness, linesegments). linesegments is a list of coordinates: (x1, y1, x2, y2). ‘‘‘ dc.BeginDrawing() for colour, thickness, lineSegments in lines: pen = wx.Pen(wx.NamedColour(colour), thickness, wx.SOLID) dc.SetPen(pen) for lineSegment in lineSegments: dc.DrawLine(*lineSegment) dc.EndDrawing() class DoodleFrame(wx.Frame): def __init__(self, parent=None): super(DoodleFrame, self).__init__(parent, title="Doodle Frame", size=(800,600), style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) doodle = DoodleWindow(self) if __name__ == ‘__main__‘: app = wx.App() frame = DoodleFrame() frame.Show() app.MainLoop()
参考链接:https://www.yiibai.com/wxpython
原文地址:https://www.cnblogs.com/moying-wq/p/10289814.html