wxPython 教程 (八): wxPython 高级 widgets

总目录:wxPython 教程目录 
本节内容:wxPython 高级 widgets
本节译自:zetcode
上一篇:wxPython 教程 (七): 部件 
下一篇:wxPython 教程 (九): 拖拽

动态语言使用简单,非常便于原型设计、内部开发以及学习编程。如果需要快速的解决方案或者在短期内就会更改的应用,使用动态语言更优于编译语言。相反,如果我们开发资源密集型应用、游戏以及高质量多媒体应用,那么使用 C 是最正确的选择。
本节,主要讲解多个 wxPython 高级 widgets 。wxPython 有很多有名的高级 widgets, 比如 树形 widget、HTML 窗口、 网格 widget、列表框 widget 甚至具有高级样式编排能力的编辑器 widget。

wx.ListBox

wx.ListBox 用来展示和操作一组列表项,正如其名所示,它有一个矩形框,里面有一组字符串。我们可以用它来展示一列 MP3 文件、书名、大项目的模块名或者一堆朋友的名字。 wx.ListBox 可以有两种形式,一种单选一种多选,单选是默认形式。wx.ListBox 有两个可触发事件,一个是 wx.EVT_COMMAND_LISTBOX_SELECTED 事件,当我们选择列表中的一个字符串时会触发这一事件;另一个是 wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED,当我们双击一个条目时会触发该事件。wx.ListBox 中条目的个数在 GTK 平台是有限制的,根据文档,大概是 2000 左右,需要滚动时会自动展示滚动条。

wx.ListBox widget 构造函数如下:

1
2
3
wx.ListBox(wx.Window parent, int id=-1, wx.Point pos=wx.DefaultPosition,
    wx.Size size=wx.DefaultSize, list choices=[], long style=0,
    wx.Validator validator=wx.DefaultValidator, string name=wx.ListBoxNameStr)

其中有一个 choices 选项,可以作为展示的 list,默认为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/python
 
# listbox.py
 
import wx
 
ID_NEW = 1
ID_RENAME = 2
ID_CLEAR = 3
ID_DELETE = 4
 
 
class ListBox(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 220))
 
        panel = wx.Panel(self, -1)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        self.listbox = wx.ListBox(panel, -1)
        hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20)
 
        btnPanel = wx.Panel(panel, -1)
        vbox = wx.BoxSizer(wx.VERTICAL)
        new = wx.Button(btnPanel, ID_NEW, 'New', size=(90, 30))
        ren = wx.Button(btnPanel, ID_RENAME, 'Rename', size=(90, 30))
        dlt = wx.Button(btnPanel, ID_DELETE, 'Delete', size=(90, 30))
        clr = wx.Button(btnPanel, ID_CLEAR, 'Clear', size=(90, 30))
 
        self.Bind(wx.EVT_BUTTON, self.NewItem, id=ID_NEW)
        self.Bind(wx.EVT_BUTTON, self.OnRename, id=ID_RENAME)
        self.Bind(wx.EVT_BUTTON, self.OnDelete, id=ID_DELETE)
        self.Bind(wx.EVT_BUTTON, self.OnClear, id=ID_CLEAR)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename)
 
        vbox.Add((-1, 20))
        vbox.Add(new)
        vbox.Add(ren, 0, wx.TOP, 5)
        vbox.Add(dlt, 0, wx.TOP, 5)
        vbox.Add(clr, 0, wx.TOP, 5)
 
        btnPanel.SetSizer(vbox)
        hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20)
        panel.SetSizer(hbox)
 
        self.Centre()
        self.Show(True)
 
    def NewItem(self, event):
        text = wx.GetTextFromUser('Enter a new item', 'Insert dialog')
        if text != '':
            self.listbox.Append(text)
 
    def OnRename(self, event):
        sel = self.listbox.GetSelection()
        text = self.listbox.GetString(sel)
        renamed = wx.GetTextFromUser('Rename item', 'Rename dialog', text)
        if renamed != '':
            self.listbox.Delete(sel)
            self.listbox.Insert(renamed, sel)
 
 
    def OnDelete(self, event):
        sel = self.listbox.GetSelection()
        if sel != -1:
            self.listbox.Delete(sel)
 
    def OnClear(self, event):
        self.listbox.Clear()
 
 
app = wx.App()
ListBox(None, -1, 'ListBox')
app.MainLoop()

在我们的例子中,有 1 个 listbox 和 4 个 buttons,每个 button 对应一个不同的方法。调用 Append() 方法可以在列表中增加条目, Delete() 可以删除条目, Clear() 可以清空条目。

