[译] 开始使用 wxPython [Getting started with wxPython]

 原文:http://wiki.wxpython.org/Getting%20Started

 

1. 第一个应用程序:Hello World

  按照“国际惯例”,我们先写一个“Hello World”的应用程序,下面是代码:

1 #!/usr/bin/env python
2 import wx
3 
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-8
2 #!/usr/bin/env python
3 import wx
4 
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值)。你可以自己定义 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 MenuBar
20         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 window
11 
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 MenuBar
23         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 window
10 
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 MenuBar
21         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 it
33         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 ( Printing )

  * Macro-commands in python ( using the eval function )

[批注:

  1. 更多演示程序可以从 wxPython 的官网下载 www.wxpython.org 

]

  

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 window
13 
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 MenuBar
23         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 options
37         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 sizers
42         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 box
49         dlg = wx.MessageDialog(self, " A sample editor \n in wxPython", "About Sample Editor", wx.OK)
50         dlg.ShowModal() # Shows it
51         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 it
11         self.logger = wx.TextCtrl(self, pos = (500, 20), size= (250, 350), style = wx.TE_MULTILINE | wx.TE_READONLY)
12 
13         # A button
14         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 version
18         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 control
24         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         # Checkbox
31         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 Boxes
35         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 TextCtrl
16         self.logger = wx.TextCtrl(self, size = (200, 300), style = wx.TE_MULTILINE | wx.TE_READONLY)
17 
18         # A button
19         self.button = wx.Button(self, label = "Save")
20         self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)
21 
22         # the edit control
23         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 control
31         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 sizer
40         grid.Add((10, 40), pos = (2, 0))
41 
42         # checkbox
43         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 boxes
48         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...

  你可以使用 Widget Inspection Tool 来调试大多数的布局事件。

6.2 PyCrust 交互式终端

  wxPython 发布了一个 PyCrust 交互式终端。你可以使用它来进行交互式测试你的布局程序。

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, 提供给我们语法高亮的功能。

  参见: StyledTextCtrl

7.3 Boa-constructor

  Boa-constructor 是 wxPython 的一个 RAD IDE。

7.4 多线程

  [官方很懒啊,什么也没留下]

7.5 管理、非管理窗口

  [官方很懒啊,什么也没留下]

8 非常有用的链接[可供参考的资料]

  · http://wxPython.org/

    wxPython 官方网站了,官方提供的资源都在这了。

  · http://wxwidgets.org/

    wxWidgets 网站,要使用 wxPython 的控件,这里也有。这个的描述文档在 wxPython 里也有。

  · http://wxpython.org/maillist.php

    这个可厉害了,和大神们交流啊,去吧!不过,如果是请教问题的话,最好先整理好问题。

  · http://www.python.org/

    python 社区

  · http://starship.python.net/crew/theller/py2exe/

    python 脚本(包括wxPython)  转换成 Windows 系统的 exe 程序。

9. 这个...

  你已经了解了 wxPython 的主要功能特性,而且能够写出 wxPython 的应用程序了。不要犹豫,加入 wxPython

社区吧,订阅邮件列表,来一起讨论。

10. 感觉贡献者

  · wxPhthon 社区

  · and 很多人 ...

11. 附录

11.1 小小编辑器

  参见:WxHowtoSmallEditor

  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

  参考:WxHowtoBuildingForms

  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 feasible
142         # for the frame since the frame contains just one component. If the
143         # frame had contained more than one component, we would use sizers
144         # 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 画图

  参考:WxHowtoDrawing

  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 popup
110             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 is
115             dragging then draw a line from the last event position to the
116             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 + currentPosition
121             self.drawLines(dc, (self.currentColour, self.currentThickness,
122                 [lineSegment]))
123             self.currentLine.append(lineSegment)
124             self.previousPosition = currentPosition
125 
126     def onSize(self, event):
127         ''' Called when the window is resized. We set a flag so the idle
128             handler will resize the buffer. '''
129         self.reInitBuffer = True
130 
131     def onIdle(self, event):
132         ''' If the size was changed then resize the bitmap used for double
133             buffering to match the window size.  We do it in Idle time so
134             there is only one refresh after resizing is done, not lots while
135             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 real
143         # wx.PaintDC and then blit the bitmap to it when dc is
144         # deleted.  Since we don't need to draw anything else
145         # 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.menu
152 
153     # These two event handlers are called before the menu is displayed
154     # 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 determine
164     # 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 methods
172     @staticmethod
173     def drawLines(dc, *lines):
174         ''' drawLines takes a device context (dc) and a list of lines
175         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 工程管理

  参见:WxProject

  该部分展示了如何使用 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_file
114             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.root
120             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 = False
129         except IOError:
130             pass
131 
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 = 0
139             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 = False
148         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 = True
154         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 = False
161         return open_it
162 
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 False
168         return True
169 
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 = True
207         if self.root:
208             if self.activeitem != self.root:
209                 if self.editor.IsModified():  # Save modified file before
210                     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 = False
215                 if go_ahead:
216                     self.tree.SetItemBold(self.activeitem, 0)
217         return go_ahead
218 
219     def OnProjectExit(self, event):
220         """Quit the program."""
221         if not self.close:
222             self.close = True
223             if not self.SaveCurrentFile():
224                 self.close = False
225             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 = False
231             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             return
240 
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             return
252         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                 return
264         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                 return
272         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 = True
284 
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 = item
294             if item != self.root:
295                 # load the current selected file
296                 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 True
314 
315 
316 if __name__ == '__main__':
317     app = App(0)
318     app.MainLoop()

 

11.5 FAQ

  参见:FAQ

 

12. 备注

12.1 '国际惯例'

  欢迎评论和发表意见

12.2 不同系统的路径问题

  代码:

1 import os
2 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)

 

 

 

 

 

 

 

 

 

 

 

 

  

 

转载于:https://www.cnblogs.com/YBhello/p/5440800.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值