wxpython入门第九步(自定义组件)

创建自定义组件

工具包通常只提供最常见的小部件,如按钮、文本小部件、滚动条、滑块等,没有一个工具包能提供所有可能的小部件。wxPython有很多组件,更多的组件是由客户程序员创建的,我们可以通过两种方式创建自定义组件:一是修改或增强现有的小部件,二是创建自定义组件。

自定义组件的创建有两种方式:一是修改或增强现有的组件,二是从头开始创建一个自定义组件。

一个超链接组件

第一个例子将创建一个超链接。超链接部件将基于现有的wx.lib.stattext.GenStaticText部件。

#hyperlink.py

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(11, wx.SWISS, wx.NORMAL, wx.BOLD, True, 'Verdana')
        self.font2 = wx.Font(11, 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.Cursor(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)

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)

        st = GenStaticText(panel, label='Go to web site:')
        st.SetFont(wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana'))
        hbox.Add(st, flag=wx.LEFT, border=20)

        link_wid = Link(panel, label='ZetCode')
        link_wid.SetUrl('http://www.zetcode.com')
        hbox.Add(link_wid, flag=wx.LEFT, border=20)
        
        vbox.Add(hbox, flag=wx.TOP, border=30)
        panel.SetSizer(vbox)

        self.SetTitle('A Hyperlink')
        self.Centre()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

image-20201103231151066

这个超链接组件是基于一个现有的组件。在这个例子中,我们不画任何东西,只是使用一个现有的组件,我们对其进行了一些修改。

from wx.lib.stattext import GenStaticText
import webbrowser

在这里,我们导入基础widget,从它那里我们得到了超链接widget和webbrowser模块。webbrowser 模块是一个标准的 Python 模块。我们将用它来打开默认浏览器中的链接。

self.SetFont(self.font2)
self.SetForegroundColour('#0000ff')

创建一个超链接组件背后的想法很简单,我们从一个基本的wx.lib.stattext.GenStaticText小组件类继承。所以我们有了一个文本小组件。然后我们对它进行一些修改。我们改变文字的字体和颜色。

if e.Moving():
    self.SetCursor(wx.Cursor(wx.CURSOR_HAND))
    self.SetFont(self.font1)

如果我们将鼠标指针悬停在链接上,就会将字体改为下划线,同时也将鼠标指针改为手标。

elif e.LeftUp():
    webbrowser.open_new(self.url)

如果我们左键点击链接,就会在默认的浏览器中打开链接。

Burning widget

这是一个我们从头开始创建widget的例子,我们在窗口底部放置一个wx.Panel,然后手动绘制整个widget。如果你曾经刻录过CD或DVD,你已经看到过这种widget。

#burning.py

import wx

class Burning(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, 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, e):

        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, e):

        self.Refresh()


class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        self.cw = 75

        panel = wx.Panel(self)
        CenterPanel = wx.Panel(panel)

        self.sld = wx.Slider(CenterPanel, value=75, maxValue=750, size=(200, -1),
            style=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 = Burning(panel)
        hbox.Add(self.wid, 1, wx.EXPAND)

        hbox2.Add(CenterPanel, 1, wx.EXPAND)
        hbox3.Add(self.sld, 0, wx.LEFT|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.SetTitle("Burning widget")
        self.Centre()

    def OnScroll(self, e):

        self.cw = self.sld.GetValue()
        self.wid.Refresh()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

image-20201103231548941

这个小工具以图形方式显示了一个介质的总容量和我们可用的空间。该小组件由一个滑块控制。我们自定义部件的最小值是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参数决定了我们开始用红色绘制的点。注意使用浮点运算。这是为了达到更高的精度。

实际的绘制包括三个步骤。我们绘制黄色或红色和黄色的矩形。然后我们画出垂直线,将部件分成几个部分。最后,我们画出数字,这表示介质的容量。

def OnSize(self, e):
   
    self.Refresh()

每次调整窗口大小时,我们都会刷新小组件。这将导致小组件重新绘制自己。

def OnScroll(self, e):
   
    self.cw = self.sld.GetValue()
    self.wid.Refresh()

如果我们滚动滑块,我们就会得到实际值并将其保存到self.cw参数中。当燃烧的widget被绘制时,就会使用这个值。然后,我们将重新绘制该小组件。

CPU widget

有一些系统应用程序可以测量系统资源,如温度、内存或CPU消耗。为了使应用程序更有吸引力,我们创建了专门的组件。

以下是系统应用程序中经常使用的组件件。

#cpu.py

import wx


class CPU(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, size=(80, 110))

        self.parent = parent
        self.SetBackgroundColour('#000000')
        self.Bind(wx.EVT_PAINT, self.OnPaint)


    def OnPaint(self, e):

        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 Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        self.sel = 0

        panel = wx.Panel(self)
        centerPanel = wx.Panel(panel)

        self.cpu = CPU(centerPanel)

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        self.slider = wx.Slider(panel, value=self.sel, maxValue=100, size=(-1, 100),
              style=wx.VERTICAL | wx.SL_INVERSE)
        self.slider.SetFocus()

        hbox.Add(centerPanel, 0,  wx.LEFT | wx.TOP, 20)
        hbox.Add(self.slider, 0, wx.LEFT | wx.TOP, 30)

        self.Bind(wx.EVT_SCROLL, self.OnScroll)

        panel.SetSizer(hbox)

        self.SetTitle("CPU")
        self.Centre()


    def OnScroll(self, e):

        self.sel = e.GetInt()
        self.cpu.Refresh()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

image-20201103231824854

我们创建一个黑色的面板。然后我们在这个面板上绘制小矩形。矩形的颜色取决于滑块的值。颜色可以是深绿色或亮绿色。

dc.SetDeviceOrigin(0, 100)
dc.SetAxisOrientation(True, True)

这里我们把默认的坐标系改成了笛卡尔坐标系。这是为了让绘图更直观。

pos = self.parent.GetParent().GetParent().sel
rect = pos / 5

在这里,我们得到sizer的值。我们每列有20个矩形。滑块有100个数字。矩形参数将滑块的值转换为矩形,并以亮绿色绘制。

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个。如果被画的矩形数量大于换算后的矩形值,我们就用深绿色画,否则就用亮绿色画。

你可能感兴趣的