1
2
self.listbox = wx.ListBox(panel, -1)
hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20)

我们创建了一个空的 wx.ListBox,边框是 20px。

1
self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename)

我们绑定了 使用 wx.EVT_LISTBOX_DCLICK 绑定器将 wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED 事件绑定至 OnRename() 方法。当用户双击特定元素时将弹出一个重命名对话框。

1
2
3
4
def NewItem(self, event):
    text = wx.GetTextFromUser('Enter a new item', 'Insert dialog')
    if text != '':
        self.listbox.Append(text)

点击 New 按钮时,NewItem() 被调用,将展示一个 wx.GetTextFromUser 对话框,该对话框返回用户的输入。如果输入非空,我们使用 Append() 将其添加至 listbox。

1
2
3
4
def OnDelete(self, event):
    sel = self.listbox.GetSelection()
    if sel != -1:
        self.listbox.Delete(sel)

删除一个 item 分两步。首先使用 GetSelection() 获取被选择条目的 index,然后将 index 作为参数传入 Delete() 方法删除该元素。

1
2
self.listbox.Delete(sel)
self.listbox.Insert(renamed, sel)

wx.ListBox 部件没有 Rename() 方法,所以我们删除之前选择的字符串,然后在原来的地方插入一个新的字符串。

1
2
def OnClear(self, event):
    self.listbox.Clear()

清空 listbox 比较简单,我们简单调用 Clear() 即可。

tutorial wxpython-jiaocheng

图:wx.ListBox 部件

wx.html.HtmlWindow 部件

wx.html.HtmlWindow 部件可以展示 HTML 页面,它不是一个完整成熟的浏览器,但我们可以使用它做很多有意思的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/python
 
import wx
import wx.html as html
 
ID_CLOSE = 1
 
page = \'<html><body bgcolor="#8e8e95">\
<table cellspacing="5" border="0" width="250"> \
<tr width="200" align="left"> \
<td bgcolor="#e7e7e7">  Maximum</td> \
<td bgcolor="#aaaaaa">  <b>9000</b></td> \
</tr> \
<tr align="left"> \
<td bgcolor="#e7e7e7">  Mean</td> \
<td bgcolor="#aaaaaa">  <b>6076</b></td>\
</tr> \
<tr align="left"> \
<td bgcolor="#e7e7e7">  Minimum</td> \
<td bgcolor="#aaaaaa">  <b>3800</b></td> \
</tr> \
<tr align="left"> \
<td bgcolor="#e7e7e7">  Median</td> \
<td bgcolor="#aaaaaa">  <b>6000</b></td> \
</tr> \
<tr align="left"> \
<td bgcolor="#e7e7e7">  Standard Deviation</td> \
<td bgcolor="#aaaaaa">  <b>6076</b></td> \
</tr> \
</body></table>\
</html>\'
 
 
class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(400, 290))
 
        panel = wx.Panel(self, -1)
 
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        htmlwin = html.HtmlWindow(panel, -1, style=wx.NO_BORDER)
        htmlwin.SetBackgroundColour(wx.RED)
        htmlwin.SetStandardFonts()
        htmlwin.SetPage(page)
 
        vbox.Add((-1, 10), 0)
        vbox.Add(htmlwin, 1, wx.EXPAND | wx.ALL, 9)
 
        bitmap = wx.StaticBitmap(panel, -1, wx.Bitmap('images/newt.png'))
        hbox.Add(bitmap, 1, wx.LEFT | wx.BOTTOM | wx.TOP, 10)
        buttonOk = wx.Button(panel, ID_CLOSE, 'Ok')
 
        self.Bind(wx.EVT_BUTTON, self.OnClose, id=ID_CLOSE)
 
        hbox.Add((100, -1), 1, wx.EXPAND | wx.ALIGN_RIGHT)
        hbox.Add(buttonOk, flag=wx.TOP | wx.BOTTOM | wx.RIGHT, border=10)
        vbox.Add(hbox, 0, wx.EXPAND)
 
        panel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
    def OnClose(self, event):
        self.Close()
 
app = wx.App(0)
MyFrame(None, -1, 'Basic Statistics')
app.MainLoop()

tutorial wxpython-jiaocheng

图:wx.ListBox 部件

Help 窗口

我们可以使用 wx.html.HtmlWindow 提供帮助窗口,该窗口可以是一个独立的 window 或者是应用的一部分。下面的例子展示了后者的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/python
 
