Редактирование: WxPython FAQ Custom widgets

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.
Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 1: Строка 1:
 +
Have you ever looked at an application and wondered, how a particular gui item was created? Probably every wannabe programmer has. Then you were looking at a list of widgets provided by your favourite gui library. But you couldn't find it.
 +
Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets.
 +
There are actually two kinds of toolkits. Spartan toolkits and heavy weight toolkits. The FLTK toolkit is a kind of a spartan toolkit. It provides only the very basic widgets and assumes, that the programemer will create the more complicated ones himself. wxPython is a heavy weight one. It has lots of widgets. Yet it does not provide the more specialized widgets. For example a speed meter widget, a widget that measures the capacity of a CD to be burned (found e.g. in nero). Toolkits also don't have usually charts.
 +
 +
Programmers must create such widgets by themselves. They do it by using the drawing tools provided by the toolkit.
 +
There are two possibilities. A programmer can modify or enhance an existing widget. Or he can create a custom widget from scratch.
 +
 +
Here I assume, you have read the chapter on the [[WxPython_FAQ_GDI | GDI]].
 +
 +
== A hyperlink widget  ==
 +
The first example will create a hyperlink. The hyperlink widget will be based on an existing <i>wx.lib.stattext.GenStaticText</i> widget.
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# link.py
 +
 +
import wx
 +
from wx.lib.stattext import GenStaticText
 +
import webbrowser
 +
 +
class Link(GenStaticText):
 +
    def __init__(self, parent, id=-1, label='', pos=(-1, -1),
 +
        size=(-1, -1), style=0, name='Link', URL=''):
 +
 +
        GenStaticText.__init__(self, parent, id, label, pos, size, style, name)
 +
 +
        self.url = URL
 +
 +
        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 OnMouseEvent(self, event):
 +
        if event.Moving():
 +
            self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
 +
            self.SetFont(self.font1)
 +
 +
        elif event.LeftUp():
 +
            webbrowser.open_new(self.url)
 +
 +
        else:
 +
            self.SetCursor(wx.NullCursor)
 +
            self.SetFont(self.font2)
 +
 +
        event.Skip()
 +
 +
 +
class HyperLink(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(220, 150))
 +
 +
        panel = wx.Panel(self, -1)
 +
        Link(panel, -1, 'ZetCode', pos=(10, 60), URL='http://www.zetcode.com')
 +
        motto = GenStaticText(panel, -1, 'Knowledge only matters', pos=(10, 30))
 +
        motto.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana'))
 +
 +
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
app = wx.App()
 +
HyperLink(None, -1, 'A Hyperlink')
 +
app.MainLoop()
 +
</source>
 +
 +
This hyperlink widget is based on an existing widget. In this example we don't draw anything, we just use an existing widget, which we modify a bit.
 +
 +
<source lang="python">
 +
from wx.lib.stattext import GenStaticText
 +
import webbrowser
 +
 +
</source>
 +
 +
Here we import the base widget from which we derive our hyperlink widget and the webbrowser module. webbrowser module is a standard python module. We will use it to open links in a default browser.
 +
 +
<source lang="python">
 +
self.SetFont(self.font2)
 +
self.SetForegroundColour('#0000ff')
 +
</source>
 +
 +
The idea behind creating a hyperlink widget is simple. We inherit from a base <i>wx.lib.stattext.GenStaticText</i> widget class. So we have a text widget. Then we modify it a bit to make a hyperlink out of this text. We change the font and the colour of the text. Hyperlinks are usually blue.
 +
 +
<source lang="python">
 +
if event.Moving():
 +
    self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
 +
    self.SetFont(self.font1)
 +
</source>
 +
 +
If we hover a mouse pointer over the link, we change the font to underlined and also change the mouse pointer to a hand cursor.
 +
 +
<source lang="python">
 +
elif event.LeftUp():
 +
    webbrowser.open_new(self.url)
 +
</source>
 +
If we left click on the link, we open the link in a defaul browser.
 +
 +
[[image: wxPython_faq_link.jpg | center]]
 +
[[image: wxPython_faq_linkw.jpg | center]]
 +
 +
