总目录:wxPython 教程目录 本节内容:wxPython 自定义控件 本节译自:zetcode 上一篇:wxPython 教程 (十二): GDI 下一篇:wxPython 教程 (十四): 提示和技巧
本节讲述 wxPython 自定义控件 。GUI Toolkits 会提供多数常用的部件,比如按钮、文本控件、滚动条、滑块等等。wxPython 也会提供很多控件,但若需要更定制化的控件还是需要开发者自己编写。
自定义控件通过两种方式创建:一种是通过修改或增强现有控件,另一种是我们从零开始直接创建。
超链接控件
第一个例子是创建一个超链接控件,我们基于 wx.lib.stattext.GenStaticText 控件来构建它。
#!/usr/bin/python
import wx
from wx.lib.stattext import GenStaticText
import webbrowser
        
class Link(GenStaticText):
           
    def __init__(self, *args, **kw):
        super(Link, self).__init__(*args, **kw)         
        self.font1 = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, True, 'Verdana')
        self.font2 = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana')
        self.SetFont(self.font2)
        self.SetForegroundColour('#0000ff')
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
        self.Bind(wx.EVT_MOTION, self.OnMouseEvent)
        
    def SetUrl(self, url):
        
        self.url = url
    def OnMouseEvent(self, e):
        
        if e.Moving():
            
            self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
            self.SetFont(self.font1)
        elif e.LeftUp():
            
            webbrowser.open_new(self.url)
        else:
            self.SetCursor(wx.NullCursor)
            self.SetFont(self.font2)
        e.Skip()
class Example(wx.Frame):
           
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        
        self.InitUI()
        
    def InitUI(self):    
        panel = wx.Panel(self)
        lnk = Link(panel, label='ZetCode', pos=(10, 60))
        lnk.SetUrl('http://www.zetcode.com')
        
        motto = GenStaticText(panel, label='Knowledge only matters', pos=(10, 30))
        motto.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana'))
        self.SetSize((220, 150))
        self.SetTitle('A Hyperlink')
        self.Centre()
        self.Show(True)
def main():
    
    ex = wx.App()
    Example(None)
    ex.MainLoop()    
if __name__ == '__main__':
    main()   
超链接控件是基于现有控件改造的。在这个例子中,我们没有绘制任何东西,我们只是使用了一个现成的空间,然后修改了一点点。
from wx.lib.stattext import GenStaticText import webbrowser
这里我们导入了需要的模块。webbrowser 是 python 的标准模块,我们使用它在默认浏览器中打开超链接。
self.SetFont(self.font2)
self.SetForegroundColour('#0000ff')
我们修改了字体和文本的颜色。
if e.Moving():
    
    self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
    self.SetFont(self.font1)
如果鼠标移到链接上方时,我们显示文本下划线,并将鼠标设置为手型。
elif e.LeftUp():
    
    webbrowser.open_new(self.url)
如果点击链接,我们在默认浏览器打开它。
图:超链接控件
烧录控件
这个例子中,我们将从头创建一个控件。我们将在窗口的底部放置一个 wx.Panel,手动绘制整个控件。如果你之前烧录过 CD 或者 DVD,你应该见过类似控件。
为了避免 Windows 系统中的闪烁,我们需要使用双重缓冲(double buffering)。
#!/usr/bin/python
# burning.py
import wx
class Widget(wx.Panel):
    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id, size=(-1, 30), style=wx.SUNKEN_BORDER)
        
        self.parent = parent
        self.font = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
            wx.FONTWEIGHT_NORMAL, False, 'Courier 10 Pitch')
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
    def OnPaint(self, event):
    
        num = range(75, 700, 75)
        dc = wx.PaintDC(self)
        dc.SetFont(self.font)
        w, h = self.GetSize()
        self.cw = self.parent.GetParent().cw
        step = int(round(w / 10.0))
        j = 0
        till = (w / 750.0) * self.cw
        full = (w / 750.0) * 700
        if self.cw >= 700:
            dc.SetPen(wx.Pen('#FFFFB8')) 
            dc.SetBrush(wx.Brush('#FFFFB8'))
            dc.DrawRectangle(0, 0, full, 30)
            dc.SetPen(wx.Pen('#ffafaf'))
            dc.SetBrush(wx.Brush('#ffafaf'))
            dc.DrawRectangle(full, 0, till-full, 30)
        else: 
            dc.SetPen(wx.Pen('#FFFFB8'))
            dc.SetBrush(wx.Brush('#FFFFB8'))
            dc.DrawRectangle(0, 0, till, 30)
        dc.SetPen(wx.Pen('#5C5142'))
        
        for i in range(step, 10*step, step):
        
            dc.DrawLine(i, 0, i, 6)
            width, height = dc.GetTextExtent(str(num[j]))
            dc.DrawText(str(num[j]), i-width/2, 8)
            j = j + 1
    def OnSize(self, event):
        self.Refresh()
class Burning(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(330, 200))
        self.cw = 75
        panel = wx.Panel(self, -1)
        CenterPanel = wx.Panel(panel, -1)
        self.sld = wx.Slider(CenterPanel, -1, 75, 0, 750, (-1, -1), 
            (150, -1), wx.SL_LABELS)
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
        self.wid = Widget(panel, -1)
        hbox.Add(self.wid, 1, wx.EXPAND)
        hbox2.Add(CenterPanel, 1, wx.EXPAND)
        hbox3.Add(self.sld, 0, wx.TOP, 35)
        CenterPanel.SetSizer(hbox3)
        vbox.Add(hbox2, 1, wx.EXPAND)
        vbox.Add(hbox, 0, wx.EXPAND)
        self.Bind(wx.EVT_SCROLL, self.OnScroll)
        panel.SetSizer(vbox)
        self.sld.SetFocus()
        self.Centre()
        self.Show(True)
    def OnScroll(self, event):
        self.cw = self.sld.GetValue()
        self.wid.Refresh()
