PyGTK Custom Widget不会重绘

时间:2013-01-05 13:55:33

标签: python pygtk

我在更新自定义GTK +小部件时遇到问题:VUWidget。 Generator类正在更新类Section的level属性,其子类具有VUWidget属性。 Generator类正确更新级别属性的值。

import pygtk  
pygtk.require("2.0")  
import gtk, gtk.gdk  
import cairo  

#=========================================================================

class VUWidget(gtk.DrawingArea):
    __gtype_name__ = 'VUWidget'

    _BACKGROUND_RGB = (0., 0., 0.)
    _LEVEL_GRADIENT_BOTTOM_ORGBA = (1, 0, 1, 0, 1)
    _LEVEL_GRADIENT_TOP_ORGBA = (0, 1, 0, 0, 1)

    #_____________________________________________________________________

    def __init__(self, section):
        gtk.DrawingArea.__init__(self)      
        self.section = section      

        self.connect("configure_event", self.on_configure_event)
        self.connect("expose-event", self.OnDraw)
        self.section.connect("changed-value", self.ValueChanged)

        self.set_size_request(30,100)       
        self.realize()
        self.show()
    #_____________________________________________________________________


    def ValueChanged(self, widget, level):
        #print ("Callback %f" % self.section.GetLevel()) 

        rect = self.get_allocation()                
        self.window.invalidate_rect(rect, False)
        return False
    #_____________________________________________________________________


    def GenerateBackground(self):       
        rect = self.get_allocation()
        ctx = cairo.Context(self.source)

        ctx.set_line_width(2)
        ctx.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
        pat = cairo.LinearGradient(0.0, 0.0, 0, rect.height)
        pat.add_color_stop_rgba(*self._LEVEL_GRADIENT_BOTTOM_ORGBA)
        pat.add_color_stop_rgba(*self._LEVEL_GRADIENT_TOP_ORGBA)
        ctx.rectangle(0, 0, rect.width, rect.height)
        ctx.set_source(pat)

        ctx.fill()
    #_____________________________________________________________________


    def on_configure_event(self, widget, event):
        self.source = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.allocation.width, self.allocation.height)
        self.GenerateBackground()

        return self.OnDraw(widget, event)
    #_____________________________________________________________________


    def OnDraw(self, widget, event):        
        ctx = self.window.cairo_create()
        ctx.save()

        rect = self.get_allocation()

        ctx.rectangle(0, 0, rect.width, rect.height)
        ctx.set_source_rgb(*self._BACKGROUND_RGB)
        ctx.fill()

        ctx.rectangle(0, rect.height * (1. - self.section.GetLevel()), rect.width, rect.height)
        ctx.clip()

        ctx.set_source_surface(self.source, 0, 0)   
        ctx.paint()

        ctx.restore()

        return False        
    #_____________________________________________________________________


    def Destroy(self):
        del self.source
        self.destroy()  
    #_____________________________________________________________________  

#=========================================================================

信号在类Section中实现并正确发出

__gsignals__ = {
    'changed-value' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,))
    }

此致 CK

1 个答案:

答案 0 :(得分:3)

这是一种方法,我将其子类化并提供自定义 draw 方法。如果状态有任何更改,我会调用 invalidate ,强制小部件重新绘制 expose-event 。如果您需要不同的上下文,可以在 on_expose_event 中提供 方法。

所以基本上,只在一个的地方进行所有绘图。如果窗口小部件应显示不同的内容,请设置新状态并重新呈现。使维护变得容易。

#cairovehicle.py

import gtk

class CairoVehicle(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.connect("expose-event", self.on_expose_event)

    def get_background(self):
        """Serves as a caching solution."""
        return self.__bg

    def set_background(self, pattern):
        """Serves as a caching solution."""
        self.__bg = pattern

    def get_brush(self):
        """Serves as a caching solution."""
        return self.__brush

    def set_brush(self, pattern):
        """Serves as a caching solution."""
        self.__brush = pattern

    def on_expose_event(self, widget, event):
        context = self.window.cairo_create()

        # Restrict cairo to the exposed area
        context.rectangle(*event.area)
        context.clip()

        self.width, self.height = self.window.get_size()

        self.draw(context)

    def on_configure_event(self, widget, event):
        """Override this in case you want to do something when 
           the widget's size changes."""

        return super(CairoVehicle, self).on_configure_event(widget, event)

    def invalidate(self):
        """Force a re-rendering of the window."""

        rect = self.get_allocation()

        # Compensate for parent offset, if any.
        parent = self.get_parent()
        if parent:
            offset = parent.get_allocation()
            rect.x -= offset.x
            rect.y -= offset.y

        self.window.invalidate_rect(rect, False)

    def draw(self, context):
        """Override this."""

        # do custom drawing here

        raise NotImplementedError()

    def make_grid(self, context, fgcolor, bgcolor, gapwidth, linewidth, 
                                                        width, height):

         context.set_source_rgba(*bgcolor)
         context.rectangle(0, 0, width, height)
         context.fill()

         context.set_source_rgba(*fgcolor)
         context.set_line_width(linewidth)

         # uneven linewidths lead to blurry displaying when set on integer
         # coordinates, so in that case move coordinates away by half a
         # pixel.
         adjust = 0.5 if linewidth % 2 else 0
         i = 1
         j = 1

         while gapwidth*i-adjust < width:
             context.move_to(gapwidth*i-adjust, 0)
             context.line_to(gapwidth*i-adjust, height)
             context.stroke()
             i += 1

         while gapwidth*j-adjust < height:
             context.move_to(0, gapwidth*j-adjust)
             context.line_to(width, gapwidth*j-adjust)
             context.stroke()
             j += 1


class Grid(CairoVehicle):
    def draw(self, context):
        context.push_group()
        self.make_grid(context, fgcolor=(0, 0, 0, 1), bgcolor=(1, 1, 1, 1),
                    gapwidth=20, linewidth=1, width=self.width,
                    height=self.height)
        self.set_background(context.pop_group())
        context.set_source(self.get_background())
        context.paint()


if __name__ == "__main__":
    w = gtk.Window()
    w.connect("destroy", gtk.main_quit)
    w.show()

    cv = Grid()
    cv.show()
    w.add(cv)
    gtk.main()

对评论的补充回复:

Gtk.Window和Gdk.Window在概念上是不同的。第一个是一个容器,可以只有一个其他小部件(其他容器或任何其他小部件)。第二个用于在显示器上绘制东西并捕获事件。 gdk.Window是在一个小部件的“实现”事件中构建的。在此之前,它在PyGtk中只是无。恕我直言,它是不可取的(甚至不知道它是否可行)在gtk.Window.window上绘制。自定义绘图应在gtk.DrawingArea.window中完成。这就是DrawingArea的用途。

<强>参考文献:

https://mail.gnome.org/archives/gtk-app-devel-list/1999-January/msg00138.html
GtkDrawingArea - how to make it drawable?