wxPython 教程(八) wxPython 高级 widgets

转自:http://thisis.yorven.site/blog/index.php/2017/10/09/wxpython-jiaocheng-gaoji-widgets/

动态语言使用简单,非常便于原型设计、内部开发以及学习编程。如果需要快速的解决方案或者在短期内就会更改的应用,使用动态语言更优于编译语言。相反,如果我们开发资源密集型应用、游戏以及高质量多媒体应用,那么使用 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 构造函数如下:

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,默认为空。

#!/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() 可以清空条目。

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

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

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

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

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。

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

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

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

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

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

清空 listbox 比较简单,我们简单调用 Clear() 即可。
wx.ListBox 部件

wx.html.HtmlWindow 部件

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

#!/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()

wx.ListBox 部件

Help 窗口

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

#!/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 键,即可在右侧显示帮助窗口,点击关闭按钮可以关闭帮助窗口。

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

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

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 的一部分。

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

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

self.panelLeft.SetFocus()

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

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

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

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

<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>

<li><a href="#basic">Basic statistics</a></li>
...
<a name="basic">

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

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

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

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 的基本功能。

#!/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()
self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT)

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

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。

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。

#!/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()

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

from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin

这里我们导入了 mixin。

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,这称作多重继承。最后一列会自动改变宽度来占用剩余的空间。
自适应宽度例子
在下面的例子中,我们展示如何创建可排序的列。当点击列头时,对应的内容将被排序。

#!/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 的例子。

ColumnSorterMixin.__init__(self, len(actresses))

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

self.itemDataMap = actresses

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

def GetListCtrl(self):
return self

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

self.list.SetItemData(index, key)

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

Reader

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

#!/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 分别位于左侧和右侧。

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

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

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

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

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

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

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

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

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

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

CheckListCtrl

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

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

#!/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()

Repository

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 个不同的类。

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 高级 widgets

https://www.laoyuyu.me/2019/02/14/wxpython/wx_python_8/

作者

AriaLyy

发布于

2019-02-14

许可协议

CC BY-NC-SA 4.0

评论