当前位置: 首页 > 文档资料 > PyGTK 教程 >

05 PyGTK 中的信号和事件

优质
小牛编辑
134浏览
2023-12-01

在PyGTK教程的这章中,我们将讨论信号和事件。

所有的GUI应用程序都是事件驱动,PyGTK应用程序也不列外。程序调用gtk.main()函数开始一个主循环。该循环不断地检查新产生的事件。如果没有事件,程序将等待,什么都不做。

Events(事件)是从X服务到程序之间的消息。当我们点击一个按钮部件,这个点击(clicked)信号将会被发出(be emitted)。有些信号是所有的部件都继承,例如destroy(销毁部件);有些信号是部件所特有的,例如toggled信号是toggle按钮所特有的。

程序员用信号处理函数(signal handlers)来对各种信号进行交互(react)。这些处理函数在回收前被GTK程序员调用。【译者注:此处翻译有些别扭,英文比较难以理解】

handler_id = button.connect("clicked", self.on_clicked)

这里我们使用GObject类(GtkButton是一个GObject类的子类)的connect()方法,来连接一个回调函数on_clicked()到被称为clicked的信号上。

connect()方法返回一个handler id,它被用来唯一地表式这个回调方法。这个id可以被下列方法使用:

def disconnect(handler_id)

def handler_disconnect(handler_id)

def handler_is_connected(handler_id)

def handler_block(handler_id)

def handler_unblock(handler_id)

这些方法能够从一个GObject部件断开其处理函数,或者阻塞、放行它。

Signals vs events

关于这两者之间的不同,我们往往有许多困惑。

信号(signals)和事件(events)是两个不同的事情。一个事件是窗口系统事件中的一个一对一的映射。摁下键盘键,更改窗口大小或者摁下按钮都是典型的窗口系统事件。窗口系统事件被报告给应用程序的主循环。GDK解释这些窗口系统事件,并且通过信号传递过去。

一个信号是一个回调机制。如果一个对象想要被通知关于另外一个对象的活动或状态变化,它就注册一个回调信号(callback)。当这个对象发出了一个信号,它就查找回调信号列表,这些列表中的信号已经被注册过了,然后为特定的信号调用回调函数。它能够有选择地发送一些事先定义好的数据。

信号(signals)是一种通用目的通知框架。它们不仅被用作UI变化的通知,还被用作应用程序状态变化的通知。信号机制(signals)是通用的,也是强大的,它们的使用非常广泛。任何GObject对象都能发射或者接受一个对象。某种类型的对象可能有一个或者多个信号,其中每一个都可能有一个参数列表和返回值。然后处理函数(handlers)能被连接到这种类型的对象的实例。当一个信号在一个实例上被发射,每个已经连接的处理函数将会被调用。

信号机制和事件机制之间唯一的关系是,信号被用来发射来自X服务的事件的通知。

信号机制是gtk.GObject的一种特征,也是它的一个子类。事件是一个Gdk/Xlib的概念。

Simple example

下面的例子将展示,我们在两个基本的信号之间进行交互。

Code:quitbutton.py

#!/usr/bin/python

# ZetCode PyGTK tutorial 
#
# The example shows how to work with 
# destroy and clicked signals
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009

import gtk

class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Quit Button")
        self.set_size_request(250, 200)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", self.on_destroy)
        
        fixed = gtk.Fixed()

        quit = gtk.Button("Quit")
        quit.connect("clicked", self.on_clicked)
        quit.set_size_request(80, 35)

        fixed.put(quit, 50, 50)

        self.add(fixed)
        self.show_all()
        
    def on_destroy(self, widget):
        gtk.main_quit()
        
    def on_clicked(self, widget):
        gtk.main_quit()

PyApp()
gtk.main()

当我们关闭窗口的时候,销毁(destroy)信号被触发。而默认情况下,当我们点击标题栏上的关闭按钮,程序不会退出。

self.connect("destroy", self.on_destroy)

connect()方法将on_destroy()方法连接到destroy信号上。

quit.connect("clicked", self.on_clicked)

按下退出按钮,clicked信号被激发。当我们点击退出按钮,我们就调用了on_clicked()方法。

def on_destroy(self, widget):
    gtk.main_quit()

在on_destroy()方法中,我们对destroy信号作出反应。我们调用了gtk.main_quit()方法,它使我们的应用程序终止。

def on_clicked(self, widget):
    gtk.main_quit()