# helpwindow.py
 
import wx
import wx.html as html
 
class HelpWindow(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(570, 400))
 
        toolbar = self.CreateToolBar()
        toolbar.AddLabelTool(1, 'Exit', wx.Bitmap('icons/exit.png'))
        toolbar.AddLabelTool(2, 'Help', wx.Bitmap('icons/help.png'))
        toolbar.Realize()
 
        self.splitter = wx.SplitterWindow(self, -1)
        self.panelLeft = wx.Panel(self.splitter, -1, style=wx.BORDER_SUNKEN)
 
        self.panelRight = wx.Panel(self.splitter, -1)
        vbox2 = wx.BoxSizer(wx.VERTICAL)
        header = wx.Panel(self.panelRight, -1, size=(-1, 20))
        header.SetBackgroundColour('#6f6a59')
        header.SetForegroundColour('WHITE')
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        st = wx.StaticText(header, -1, 'Help', (5, 5))
        font = st.GetFont()
        font.SetPointSize(9)
        st.SetFont(font)
        hbox.Add(st, 1, wx.TOP | wx.BOTTOM | wx.LEFT, 5)
 
        close = wx.BitmapButton(header, -1, wx.Bitmap('icons/fileclose.png', wx.BITMAP_TYPE_PNG),
        style=wx.NO_BORDER)
        close.SetBackgroundColour('#6f6a59')
        hbox.Add(close, 0)
        header.SetSizer(hbox)
 
        vbox2.Add(header, 0, wx.EXPAND)
 
        help = html.HtmlWindow(self.panelRight, -1, style=wx.NO_BORDER)
        help.LoadPage('help.html')
        vbox2.Add(help, 1, wx.EXPAND)
        self.panelRight.SetSizer(vbox2)
        self.panelLeft.SetFocus()
 
        self.splitter.SplitVertically(self.panelLeft, self.panelRight)
        self.splitter.Unsplit()
 
        self.Bind(wx.EVT_BUTTON, self.CloseHelp, id=close.GetId())
        self.Bind(wx.EVT_TOOL, self.OnClose, id=1)
        self.Bind(wx.EVT_TOOL, self.OnHelp, id=2)
 
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
 
        self.CreateStatusBar()
 
        self.Centre()
        self.Show(True)
 
    def OnClose(self, event):
        self.Close()
 
    def OnHelp(self, event):
        self.splitter.SplitVertically(self.panelLeft, self.panelRight)
        self.panelLeft.SetFocus()
 
    def CloseHelp(self, event):
        self.splitter.Unsplit()
        self.panelLeft.SetFocus()
 
    def OnKeyPressed(self, event):
        keycode = event.GetKeyCode()
        if keycode == wx.WXK_F1:
            self.splitter.SplitVertically(self.panelLeft, self.panelRight)
            self.panelLeft.SetFocus()
 
 
app = wx.App()
HelpWindow(None, -1, 'HelpWindow')
app.MainLoop()

开始时,帮助窗口是隐藏的,点击工具栏的帮助页面或者按 F1 键,即可在右侧显示帮助窗口,点击关闭按钮可以关闭帮助窗口。

1
2
self.splitter.SplitVertically(self.panelLeft, self.panelRight)
self.splitter.Unsplit()

我们竖直分割出一个 panel, 然后调用 Unsplit() 方法,该方法会默认隐藏右侧或底部的窗格。
我们将右侧的 panel 划分为 2 个部分,即 header 和 body。头部包含静态文本和一个 bitmap 按钮, body 部分则放置了一个 wx.html.Window。

1
2
3
close = wx.BitmapButton(header, -1, wx.Bitmap('icons/fileclose.png', wx.BITMAP_TYPE_PNG),
    style=wx.NO_BORDER)
close.SetBackgroundColour('#6f6a59')

该 bitmap 按钮的 style 被设置为 wx.NO_BORDER,背景色被设置为 header panel 的颜色,这样可以使得该按钮看起来属于 header 的一部分。

1
2
help = html.HtmlWindow(self.panelRight, -1, style=wx.NO_BORDER)
help.LoadPage('help.html')

在右侧,我们新建了一个 wx.html.HtmlWindow 部件,HTML 代码被放置在单独的文件中,使用 LoadPage() 方法获取 HTML 代码。

1
self.panelLeft.SetFocus()

我们让左侧面板获取焦点,为了能让键盘控制窗口(F1 打开帮助窗口),它必须拥有焦点。

