# PyGTK 中的自定义小部件

> 原文： [http://zetcode.com/gui/pygtk/customwidget/](http://zetcode.com/gui/pygtk/customwidget/)

工具箱通常仅提供最常见的窗口小部件，例如按钮，文本窗口小部件，滑块等。没有工具箱可以提供所有可能的窗口小部件。

客户程序员必须创建更专业的小部件。 他们使用工具箱提供的绘图工具来完成此任务。 有两种可能：程序员可以修改或增强现有的小部件，或者可以从头开始创建自定义小部件。

## 刻录小部件

这是我们从头开始创建的小部件的示例。 可以在各种媒体刻录应用（例如 Nero 烧录 ROM）中找到此小部件。

`burning.py`

```py
#!/usr/bin/python
# -*- coding: utf-8 -*-

# ZetCode PyGTK tutorial 
#
# This example creates a burning
# custom widget
#
# author: Jan Bodnar
# website: zetcode.com 
# last edited: April 2011

import gtk
import cairo

class Burning(gtk.DrawingArea):

    def __init__(self, parent):

        self.par = parent
        super(Burning, self).__init__()

        self.num = ( "75", "150", "225", "300", 
            "375", "450", "525", "600", "675" )

        self.set_size_request(-1, 30)
        self.connect("expose-event", self.expose)

    def expose(self, widget, event):

        cr = widget.window.cairo_create()
        cr.set_line_width(0.8)

        cr.select_font_face("Courier", 
            cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
        cr.set_font_size(11)

        width = self.allocation.width

        self.cur_width = self.par.get_cur_value()

        step = round(width / 10.0)

        till = (width / 750.0) * self.cur_width
        full = (width / 750.0) * 700

        if (self.cur_width >= 700):

            cr.set_source_rgb(1.0, 1.0, 0.72)
            cr.rectangle(0, 0, full, 30)
            cr.save()
            cr.clip()
            cr.paint()
            cr.restore()

            cr.set_source_rgb(1.0, 0.68, 0.68)
            cr.rectangle(full, 0, till-full, 30)
            cr.save()
            cr.clip()
            cr.paint()
            cr.restore()

        else:     
            cr.set_source_rgb(1.0, 1.0, 0.72)
            cr.rectangle(0, 0, till, 30)
            cr.save()
            cr.clip()
            cr.paint()
            cr.restore()

        cr.set_source_rgb(0.35, 0.31, 0.24)

        for i in range(1, len(self.num) + 1):
            cr.move_to(i*step, 0)
            cr.line_to(i*step, 5)
            cr.stroke()

            (x, y, width, height, dx, dy) = cr.text_extents(self.num[i-1])
            cr.move_to(i*step-width/2, 15)
            cr.text_path(self.num[i-1])
            cr.stroke()

class PyApp(gtk.Window): 

    def __init__(self):
        super(PyApp, self).__init__()

        self.set_title("Burning")
        self.set_size_request(350, 200)        
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)

        self.cur_value = 0

        vbox = gtk.VBox(False, 2)

        scale = gtk.HScale()
        scale.set_range(0, 750)
        scale.set_digits(0)
        scale.set_size_request(160, 40)
        scale.set_value(self.cur_value)
        scale.connect("value-changed", self.on_changed)

        fix = gtk.Fixed()
        fix.put(scale, 50, 50)

        vbox.pack_start(fix)

        self.burning = Burning(self)
        vbox.pack_start(self.burning, False, False, 0)

        self.add(vbox)
        self.show_all()

    def on_changed(self, widget):
        self.cur_value = widget.get_value()
        self.burning.queue_draw()

    def get_cur_value(self):
        return self.cur_value

PyApp()
gtk.main()

```

我们在窗口底部放置一个`DrawingArea`并手动绘制整个窗口小部件。 所有重要的代码都驻留在`Burning`类的`expose()`方法中。 此小部件以图形方式显示了介质的总容量和可供我们使用的可用空间。 该小部件由比例小部件控制。 自定义窗口小部件的最小值为 0，最大值为 750。如果值达到 700，则开始绘制红色。 这通常表示过度燃烧。

```py
self.num = ( "75", "150", "225", "300", 
    "375", "450", "525", "600", "675" )

```

这些数字显示在刻录小部件上。 它们显示了介质的容量。

```py
self.cur_width = self.par.get_cur_value()

```

这两行从刻度小部件获取当前数字。 我们获得父窗口小部件，并从父窗口小部件中获得当前值。

```py
till = (width / 750.0) * self.cur_width
full = (width / 750.0) * 700

```

直到参数确定要绘制的总大小。 该值来自滑块小部件。 它占整个面积的一部分。 `full`参数确定了我们开始绘制红色的点。

```py
cr.set_source_rgb(1.0, 1.0, 0.72)
cr.rectangle(0, 0, till, 30)
cr.save()
cr.clip()
cr.paint()
cr.restore()

```

此代码在此处绘制了一个黄色矩形，直到介质充满为止。

```py
(x, y, width, height, dx, dy) = cr.text_extents(self.num[i-1])
cr.move_to(i*step-width/2, 15)
cr.text_path(self.num[i-1])
cr.stroke()

```

这里的代码在刻录小部件上绘制数字。 我们计算`TextExtents`来正确定位文本。

```py
def on_changed(self, widget):
    self.cur_value = widget.get_value()
    self.burning.queue_draw()

```

我们从小部件中获取值，并将其存储在`cur_value`变量中以备后用。 我们重新绘制刻录的小部件。

![Burning widget](img/feb3b93e26091d1e6e3bdaaad9382a6f.jpg)

图：烧录小部件

在本章中，我们在 PyGTK 中创建了一个自定义小部件。