这里是on_clicked()方法。它获得了两个参数,其中widget参数就是这个激发该信号的对象,在我们的例子中,它就是quit按钮。不同的对象发送不同的信号。发送到方法函数的信号和参数能够在PyGTK库的参考手册上找到。参考手册网址是: http://pygtk.org/docs/pygtk/index.html

Creating a custom signal

在下面的代码示例中,我们创建并发送了一个定制的信号(custom signal)。

Code: customsignal.py

#!/usr/bin/python

# ZetCode PyGTK tutorial 
#
# This example shows how to create
# and send a custom singal
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009

import gobject

class Sender(gobject.GObject):
    def __init__(self):
        self.__gobject_init__()
        

gobject.type_register(Sender)
gobject.signal_new("z_signal", Sender, gobject.SIGNAL_RUN_FIRST,
                   gobject.TYPE_NONE, ())

class Receiver(gobject.GObject):
    def __init__(self, sender):
        self.__gobject_init__()
        
        sender.connect('z_signal', self.report_signal)
        
    def report_signal(self, sender):
        print "Receiver reacts to z_signal"

def user_callback(object):
    print "user callback reacts to z_signal"

if __name__ == '__main__':
    
    sender = Sender()
    receiver = Receiver(sender)

    sender.connect("z_signal", user_callback)
    sender.emit("z_signal")

我们创建了两个GObject对象,sender和receiver对象。Sender发射了一个信号,这个信号被receiver接收了。我们也将一个回调函数连接到了这个信号。

class Sender(gobject.GObject):
    def __init__(self):
        self.__gobject_init__()

这是发送者对象,他通过默认的构造函数被创建。

gobject.type_register(Sender)
gobject.signal_new("z_signal", Sender, gobject.SIGNAL_RUN_FIRST,
                  gobject.TYPE_NONE, ())

我们注册了一个新的对象和一个新的信号。Signal_new()函数为Sender对象注册了一个被称为z_signal的信号。这个SIGNAL_RUN_FIRST参数意思是,接收信号对象的默认处理函数作为第一次被调用。后面两个参数是返回值类型和参数类型。在我们的例子中,我们不返回任何值,并且不发送参数。

sender.connect('z_signal', self.report_signal)

接收者receiver对象监听z_signal信号。

sender = Sender()
receiver = Receiver(sender)

Sender和Receiver对象被实例化。receiver将sender作为参数,因此receiver能够监听sender的信号。

sender.connect("z_signal", user_callback)

这里我们将这个信号连接到用户的回调函数。

sender.emit("z_signal")

z_signal信号正在被发射。

class Sender(gobject.GObject):

    __gsignals__ = {
        'z_signal': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
    }

    def __init__(self):
        self.__gobject_init__() 
        
gobject.type_register(Sender)

我们也可以用__gsignals__类的属性来注册一个新的信号。

Predefined signal handlers

在PyGTK中的某些对象可能有预定义的信号处理函数(predefined signal handlers)。这些函数的名称以do_*开始,例如:do_expose(),do_show()或者do_clicked()等。

Code:move.py

#!/usr/bin/python

# ZetCode PyGTK tutorial 
#
# This example overrides predefined
# do_configure_event() signal handler
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009

import gtk
import gobject

class PyApp(gtk.Window):
    __gsignals__ = {
        "configure-event" : "override"
        }

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

        self.set_size_request(200, 150)
        self.set_position(gtk.WIN_POS_CENTER)
       
        self.connect("destroy", gtk.main_quit)

        self.show_all()

    def do_configure_event(self, event):
        
        title = "%s, %s" % (event.x, event.y)
        self.set_title(title)
        gtk.Window.do_configure_event(self, event)
       

PyApp()
gtk.main()

当我们移动窗口和更改窗口大小的时候后,X服务将会发送配置事件(configure events),然后它们就被传送到configure-event信号。

在我们的示例代码中,我们在标题栏上展示了窗口的左上角的x, y坐标。我们可以很简单地将一个信号处理函数连接到configure-event信号,但是我们采用了一个不同的策略。在实现逻辑上的需要的地方,我们重写了默认的类处理函数。

__gsignals__ = {
    "configure-event" : "override"
    }

这里说明,我们将要重写默认的on_configure_event()方法。

def do_configure_event(self, event):
       
    title = "%s, %s" % (event.x, event.y)
    self.set_title(title)
    gtk.Window.do_configure_event(self, event)