1
2
3
def OnHelp(self, event):
    self.splitter.SplitVertically(self.panelLeft, self.panelRight)
    self.panelLeft.SetFocus()

调用 OnHelp() 可以竖直分出两个 panel,从而显示出 帮助窗口,别忘了让左侧的 panel 聚焦,因为分割窗口会让它失去初始的焦点。

下面是我们的应用中载入的 html 页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<html>
 
<body bgcolor="#ababab">
<h4>Table of Contents</h4>
 
<ul>
<li><a href="#basic">Basic statistics</a></li>
<li><a href="#advanced">Advanced statistics</a></li>
<li><a href="#intro">Introducing Newt</a></li>
<li><a href="#charts">Working with charts</a></li>
<li><a href="#pred">Predicting values</a></li>
<li><a href="#neural">Neural networks</a></li>
<li><a href="#glos">Glossary</a></li>
</ul>
 
<p>
<a name="basic">
<h6>Basic Statistics</h6>
Overview of elementary concepts in statistics.
Variables. Correlation. Measurement scales. Statistical significance.
Distributions. Normality assumption.
</a>
</p>
 
<p>
<a name="advanced">
<h6>Advanced Statistics</h6>
Overview of advanced concepts in statistics. Anova. Linear regression.
Estimation and  hypothesis testing.
Error terms.
</a>
</p>
 
<p>
<a name="intro">
<h6>Introducing Newt</h6>
Introducing the basic functionality of the Newt application. Creating sheets.
Charts. Menus and Toolbars. Importing data. Saving data in various formats.
Exporting data. Shortcuts. List of methods.
</a>
</p>
 
<p>
<a name="charts">
<h6>Charts</h6>
Working with charts. 2D charts. 3D charts. Bar, line, box, pie, range charts.
Scatterplots. Histograms.
</a>
</p>
 
<p>
<a name="pred">
<h6>Predicting values</h6>
Time series and forecasting. Trend Analysis. Seasonality. Moving averages.
Univariate methods. Multivariate methods. Holt-Winters smoothing.
Exponential smoothing. ARIMA. Fourier analysis.
</a>
</p>
 
<p>
<a name="neural">
<h6>Neural networks</h6>
Overview of neural networks. Biology behind neural networks.
Basic artificial Model. Training. Preprocessing. Postprocessing.
Types of neural networks.
</a>
</p>
 
<p>
<a name="glos">
<h6>Glossary</h6>
Terms and definitions in statistics.
</a>
</p>
 
</body>
</html>
1
2
3
<li><a href="#basic">Basic statistics</a></li>
...
<a name="basic">

上面的语句,我通常会写成

1
<div id="basic">...</div>

,但 wx.html.HtmlWindow 仅支持标准 HTML 标记的一部分子集,需要注意。

tutorial wxpython-jiaocheng

图:帮助窗口

wx.ListCtrl 部件

wx.ListCtrl 是一列条目的图形展示部件。 wx.ListBox 尽可以展示一列内容,而 wx.ListCtrl 则可以展示多列。wx.ListCtrl 是一个非常常见和有用的窗口部件。比如一个文件管理器使用 wx.ListCtrl 可以展示文件系统的文件夹和文件,一个 CD 刻录软件使用该部件展示将被刻录到 CD 的文件。

wx.ListCtrl 有三种不同的使用格式:列表视图、报告视图和图标视图,分别通过 style 参数:wx.LC_REPORT、wx.LC_LIST 和 wx.LC_ICON 来控制。

详尽的 style 包括:

  • wx.LC_LIST
  • wx.LC_REPORT
  • wx.LC_VIRTUAL
  • wx.LC_ICON
  • wx.LC_SMALL_ICON
  • wx.LC_ALIGN_LEFT
  • wx.LC_EDIT_LABELS
  • wx.LC_NO_HEADER
  • wx.LC_SORT_ASCENDING
  • wx.LC_SORT_DESCENDING
  • wx.LC_HRULES
  • wx.LC_VRULES

简单例子

第一个例子我们首先减少了 wx.ListCtrl 的基本功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/python
 
# actresses.py
 
import wx
import sys
 
packages = [('jessica alba', 'pomona', '1981'), ('sigourney weaver', 'new york', '1949'),
    ('angelina jolie', 'los angeles', '1975'), ('natalie portman', 'jerusalem', '1981'),
    ('rachel weiss', 'london', '1971'), ('scarlett johansson', 'new york', '1984' )]
 
 
 