app = wx.App()
Burning(None, -1, 'Burning widget')
app.MainLoop()
所有重要的代码都在 Widget 类的 OnPaint() 函数内。这个控件像我们展示了媒介(CD/DVD)的总容量和剩余的空间,通过滑块空间来控制。最小值为 0,最大值为 750。如果值到了 700,我们将颜色编程红色,这提示过度烧录。
w, h = self.GetSize() self.cw = self.parent.GetParent().cw ... till = (w / 750.0) * self.cw full = (w / 750.0) * 700
我们动态的绘制空间。窗口越大,烧录控件越大,反之亦然。这就是为什么我们要先计算 wx.Panel 的尺寸的原因。till 参数决定了要绘制的总大小。这个参数来自于滑块空间,它是整个区域的一部分。full 参数决定了我们要使用红色绘制的那个节点值。注意到,我们使用了浮点数,这样可以达到更高的精度。
实际的绘制包括 3 步,我们绘制了黄/红 和 黄色矩阵。然后绘制竖线,这些竖线将控件划分为多个部分。最终,我们绘制数字,它提示媒介的容量。
def OnSize(self, event):
    self.Refresh()
每次窗口大小改变时,我们刷新控件。
def OnScroll(self, event):
    self.cw = self.sld.GetValue()
    self.wid.Refresh()
当我们移动滑块控件的浮标时,我们得到实际的值,并将它存入 self.cw。这个值被用来绘制烧录控件,我们让控件刷新重绘。
图:烧录控件
CPU 控件
有一些应用会监测系统资源,温度、内存、CPU 消耗等等。如果单纯的使用文字来展示 CPU 54% 也许不那么令人映像深刻,定制化的控件可能会让应用更加的吸引人。下面例子中的控件是我们在系统应用中经常看见的。同上,为了避免 Windows 平台下的闪烁,我们使用双重缓冲。
#!/usr/bin/python
# cpu.py
import wx
class CPU(wx.Panel):
    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id, size=(80, 110))
        self.parent = parent
        self.SetBackgroundColour('#000000')
        self.Bind(wx.EVT_PAINT, self.OnPaint)
    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.SetDeviceOrigin(0, 100)
        dc.SetAxisOrientation(True, True)
        pos = self.parent.GetParent().GetParent().sel
        rect = pos / 5
        for i in range(1, 21):
            if i > rect:
                dc.SetBrush(wx.Brush('#075100'))
                dc.DrawRectangle(10, i*4, 30, 5)
                dc.DrawRectangle(41, i*4, 30, 5)
            else:
                dc.SetBrush(wx.Brush('#36ff27'))
                dc.DrawRectangle(10, i*4, 30, 5)
                dc.DrawRectangle(41, i*4, 30, 5)
class CPUWidget(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(190, 140))
        self.sel = 0
        panel = wx.Panel(self, -1)
        centerPanel = wx.Panel(panel, -1)
        self.cpu = CPU(centerPanel, -1)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        self.slider = wx.Slider(panel, -1, self.sel, 0, 100, (-1, -1), (25, 90), 
		wx.VERTICAL | wx.SL_LABELS | wx.SL_INVERSE)
        self.slider.SetFocus()
        hbox.Add(centerPanel, 0,  wx.LEFT | wx.TOP, 20)
        hbox.Add(self.slider, 0, wx.LEFT | wx.TOP, 23)
        self.Bind(wx.EVT_SCROLL, self.OnScroll)
        panel.SetSizer(hbox)
        self.Centre()
        self.Show(True)
    def OnScroll(self, event):
        self.sel = event.GetInt()
        self.cpu.Refresh()
app = wx.App()
CPUWidget(None, -1, 'cpu')
app.MainLoop()
这个控件非常简单,我们创建了一个洪泽 panel。然后我们绘制了一些矩阵,矩阵的颜色取决于滑块控件的值,可以使深绿或者亮绿。
dc.SetDeviceOrigin(0, 100) dc.SetAxisOrientation(True, True)
这里我们将坐标系改为笛卡尔坐标系,这将让绘制更加直观。
pos = self.parent.GetParent().GetParent().sel rect = pos / 5
这里我们获取了 sizer 的值。一列有20个矩形,滑块有 100 个数字。rect 参数将滑块值转变为需要用亮绿色绘制的矩形数目。
for i in range(1, 21):
    if i > rect:
        dc.SetBrush(wx.Brush('#075100'))
        dc.DrawRectangle(10, i*4, 30, 5)
        dc.DrawRectangle(41, i*4, 30, 5)
    else:
        dc.SetBrush(wx.Brush('#36ff27'))
        dc.DrawRectangle(10, i*4, 30, 5)
        dc.DrawRectangle(41, i*4, 30, 5)
这里我们绘制了 40 个矩形,每列 20 个。如果矩形数目大于滑块值变换后的值,我们将它设置为暗绿色,否则用亮绿色。

图:CPU 控件
本节中,我们创建了 wxPython 自定义控件 。