此处我们重写了预定义的do_configure_event()方法。我们设置窗口的坐标显示到窗口的标题上。当然也要注意最后一行,它明确地调用了父类的do_configure_event()方法。这是因为它还做了重要的工作。请尝试注释掉这一行,看看发生了什么。改变窗口大小将不能正确工作【译者注:在Win下尝试似乎都能正确工作】。如果我们重写了默认的处理函数,我有可能会或者不会调用父类的方法,在这个例子中,我们必须要调用。

Figure:Configure signal

Signals of a button

下面的例子将展示各种各样的按钮信号。

Code:buttonsignals.py

#!/usr/bin/python

# ZetCode PyGTK tutorial 
#
# This program shows various signals 
# of a button widget
# It emits a button-release-event which
# triggers a released singal
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009

import gtk

class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Signals")
        self.set_size_request(250, 200)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)
        
        fixed = gtk.Fixed()
        
        self.quit = gtk.Button("Quit")
        
        self.quit.connect("pressed", self.on_pressed)
        self.quit.connect("released", self.on_released)
        self.quit.connect("clicked", self.on_clicked)
        
        self.quit.set_size_request(80, 35)

        fixed.put(self.quit, 50, 50)
        
        self.add(fixed)
        self.show_all()
        self.emit_signal()
        
    def emit_signal(self):
                
        event = gtk.gdk.Event(gtk.gdk.BUTTON_RELEASE)
        event.button = 1
        event.window = self.quit.window
        event.send_event = True
                
        self.quit.emit("button-release-event", event)
        
        
    def on_clicked(self, widget):
        print "clicked"
        
    def on_released(self, widget):
        print "released"
        
    def on_pressed(self, widget):
        print "pressed"

PyApp()
gtk.main()

一个按钮能够发出不止一种类型的信号。我们这里用它们的三种信号进行工作,分别是clicked, pressedreleased信号。我们也展示了一个事件信号激发另外一个信号。

self.quit.connect("pressed", self.on_pressed)
self.quit.connect("released", self.on_released)
self.quit.connect("clicked", self.on_clicked)

我们对所有三个信号注册了回调函数。

self.emit_signal()

程序开始运行的时候,我们激发了一个特别的信号。

def emit_signal(self):
               
    event = gtk.gdk.Event(gtk.gdk.BUTTON_RELEASE)
    event.button = 1
    event.window = self.quit.window
    event.send_event = True
               
    self.quit.emit("button-release-event", event)

我们激发button-release-event信号,它将一个Event对象作为参数。当这个程序启动之后,我们应该在控制台窗口上看到"released"文本。当我们点击一下按钮,所有三个信号都被激发了。

Blocking an event handler

我们能够阻塞一个信号的处理函数,下面的例子将展示这个。

Code:block.py

#!/usr/bin/python

# ZetCode PyGTK tutorial 
#
# This example shows how to block/unblock
# a signal handler
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009

import gtk

class PyApp(gtk.Window):

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

        self.set_title("Blocking a callback")
        self.set_size_request(250, 180)
        self.set_position(gtk.WIN_POS_CENTER)
        
        fixed = gtk.Fixed()
        button = gtk.Button("Click")
        button.set_size_request(80, 35)
        self.id = button.connect("clicked", self.on_clicked)
        fixed.put(button, 30, 50)

        check = gtk.CheckButton("Connect")
        check.set_active(True)
        check.connect("clicked", self.toggle_blocking, button)
        fixed.put(check, 130, 50)

        self.connect("destroy", gtk.main_quit)

        self.add(fixed)
        self.show_all()

    def on_clicked(self, widget):
        print "clicked"

    def toggle_blocking(self, checkbox, button):
        if checkbox.get_active():
           button.handler_unblock(self.id)
        else:
           button.handler_block(self.id)

PyApp()
gtk.main()

在这个代码示例中,我们有一个按钮和一个复选框。当我们点击按钮并且复选框是选中状态时,我们在控制台上显示了"clicked"的文本。这个复选框阻塞或者放行来自按钮clicked信号和它的处理函数方法之间的连接。

self.id = button.connect("clicked", self.on_clicked)

connect()方法返回这个处理函数的id, 这个id用来阻塞或者放行这个处理。

def toggle_blocking(self, checkbox, button):
    if checkbox.get_active():
       button.handler_unblock(self.id)
    else:
       button.handler_block(self.id)

这几行代码是阻塞和放行相应的方法的回调。

Figure:blocking a callback