class Actresses(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(380, 230))
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        panel = wx.Panel(self, -1)
 
        self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT)
        self.list.InsertColumn(0, 'name', width=140)
        self.list.InsertColumn(1, 'place', width=130)
        self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)
        for i in packages:
            index = self.list.InsertStringItem(sys.maxint, i[0])
            self.list.SetStringItem(index, 1, i[1])
            self.list.SetStringItem(index, 2, i[2])
 
        hbox.Add(self.list, 1, wx.EXPAND)
        panel.SetSizer(hbox)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
Actresses(None, -1, 'actresses')
app.MainLoop()
1
self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT)

我们使用 wx.LC_REPORT style 新建了一个 wx.ListCtrl。

1
2
3
self.list.InsertColumn(0, 'name', width=140)
self.list.InsertColumn(1, 'place', width=130)
self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)

上面的代码插入了 3 列,可以单独控制每一列的宽度和格式,默认的格式是 wx.LIST_FORMAT_LEFT。

1
2
3
4
for i in packages:
    index = self.list.InsertStringItem(sys.maxint, i[0])
    self.list.SetStringItem(index, 1, i[1])
    self.list.SetStringItem(index, 2, i[2])

我们使用两种方法将数据插入到 wx.ListCtrl 中去。对每一行,首先调用 InsertStringItem() 方法,第一个参数为行号,使用 sys.maxint 可以保证每次调用时插入的行在上次插入行之后。该方法返回行的索引值。通过 SetStringItem() 方法可以在当前行的后续列中插入数据。

Mixins

Mixins 是可以增强 wx.ListCtrl 功能的 class。Mixin 类也叫做 helper 类,位于 wx.lib.mixins.listctrl 模块。编码人员必须继承这些类才可使用它们。
2.8.1.1 版本中,有 5 个可用的 mixins:

  • wx.ColumnSorterMixin
  • wx.ListCtrlAutoWidthMixin
  • wx.ListCtrlSelectionManagerMix
  • wx.TextEditMixin
  • wx.CheckListCtrlMixin

wx.ColumnSorterMixin 可以在 report 试图中允许排序, wx.ListCtrlAutoWidthMixin 可以自动的调整 wx.ListCtrl 最后一列的宽度。默认情况下,最后一列不会占用剩余的空间,参考之前的例子。 wx.ListCtrlSelectionManagerMix 定义了独立于平台的选择策略。wx.TextEditMixin 可以允许文本编辑。wx.CheckListCtrlMixin 为每一行添加一个选择框,这样我们可以进行更多的操作。

下面的代码展示如何使用 ListCtrlAutoWidthMixin。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/python
 
# autowidth.py
 
import wx
import sys
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
 
actresses = [('jessica alba', 'pomona', '1981'), ('sigourney weaver', 'new york', '1949'),
    ('angelina jolie', 'los angeles', '1975'), ('natalie portman', 'jerusalem', '1981'),
    ('rachel weiss', 'london', '1971'), ('scarlett johansson', 'new york', '1984' )]
 
 
class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
        ListCtrlAutoWidthMixin.__init__(self)
 
 
class Actresses(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(380, 230))
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        panel = wx.Panel(self, -1)
 
        self.list = AutoWidthListCtrl(panel)
        self.list.InsertColumn(0, 'name', width=140)
        self.list.InsertColumn(1, 'place', width=130)
        self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)
 
        for i in actresses:
            index = self.list.InsertStringItem(sys.maxint, i[0])
            self.list.SetStringItem(index, 1, i[1])
            self.list.SetStringItem(index, 2, i[2])
 
        hbox.Add(self.list, 1, wx.EXPAND)
        panel.SetSizer(hbox)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
Actresses(None, -1, 'actresses')
app.MainLoop()

我们对前面的例子稍微修改了下。

1
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin

这里我们导入了 mixin。

1
2
3
4
class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
        ListCtrlAutoWidthMixin.__init__(self)

我们创建了一个新的 AutoWidthListCtrl 类,这个类继承自 wx.ListCtrl 和 ListCtrlAutoWidthMixin,这称作多重继承。最后一列会自动改变宽度来占用剩余的空间。

tutorial wxpython-jiaocheng

图:自适应宽度例子

在下面的例子中,我们展示如何创建可排序的列。当点击列头时,对应的内容将被排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/python
 
# sorted.py
 
import wx
import sys
from wx.lib.mixins.listctrl import ColumnSorterMixin
 
