原文链接:wxPython 教程 (十一): wxPython 应用骨架
在本节,我们将创建一些 wxPython 应用骨架 。样例脚本将只负责设计界面而非实现功能,目标是为了展示几款流行 GUI 界面如何用 wxPython 来完成。
文件管理器
我们将创建一个 叫做 File Hunter 的应用,它是一个文件管理器,界面模仿 Unix 系统的文件管理器 Krusader。如果我们双击 splitter 部件,File Hunter 将被分成同样宽度的两个部分,如果我们重新调整主窗口大小时也会发生同样的事。
#!/usr/bin/python import wx import os import time ID_BUTTON=100 ID_EXIT=200 ID_SPLITTER=300 class MyListCtrl(wx.ListCtrl): def __init__(self, parent, id): wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT) files = os.listdir('.') images = ['images/empty.png', 'images/folder.png', 'images/source_py.png', 'images/image.png', 'images/pdf.png', 'images/up16.png'] self.InsertColumn(0, 'Name') self.InsertColumn(1, 'Ext') self.InsertColumn(2, 'Size', wx.LIST_FORMAT_RIGHT) self.InsertColumn(3, 'Modified') self.SetColumnWidth(0, 220) self.SetColumnWidth(1, 70) self.SetColumnWidth(2, 100) self.SetColumnWidth(3, 420) self.il = wx.ImageList(16, 16) for i in images: self.il.Add(wx.Bitmap(i)) self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) j = 1 self.InsertStringItem(0, '..') self.SetItemImage(0, 5) for i in files: (name, ext) = os.path.splitext(i) ex = ext[1:] size = os.path.getsize(i) sec = os.path.getmtime(i) self.InsertStringItem(j, name) self.SetStringItem(j, 1, ex) self.SetStringItem(j, 2, str(size) + ' B') self.SetStringItem(j, 3, time.strftime('%Y-%m-%d %H:%M', time.localtime(sec))) if os.path.isdir(i): self.SetItemImage(j, 1) elif ex == 'py': self.SetItemImage(j, 2) elif ex == 'jpg': self.SetItemImage(j, 3) elif ex == 'pdf': self.SetItemImage(j, 4) else: self.SetItemImage(j, 0) if (j % 2) == 0: self.SetItemBackgroundColour(j, '#e6f1f5') j = j + 1 class FileHunter(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, -1, title) self.splitter = wx.SplitterWindow(self, ID_SPLITTER, style=wx.SP_BORDER) self.splitter.SetMinimumPaneSize(50) p1 = MyListCtrl(self.splitter, -1) p2 = MyListCtrl(self.splitter, -1) self.splitter.SplitVertically(p1, p2) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_SPLITTER_DCLICK, self.OnDoubleClick, id=ID_SPLITTER) filemenu= wx.Menu() filemenu.Append(ID_EXIT,"E&xit"," Terminate the program") editmenu = wx.Menu() netmenu = wx.Menu() showmenu = wx.Menu() configmenu = wx.Menu() helpmenu = wx.Menu() menuBar = wx.MenuBar() menuBar.Append(filemenu,"&File") menuBar.Append(editmenu, "&Edit") menuBar.Append(netmenu, "&Net") menuBar.Append(showmenu, "&Show") menuBar.Append(configmenu, "&Config") menuBar.Append(helpmenu, "&Help") self.SetMenuBar(menuBar) self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT | wx.TB_TEXT) tb.AddSimpleTool(10, wx.Bitmap('images/previous.png'), 'Previous') tb.AddSimpleTool(20, wx.Bitmap('images/up.png'), 'Up one directory') tb.AddSimpleTool(30, wx.Bitmap('images/home.png'), 'Home') tb.AddSimpleTool(40, wx.Bitmap('images/refresh.png'), 'Refresh') tb.AddSeparator() tb.AddSimpleTool(50, wx.Bitmap('images/write.png'), 'Editor') tb.AddSimpleTool(60, wx.Bitmap('images/terminal.png'), 'Terminal') tb.AddSeparator() tb.AddSimpleTool(70, wx.Bitmap('images/help.png'), 'Help') tb.Realize() self.sizer2 = wx.BoxSizer(wx.HORIZONTAL) button1 = wx.Button(self, ID_BUTTON + 1, "F3 View") button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit") button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy") button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move") button5 = wx.Button(self, ID_BUTTON + 5, "F7 Mkdir") button6 = wx.Button(self, ID_BUTTON + 6, "F8 Delete") button7 = wx.Button(self, ID_BUTTON + 7, "F9 Rename") button8 = wx.Button(self, ID_EXIT, "F10 Quit") self.sizer2.Add(button1, 1, wx.EXPAND) self.sizer2.Add(button2, 1, wx.EXPAND) self.sizer2.Add(button3, 1, wx.EXPAND) self.sizer2.Add(button4, 1, wx.EXPAND) self.sizer2.Add(button5, 1, wx.EXPAND) self.sizer2.Add(button6, 1, wx.EXPAND) self.sizer2.Add(button7, 1, wx.EXPAND) self.sizer2.Add(button8, 1, wx.EXPAND) self.Bind(wx.EVT_BUTTON, self.OnExit, id=ID_EXIT) self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.splitter,1,wx.EXPAND) self.sizer.Add(self.sizer2,0,wx.EXPAND) self.SetSizer(self.sizer) size = wx.DisplaySize() self.SetSize(size) self.sb = self.CreateStatusBar() self.sb.SetStatusText(os.getcwd()) self.Center() self.Show(True) def OnExit(self,e): self.Close(True) def OnSize(self, event): size = self.GetSize() self.splitter.SetSashPosition(size.x / 2) self.sb.SetStatusText(os.getcwd()) event.Skip() def OnDoubleClick(self, event): size = self.GetSize() self.splitter.SetSashPosition(size.x / 2) app = wx.App(0) FileHunter(None, -1, 'File Hunter') app.MainLoop()
电子表格
Gnumeric, KSpread, 和 OpenOffice Calc 都是 Unix 上著名的电子表格。下面的例子展示了一个 wxPython 电子表格的例子。
应用有他们自己的生命,教程样例也一样。在升级到 wxPython 2.8.11 之后,我意识到电子表格的样例无法工作了,下面这一行是问题所在:
1
|
toolbar2.AddControl(wx.StaticText(toolbar2, - 1 , ' ' )) |
当然,我们没办法给部件自己添加自己。但是之前的版本却可以接受这样的设置。目前的版本下,这无法工作并报错,可能无法在 Mac 或 Windows 上允许。我本来的目的是想在 combo box 之间多加一些空间,现在没法运行了所以我去掉了线。
除了修复这一 bug 之外,我也对代码进行了整理,使用新的 AddLabelTool() 代替了旧的 AddSimpleTool()。
#!/usr/bin/python # spreadsheet.py from wx.lib import sheet import wx class MySheet(sheet.CSheet): def __init__(self, parent): sheet.CSheet.__init__(self, parent) self.row = self.col = 0 self.SetNumberRows(55) self.SetNumberCols(25) for i in range(55): self.SetRowSize(i, 20) def OnGridSelectCell(self, event): self.row, self.col = event.GetRow(), event.GetCol() control = self.GetParent().GetParent().position value = self.GetColLabelValue(self.col) + self.GetRowLabelValue(self.row) control.SetValue(value) event.Skip() class Newt(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, -1, title, size = (550, 500)) fonts = ['Times New Roman', 'Times', 'Courier', 'Courier New', 'Helvetica', 'Sans', 'verdana', 'utkal', 'aakar', 'Arial'] font_sizes = ['10', '11', '12', '14', '16'] box = wx.BoxSizer(wx.VERTICAL) menuBar = wx.MenuBar() menu1 = wx.Menu() menuBar.Append(menu1, '&File') menu2 = wx.Menu() menuBar.Append(menu2, '&Edit') menu3 = wx.Menu() menuBar.Append(menu3, '&Edit') menu4 = wx.Menu() menuBar.Append(menu4, '&Insert') menu5 = wx.Menu() menuBar.Append(menu5, 'F&ormat') menu6 = wx.Menu() menuBar.Append(menu6, '&Tools') menu7 = wx.Menu() menuBar.Append(menu7, '&Data') menu8 = wx.Menu() menuBar.Append(menu8, '&Help') self.SetMenuBar(menuBar) toolbar1 = wx.ToolBar(self, -1, style= wx.TB_HORIZONTAL) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_new.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_open.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_save.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_cut.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_copy.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_paste.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_delete.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_undo.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_redo.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/incr22.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/decr22.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/chart.xpm')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_exit.png')) toolbar1.Realize() toolbar2 = wx.ToolBar(self, wx.TB_HORIZONTAL | wx.TB_TEXT) self.position = wx.TextCtrl(toolbar2) font = wx.ComboBox(toolbar2, -1, value = 'Times', choices=fonts, size=(100, -1), style=wx.CB_DROPDOWN) font_height = wx.ComboBox(toolbar2, -1, value = '10', choices=font_sizes, size=(50, -1), style=wx.CB_DROPDOWN) toolbar2.AddControl(self.position) toolbar2.AddControl(font) toolbar2.AddControl(font_height) toolbar2.AddSeparator() bold = wx.Bitmap('icons/stock_text_bold.png') toolbar2.AddCheckTool(-1, bold) italic = wx.Bitmap('icons/stock_text_italic.png') toolbar2.AddCheckTool(-1, italic) under = wx.Bitmap('icons/stock_text_underline.png') toolbar2.AddCheckTool(-1, under) toolbar2.AddSeparator() toolbar2.AddLabelTool(-1, '', wx.Bitmap('icons/text_align_left.png')) toolbar2.AddLabelTool(-1, '', wx.Bitmap('icons/text_align_center.png')) toolbar2.AddLabelTool(-1, '', wx.Bitmap('icons/text_align_right.png')) box.Add(toolbar1, border=5) box.Add((5,5) , 0) box.Add(toolbar2) box.Add((5,10) , 0) toolbar2.Realize() self.SetSizer(box) notebook = wx.Notebook(self, -1, style=wx.RIGHT) sheet1 = MySheet(notebook) sheet2 = MySheet(notebook) sheet3 = MySheet(notebook) sheet1.SetFocus() notebook.AddPage(sheet1, 'Sheet1') notebook.AddPage(sheet2, 'Sheet2') notebook.AddPage(sheet3, 'Sheet3') box.Add(notebook, 1, wx.EXPAND) self.CreateStatusBar() self.Centre() self.Show(True) app = wx.App() Newt(None, -1, 'SpreadSheet') app.MainLoop()
大多数代码在构建菜单和工具栏,这是一个相当简单的例子。
class MySheet(sheet.CSheet): def __init__(self, parent): sheet.CSheet.__init__(self, parent) self.row = self.col = 0 self.SetNumberRows(55) self.SetNumberCols(25) for i in range(55): self.SetRowSize(i, 20)
MySheet 类继承自 CSheet 类,它位于 wx.lib 模块,它是一个拥有一些附加功能的 wx.Grid 部件。我们把行大小设置为 20px,这仅仅是为了更美观。
control = self.GetParent().GetParent().position
位置文本部件显示了 grid 部件中被选择的单元格的位置,它位于第二个工具栏的第一个。在 MySheet 类中,我们需要引用位置文本部件,它被定义在 Newt 类中。MySheet 是 notebook 的子代,notebook 是 Newt 的子代,所以我们可以调用两次 GetParent() 方法来获取位置文本部件。
notebook
=
wx.Notebook(
self
,
-
1
, style
=
wx.RIGHT)
上面是一行涉及一个目前版本的 bug(GTK+),右侧和底部弄反了。
播放器
下面的例子是一个典型的视频播放器的骨架。
#!/usr/bin/python # -*- coding: utf-8 -*- import wx class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): self.CreateMenuBar() panel = wx.Panel(self) pnl1 = wx.Panel(self) pnl1.SetBackgroundColour(wx.BLACK) pnl2 = wx.Panel(self) slider1 = wx.Slider(pnl2, value=18, minValue=0, maxValue=1000) pause = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('pause.png')) play = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('play.png')) forw = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('forw.png')) back = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('back.png')) vol = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('volume.png')) slider2 = wx.Slider(pnl2, value=1, minValue=0, maxValue=100, size=(120, -1)) vbox = wx.BoxSizer(wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) hbox1.Add(slider1, proportion=1) hbox2.Add(pause) hbox2.Add(play, flag=wx.RIGHT, border=5) hbox2.Add(forw, flag=wx.LEFT, border=5) hbox2.Add(back) hbox2.Add((-1, -1), proportion=1) hbox2.Add(vol) hbox2.Add(slider2, flag=wx.TOP|wx.LEFT, border=5) vbox.Add(hbox1, flag=wx.EXPAND|wx.BOTTOM, border=10) vbox.Add(hbox2, proportion=1, flag=wx.EXPAND) pnl2.SetSizer(vbox) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(pnl1, proportion=1, flag=wx.EXPAND) sizer.Add(pnl2, flag=wx.EXPAND|wx.BOTTOM|wx.TOP, border=10) self.SetMinSize((350, 300)) self.CreateStatusBar() self.SetSizer(sizer) self.SetSize((350, 200)) self.SetTitle('Player') self.Centre() self.Show(True) def CreateMenuBar(self): menubar = wx.MenuBar() filem = wx.Menu() play = wx.Menu() view = wx.Menu() tools = wx.Menu() favorites = wx.Menu() help = wx.Menu() filem.Append(wx.ID_ANY, '&quit', 'Quit application') menubar.Append(filem, '&File') menubar.Append(play, '&Play') menubar.Append(view, '&View') menubar.Append(tools, '&Tools') menubar.Append(favorites, 'F&avorites') menubar.Append(help, '&Help') self.SetMenuBar(menubar) def main(): ex = wx.App() Example(None) ex.MainLoop() if __name__ == '__main__': main()
为了构建界面,我们使用了 bitmap 按钮、滑块、面板和菜单栏。
pnl1 = wx.Panel(self)
pnl1.SetBackgroundColour(wx.BLACK)
应用的主要区域使用一个黑色背景的 panel。
slider1
=
wx.Slider(pnl2, value
=
18
, minValue
=
0
, maxValue
=
1000
)
滑块 wx.Slider 是为了展示影片当前的进度。
pause
=
wx.BitmapButton(pnl2, bitmap
=
wx.Bitmap(
'pause.png'
))
play
=
wx.BitmapButton(pnl2, bitmap
=
wx.Bitmap(
'play.png'
))
Bitmap 按钮是为了控制影片播放。
self
.SetMinSize((
350
,
300
))
这里,我们设置了播放器为最小尺寸,将窗口大小调整到比它还小就没有什么意义了。
浏览器
如今 IT 世界中最重要的应用就是网络浏览器,我们在脚本中模仿了 Firefox 的界面。
#!/usr/bin/python import wx from wx.lib.buttons import GenBitmapTextButton class Browser(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(450, 400)) panel = wx.Panel(self, -1) panel.SetBackgroundColour('WHITE') menubar = wx.MenuBar() file = wx.Menu() file.Append(1, '&Quit', '') edit = wx.Menu() view = wx.Menu() go = wx.Menu() bookmarks = wx.Menu() tools = wx.Menu() help = wx.Menu() menubar.Append(file, '&File') menubar.Append(edit, '&Edit') menubar.Append(view, '&View') menubar.Append(go, '&Go') menubar.Append(bookmarks, '&Bookmarks') menubar.Append(tools, '&Tools') menubar.Append(help, '&Help') self.SetMenuBar(menubar) vbox = wx.BoxSizer(wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) toolbar1 = wx.Panel(panel, -1, size=(-1, 40)) back = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/back.png'), style=wx.NO_BORDER) forward = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/forward.png'), style=wx.NO_BORDER) refresh = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/refresh.png'), style=wx.NO_BORDER) stop = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/stop.png'), style=wx.NO_BORDER) home = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/home.png'), style=wx.NO_BORDER) address = wx.ComboBox(toolbar1, -1, size=(50, -1)) go = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/go.png'), style=wx.NO_BORDER) text = wx.TextCtrl(toolbar1, -1, size=(150, -1)) hbox1.Add(back) hbox1.Add(forward) hbox1.Add(refresh) hbox1.Add(stop) hbox1.Add(home) hbox1.Add(address, 1, wx.TOP, 4) hbox1.Add(go, 0, wx.TOP | wx.LEFT, 4) hbox1.Add(text, 0, wx.TOP | wx.RIGHT, 4) vbox.Add(toolbar1, 0, wx.EXPAND) line = wx.StaticLine(panel) vbox.Add(line, 0, wx.EXPAND) toolbar2 = wx.Panel(panel, -1, size=(-1, 30)) bookmark1 = wx.BitmapButton(toolbar2, -1, wx.Bitmap('icons/love.png'), style=wx.NO_BORDER) bookmark2 = wx.BitmapButton(toolbar2, -1, wx.Bitmap('icons/books.png'), style=wx.NO_BORDER) bookmark3 = wx.BitmapButton(toolbar2, -1, wx.Bitmap('icons/sound.png'), style=wx.NO_BORDER) hbox2.Add(bookmark1, flag=wx.RIGHT, border=5) hbox2.Add(bookmark2, flag=wx.RIGHT, border=5) hbox2.Add(bookmark3) toolbar2.SetSizer(hbox2) vbox.Add(toolbar2, 0, wx.EXPAND) line = wx.StaticLine(panel) vbox.Add(line, 0, wx.EXPAND) panel.SetSizer(vbox) self.CreateStatusBar() self.Centre() self.Show(True) app = wx.App(0) Browser(None, -1, 'Browser') app.MainLoop()
问题在于,如何创建一个在 Firefox 和 Opera 中都会使用的 combo box (地址栏下拉框) 呢?无法使用 wx.Toolbar 来创建,所以必须做一个变通的方法。
toolbar1 = wx.Panel(panel, -1, size=(-1, 40))
技巧很简单,我们创建了一个 plain 的 wx.Panel。
hbox1 = wx.BoxSizer(wx.HORIZONTAL) ... hbox1.Add(back) hbox1.Add(forward) hbox1.Add(refresh)
我们创建了一个水平的 sizer 并添加了需要的按钮。
hbox1.Add(address, 1, wx.TOP, 4)
然后我们把 combo box 添加到 sizer 中,这种 combo box 被称作地址栏。主要到这个部件是唯一的需要将 proportion 设置为 1 的部件。有必要将其设置为可改变大小的。
采用类似的方式创建第二个菜单栏,使用一条线来分割菜单栏。刚开始我以为应该可以用 panel 的边框来实现,后来经过测试发现不满意。
line = wx.StaticLine(panel)
然后我突然发现了这个,如此简单!
有时,没有合适部件的时候,我们必须创建新的解决办法。但根据直觉,往往我们能很容易找到办法。
本节,我们创建了一些应用骨架。