== Burning widget ==
 +
This is an example of a widget, that we create from a ground up. We put a <b>wx.Panel</b> on the bottom of the window and draw the entire widget manually.
 +
If you have ever burned a cd or a dvd, you already saw this kind of widget.
 +
 +
Remark for windows users. To avoid flicker, use double buffering.
 +
<source lang="python">
 +
#!/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()
 +
</source>
 +
 +
All the important code resides in the <i>OnPaint()</i> method of the Widget class. This widget shows graphically the total capacity of a medium and the free space available to us. The widget is controlled by a slider widget. The minimum value of our custom widget is 0, the maximum is 750. If we reach value 700, we began drawing in red colour. This normally indicates overburning.
 +
 +
<source lang="python">
 +
w, h = self.GetSize()
 +
self.cw = self.parent.GetParent().cw
 +
...
 +
till = (w / 750.0) * self.cw
 +
full = (w / 750.0) * 700
 +
</source>
 +
 +
We draw the widget dynamically. The greater the window, the greater the burning widget. And vice versa.
 +
That is why we must calculate the size of the <i>wx.Panel</i> onto which we draw the custom widget.
 +
The till parameter determines the total size to be drawn. This value comes from the slider widget. It is a proportion of the whole area. The full parameter determines the point, where we begin to draw in red color.
 +
Notice the use of floating point arithmetics. This is to achieve greater precision.
 +
 +
The actual drawing consists of three steps. We draw the yellow or red and yellow rectangle. Then we draw the vertical lines, which divide the widget into several parts. Finally, we draw the numbers, which indicate the capacity of the medium.
 +
 +
<source lang="python">
 +
def OnSize(self, event):
 +
    self.Refresh()
 +
</source>
 +
 +
Every time the window is resized, we refresh the widget. This causes the widget to repaint itself.
 +
 +
<source lang="python">
 +
 +
def OnScroll(self, event):
 +
    self.cw = self.sld.GetValue()
 +
    self.wid.Refresh()
 +
</source>
 +
 +
If we scroll the thumb of the slider, we get the actual value and save it into the <i>self.cw</i> parameter. This value is used, when the burning widget is drawn. Then we cause the widget to be redrawn.
 +
 +
[[image: wxPython_faq_burning.jpg | center]] [[image: wxPython_faq_burningw.jpg | center]]
 +
 +
== The CPU widget ==
 +
There are system applications that measure system resources. The temperature, memory and CPU consuption etc. By displaying a simple text like CPU 54% you probably won't impress your users. Specialized widgets are created to make the application more appealing.
 +
 +
The following widget is often used in system applications.
 +
 +
Remark for windows users. To avoid flicker, use double buffering. Change the size of the application and the width of the slider.
 +
 +
<source lang="python">
 +
#!/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()
 +
</source>
 +
 +
Creating this widget is quite simple. We create a black panel. Then we draw small rectangles onto this panel.
 +
The color of the rectangles depend on the value of the slider. The color can be dark green or bright green.
 +
 +
<source lang="python">
 +
dc.SetDeviceOrigin(0, 100)
 +
dc.SetAxisOrientation(True, True)
 +
</source>
 +
 +
Here we change the default coordinate system to cartesian. This is to make the drawing intuitive.
 +
 +
<source lang="python">
 +
pos = self.parent.GetParent().GetParent().sel
 +
rect = pos / 5
 +
</source>
 +
 +
Here we get the value of the sizer.  We have 20 rectangles in each column. The slider has 100 numbers. The rect parameter makes a convertion from slider values into rectangles, that will be drawn in bright green color.
 +
 +
<source lang="python">
 +
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)
 +
</source>
 +
 +
Here we draw 40 rectangles, 20 in each column. If the number of the rectangle being drawn is greater than the converted rect value, we draw it in a dark green color. Otherwise in bright green.
 +
 +
[[image: wxPython_faq_cpu.jpg | center]]
 +
 +
[[Категория:wxWidgets]]
 +
[[Категория:Python]]

Пожалуйста, обратите внимание, что все ваши добавления могут быть отредактированы или удалены другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Wiki.crossplatform.ru:Авторское право). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!