actresses = {
1 : ('jessica alba', 'pomona', '1981'),
2 : ('sigourney weaver', 'new york', '1949'),
3 : ('angelina jolie', 'los angeles', '1975'),
4 : ('natalie portman', 'jerusalem', '1981'),
5 : ('rachel weiss', 'london', '1971'),
6 : ('scarlett johansson', 'new york', '1984')
}
 
 
class SortedListCtrl(wx.ListCtrl, ColumnSorterMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
        ColumnSorterMixin.__init__(self, len(actresses))
        self.itemDataMap = actresses
 
    def GetListCtrl(self):
        return self
 
class Actresses(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(380, 230))
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        panel = wx.Panel(self, -1)
 
        self.list = SortedListCtrl(panel)
        self.list.InsertColumn(0, 'name', width=140)
        self.list.InsertColumn(1, 'place', width=130)
        self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)
 
        items = actresses.items()
 
        for key, data in items:
            index = self.list.InsertStringItem(sys.maxint, data[0])
            self.list.SetStringItem(index, 1, data[1])
            self.list.SetStringItem(index, 2, data[2])
            self.list.SetItemData(index, key)
 
        hbox.Add(self.list, 1, wx.EXPAND)
        panel.SetSizer(hbox)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
Actresses(None, -1, 'actresses')
app.MainLoop()

我们仍然使用 actresses 的例子。

1
ColumnSorterMixin.__init__(self, len(actresses))

ColumnSorterMixin 接受一个参数,即要排序的列的个数。

1
self.itemDataMap = actresses

必须将我们的数据匹配到 itemDataMap 属性中,且数据类型为字典。

1
2
def GetListCtrl(self):
    return self

必须新建一个 GetListCtrl() 方法,它会返回一个将被排序的 wx.ListCtrl 部件。

1
self.list.SetItemData(index, key)

还需要使用 SetItemData() 方法将每一行与一个 index 关联起来。

Reader

这是一个复杂的例子,使用 report view 展示两个 list control。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/python
 
# reader.py
 
 
import wx
 
articles = [['Mozilla rocks', 'The year of the Mozilla', 'Earth on Fire'],
            ['Gnome pretty, Gnome Slow', 'Gnome, KDE, Icewm, XFCE', 'Where is Gnome heading?'],
            ['Java number one language', 'Compiled languages, intrepreted Languages', 'Java on Desktop?']]
 
 
 
class ListCtrlLeft(wx.ListCtrl):
    def __init__(self, parent, id):
        wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.LC_HRULES |
        wx.LC_NO_HEADER | wx.LC_SINGLE_SEL)
        images = ['icons/java.png', 'icons/gnome.png', 'icons/mozilla.png']
 
        self.parent = parent
 
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelect)
 
        self.il = wx.ImageList(32, 32)
        for i in images:
            self.il.Add(wx.Bitmap(i))
 
        self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
        self.InsertColumn(0, '')
 
        for i in range(3):
            self.InsertStringItem(0, '')
            self.SetItemImage(0, i)
 
    def OnSize(self, event):
        size = self.parent.GetSize()
        self.SetColumnWidth(0, size.x-5)
        event.Skip()
 
    def OnSelect(self, event):
        window = self.parent.GetGrandParent().FindWindowByName('ListControlOnRight')
        index = event.GetIndex()
        window.LoadData(index)
 
    def OnDeSelect(self, event):
        index = event.GetIndex()
        self.SetItemBackgroundColour(index, 'WHITE')
 
    def OnFocus(self, event):
        self.SetItemBackgroundColour(0, 'red')
 
class ListCtrlRight(wx.ListCtrl):
    def __init__(self, parent, id):
        wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.LC_HRULES |
        wx.LC_NO_HEADER | wx.LC_SINGLE_SEL)
 
        self.parent = parent
 
        self.Bind(wx.EVT_SIZE, self.OnSize)
 
        self.InsertColumn(0, '')
 
 
    def OnSize(self, event):
        size = self.parent.GetSize()
        self.SetColumnWidth(0, size.x-5)
        event.Skip()
 
    def LoadData(self, index):
        self.DeleteAllItems()
        for i in range(3):
            self.InsertStringItem(0, articles[index][i])
 
 
class Reader(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE|wx.SP_NOBORDER)
 
        vbox1 = wx.BoxSizer(wx.VERTICAL)
        panel1 = wx.Panel(splitter, -1)
        panel11 = wx.Panel(panel1, -1, size=(-1, 40))
        panel11.SetBackgroundColour('#53728c')
        st1 = wx.StaticText(panel11, -1, 'Feeds', (5, 5))
        st1.SetForegroundColour('WHITE')
 
        panel12 = wx.Panel(panel1, -1, style=wx.BORDER_SUNKEN)
        vbox = wx.BoxSizer(wx.VERTICAL)
        list1 = ListCtrlLeft(panel12, -1)
 
        vbox.Add(list1, 1, wx.EXPAND)
        panel12.SetSizer(vbox)
        panel12.SetBackgroundColour('WHITE')
 
 
        vbox1.Add(panel11, 0, wx.EXPAND)
        vbox1.Add(panel12, 1, wx.EXPAND)
 
        panel1.SetSizer(vbox1)
 
        vbox2 = wx.BoxSizer(wx.VERTICAL)
        panel2 = wx.Panel(splitter, -1)
        panel21 = wx.Panel(panel2, -1, size=(-1, 40), style=wx.NO_BORDER)
        st2 = wx.StaticText(panel21, -1, 'Articles', (5, 5))
        st2.SetForegroundColour('WHITE')
 
        panel21.SetBackgroundColour('#53728c')
        panel22 = wx.Panel(panel2, -1, style=wx.BORDER_RAISED)
        vbox3 = wx.BoxSizer(wx.VERTICAL)
        list2 = ListCtrlRight(panel22, -1)
        list2.SetName('ListControlOnRight')
        vbox3.Add(list2, 1, wx.EXPAND)
        panel22.SetSizer(vbox3)
 
 
        panel22.SetBackgroundColour('WHITE')
        vbox2.Add(panel21, 0, wx.EXPAND)
        vbox2.Add(panel22, 1, wx.EXPAND)
 
        panel2.SetSizer(vbox2)
 
        toolbar = self.CreateToolBar()
        toolbar.AddLabelTool(1, 'Exit', wx.Bitmap('icons/stock_exit.png'))
        toolbar.Realize()
 
        self.Bind(wx.EVT_TOOL, self.ExitApp, id=1)
 
        hbox.Add(splitter, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
        self.SetSizer(hbox)
        self.CreateStatusBar()
        splitter.SplitVertically(panel1, panel2)
        self.Centre()
        self.Show(True)
 
 
    def ExitApp(self, event):
        self.Close()
 
 
app = wx.App()
Reader(None, -1, 'Reader')
app.MainLoop()

上面的例子展示了一个 report 试图的 wx.ListCtrl。没有 headers,我们需要新建自己的 headers。该应用中有两个 wx.ListCtrl 分别位于左侧和右侧。

1
2
3
splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE|wx.SP_NOBORDER)
...
splitter.SplitVertically(panel1, panel2)

splitter 将主窗口竖直划分为两个部分,这两个 panel 各自又有两个 panel,分别作 Feeds 和 Articles 的 headers,剩余的空间则作为 listctrl 的位置。

1
2
list2 = ListCtrlRight(panel22, -1)
list2.SetName('ListControlOnRight')

我们给 ListCtrlRight 一个名字“ListControlOnRight”,因为后续我们需要两个部件的交互。

1
2
3
4
def OnSelect(self, event):
    window = self.parent.GetGrandParent().FindWindowByName('ListControlOnRight')
    index = event.GetIndex()
    window.LoadData(index)

上面的代码位于 ListCtrlLeft 类中,我们定位了 ListCtrlRight 部件,并调用它的 LoadData() 方法。

1
2
3
4
def LoadData(self, index):
    self.DeleteAllItems()
    for i in range(3):
        self.InsertStringItem(0, articles[index][i])

LoadData() 方法首先清除所有的条目,然后从全局定义的文章列表中插入文章名,index 同时也被传入。

1
2
3
4
def OnSize(self, event):
    size = self.parent.GetSize()
    self.SetColumnWidth(0, size.x-5)
    event.Skip()

两个 wx.ListCtrl 都只有一列,这里我们让列宽度与父部件的宽度一致,否则界面将不太好看为什么要减去 5px 呢?这是一个神奇数字,如果我们减 5px,水平的滚动条就不会出现。在其他平台上,该数字可能会有变化。

tutorial wxpython-jiaocheng

图:Reader

CheckListCtrl

应用中经常会看到在一个列表部件中出现单选框,比如打包应用 Synaptic 或者 KYUM。

从编程者的思维出发,这些单选框只是简单的图片,有两种不同的状态:选择、未选择,对应两种不同的图片。我们无需亲自去实现上述功能,这已经在 CheckListCtrlMixin 中被实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/python
 
# repository.py
 
import wx
import sys
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
 
packages = [('abiword', '5.8M', 'base'), ('adie', '145k', 'base'),
    ('airsnort', '71k', 'base'), ('ara', '717k', 'base'), ('arc', '139k', 'base'),
    ('asc', '5.8M', 'base'), ('ascii', '74k', 'base'), ('ash', '74k', 'base')]
 
class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
        CheckListCtrlMixin.__init__(self)
        ListCtrlAutoWidthMixin.__init__(self)
 
 
class Repository(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(450, 400))
 
        panel = wx.Panel(self, -1)
 
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        leftPanel = wx.Panel(panel, -1)
        rightPanel = wx.Panel(panel, -1)
 
        self.log = wx.TextCtrl(rightPanel, -1, style=wx.TE_MULTILINE)
        self.list = CheckListCtrl(rightPanel)
        self.list.InsertColumn(0, 'Package', width=140)
        self.list.InsertColumn(1, 'Size')
        self.list.InsertColumn(2, 'Repository')
 
        for i in packages:
            index = self.list.InsertStringItem(sys.maxint, i[0])
            self.list.SetStringItem(index, 1, i[1])
            self.list.SetStringItem(index, 2, i[2])
 
        vbox2 = wx.BoxSizer(wx.VERTICAL)
 
        sel = wx.Button(leftPanel, -1, 'Select All', size=(100, -1))
        des = wx.Button(leftPanel, -1, 'Deselect All', size=(100, -1))
        apply = wx.Button(leftPanel, -1, 'Apply', size=(100, -1))
 
 
        self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=sel.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=des.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnApply, id=apply.GetId())
 
        vbox2.Add(sel, 0, wx.TOP, 5)
        vbox2.Add(des)
        vbox2.Add(apply)
 
        leftPanel.SetSizer(vbox2)
 
        vbox.Add(self.list, 1, wx.EXPAND | wx.TOP, 3)
        vbox.Add((-1, 10))
        vbox.Add(self.log, 0.5, wx.EXPAND)
        vbox.Add((-1, 10))
 
        rightPanel.SetSizer(vbox)
 
        hbox.Add(leftPanel, 0, wx.EXPAND | wx.RIGHT, 5)
        hbox.Add(rightPanel, 1, wx.EXPAND)
        hbox.Add((3, -1))
 
        panel.SetSizer(hbox)
 
        self.Centre()
        self.Show(True)
 
    def OnSelectAll(self, event):
        num = self.list.GetItemCount()
        for i in range(num):
            self.list.CheckItem(i)
 
    def OnDeselectAll(self, event):
        num = self.list.GetItemCount()
        for i in range(num):
            self.list.CheckItem(i, False)
 
    def OnApply(self, event):
        num = self.list.GetItemCount()
        for i in range(num):
            if i == 0: self.log.Clear()
            if self.list.IsChecked(i):
                self.log.AppendText(self.list.GetItemText(i) + '\n')
 
app = wx.App()
Repository(None, -1, 'Repository')
app.MainLoop()

tutorial wxpython-jiaocheng

图:Repository

1
2
3
4
5
class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
        CheckListCtrlMixin.__init__(self)
        ListCtrlAutoWidthMixin.__init__(self)

wxPython 允许多重继承,这里我们继承了 3 个不同的类。

1
2
3
4
def OnSelectAll(self, event):
    num = self.list.GetItemCount()
    for i in range(num):
        self.list.CheckItem(i)

在上面的代码中,我们看到了多重继承的实际操作。对 self.list 对象,我们调用了 2 个来自不同类的方法, GetItemCount() 方法来源于 CheckListCtrl 类,CheckItem() 方法来源于 CheckListCtrlMixin 类。

在本节中,我们讲解了多个 wxPython 的高级部件。


上一节:wxPython 教程 (七): 部件                           下一节:wxPython 教程 (九): 拖拽

《wxPython 教程 (八): wxPython 高级 widgets》有2个想法

  1. 老师您好,wx.ListCtrl 部件介绍内容的简单例子的代码中,第21行运行时报错:AttributeError: module ‘sys’ has no attribute ‘maxint’。网查资料sys.maxint以被弃用,但没找到能代替解决的方法。往能指教释疑,万分感谢!

  2. 您好,上面反映咨询的问题已自行找到方法解决,sys.maxint用self.list.GetItemCount ( )代替,4.0.4版本中运行代码提示以InsertItem( )和SetItem ( )代替 InsertStringItem ( ) 和SetStringItem ( )。教程有点老了,但仍然再次感谢您的分享,让我者业余小白有机会比较系统的学习,谢谢!

化蝶进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注