原文: http://zetcode.com/gui/pygtk/signals/

在 PyGTK 编程教程的这一部分中,我们将讨论信号&事件。

所有 GUI 应用都是事件驱动的。 PyGTK 应用也不例外。 应用通过gtk.main()调用启动主循环,该循环不断检查新生成的事件。 如果没有事件,则应用将等待并且不执行任何操作。

事件是从 X 服务器到应用的消息。 当我们单击按钮小部件时,单击的信号将以发出。 有所有小部件都继承的信号,例如销毁,有特定于小部件的信号,例如在切换按钮上切换。

程序员使用信号处理器来响应各种信号。 这些处理器在 GTK 程序员中称为回调。

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

在这里,我们使用GObject类的connect()方法((GtkButtonGObject))将回调on_clicked()连接到名为clicked的信号。

connect()方法返回一个处理器 ID,用于唯一标识回调方法。 该 ID 可以与以下方法一起使用:

  1. def disconnect(handler_id)
  2. def handler_disconnect(handler_id)
  3. def handler_is_connected(handler_id)
  4. def handler_block(handler_id)
  5. def handler_unblock(handler_id)

这些方法使处理器可以与GObject断开连接,也可以对其进行阻止/取消阻止。

信号与事件

关于两者之间的差异,通常会有很多困惑。

信号和事件是两件事。 事件是窗口系统事件的几乎一对一的映射。 按键,调整窗口大小或按键是典型的窗口系统事件。 窗口系统事件将报告给应用主循环。 Gdk 解释窗口系统事件并通过信号传递它们。

信号就是回调机制。 如果一个对象希望收到有关另一对象的动作或状态更改的通知,它将注册一个回调。 当对象发出信号时,它会在已向其注册的回调列表中查找并调用特定信号的回调。 它可以选择发送一些预定义的数据。

信号是一个通用的通知框架。 它们不仅用于有关 UI 更改的通知。 它们可用于有关应用状态更改的通知。 信号是通用的,有力的,其用途非常广泛。 任何GObject都可以发射和接收信号。 一个类型可以具有一个或多个信号,每个信号可以具有一个参数列表和返回值。 然后可以将处理器连接到该类型的实例。 在实例上发出信号时,将调用每个连接的处理器。

信号和事件之间的唯一联系是信号用于从 X 服务器发送有关事件的通知。

信号是gtk.Object及其子类的功能,事件是 Gdk/Xlib 概念。

简单的例子

下一个示例显示了我们如何对两个基本信号做出反应。

quitbutton.py

  1. #!/usr/bin/python
  2. # ZetCode PyGTK tutorial
  3. #
  4. # The example shows how to work with
  5. # destroy and clicked signals
  6. #
  7. # author: jan bodnar
  8. # website: zetcode.com
  9. # last edited: February 2009
  10. import gtk
  11. class PyApp(gtk.Window):
  12. def __init__(self):
  13. super(PyApp, self).__init__()
  14. self.set_title("Quit Button")
  15. self.set_size_request(250, 200)
  16. self.set_position(gtk.WIN_POS_CENTER)
  17. self.connect("destroy", self.on_destroy)
  18. fixed = gtk.Fixed()
  19. quit = gtk.Button("Quit")
  20. quit.connect("clicked", self.on_clicked)
  21. quit.set_size_request(80, 35)
  22. fixed.put(quit, 50, 50)
  23. self.add(fixed)
  24. self.show_all()
  25. def on_destroy(self, widget):
  26. gtk.main_quit()
  27. def on_clicked(self, widget):
  28. gtk.main_quit()
  29. PyApp()
  30. gtk.main()

当我们关闭窗口时,破坏信号被触发。 默认情况下,当我们单击标题栏中的关闭按钮时,应用不会退出。

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

connect()方法将on_destroy()方法插入destroy信号。

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

按下退出按钮,将触发clicked信号。 当单击退出按钮时,我们将调用on_clicked()方法。

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

on_destroy()方法中,我们对destroy信号做出反应。 我们调用gtk.main_quit()方法,该方法将终止应用。

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

这是on_clicked()方法。 它有两个参数。 widget参数是触发该信号的对象。 在我们的例子中,它是退出按钮。 不同的对象发送不同的信号。 发送到方法的信号和参数可以在 PyGTK 库的参考手册中找到。 pygtk.org/docs/pygtk/index.html

创建自定义信号

在下面的代码示例中,我们创建并发送一个自定义信号。

customsignal.py

  1. #!/usr/bin/python
  2. # ZetCode PyGTK tutorial
  3. #
  4. # This example shows how to create
  5. # and send a custom singal
  6. #
  7. # author: jan bodnar
  8. # website: zetcode.com
  9. # last edited: February 2009
  10. import gobject
  11. class Sender(gobject.GObject):
  12. def __init__(self):
  13. self.__gobject_init__()
  14. gobject.type_register(Sender)
  15. gobject.signal_new("z_signal", Sender, gobject.SIGNAL_RUN_FIRST,
  16. gobject.TYPE_NONE, ())
  17. class Receiver(gobject.GObject):
  18. def __init__(self, sender):
  19. self.__gobject_init__()
  20. sender.connect('z_signal', self.report_signal)
  21. def report_signal(self, sender):
  22. print "Receiver reacts to z_signal"
  23. def user_callback(object):
  24. print "user callback reacts to z_signal"
  25. if __name__ == '__main__':
  26. sender = Sender()
  27. receiver = Receiver(sender)
  28. sender.connect("z_signal", user_callback)
  29. sender.emit("z_signal")

我们创建两个GObjects。 发送方和接收方对象。 发送方发出一个信号,该信号被接收方对象接收。 我们还会在信号中插入回调。

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

这是一个发送者对象。 它是使用默认构造器创建的。

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

我们注册一个新对象和一个新信号。 signal_new()函数为发件人对象注册一个名为z_signal的信号。 SIGNAL_RUN_FIRST参数意味着接收信号的对象的默认处理器首先调用。 最后两个参数是返回值类型和参数类型。 在我们的示例中,我们不返回任何值,也不发送任何参数。

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

接收器监听z_signal

  1. sender = Sender()
  2. receiver = Receiver(sender)

实例化发送者和接收者对象。 接收方将发送方作为参数,以便可以监听其信号。

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

在这里,我们将信号插入用户回调。

  1. sender.emit("z_signal")

z_signal正在发射。

  1. class Sender(gobject.GObject):
  2. __gsignals__ = {
  3. 'z_signal': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
  4. }
  5. def __init__(self):
  6. self.__gobject_init__()
  7. gobject.type_register(Sender)

我们还可以使用__gsignals__类属性来注册新信号。

预定义的信号处理器

PyGTK 中的对象可能具有预定义的信号处理器。 这些处理器以do_*开头。 例如do_expose()do_show()do_clicked()

move.py

  1. #!/usr/bin/python
  2. # ZetCode PyGTK tutorial
  3. #
  4. # This example overrides predefined
  5. # do_configure_event() signal handler
  6. #
  7. # author: jan bodnar
  8. # website: zetcode.com
  9. # last edited: February 2009
  10. import gtk
  11. import gobject
  12. class PyApp(gtk.Window):
  13. __gsignals__ = {
  14. "configure-event" : "override"
  15. }
  16. def __init__(self):
  17. super(PyApp, self).__init__()
  18. self.set_size_request(200, 150)
  19. self.set_position(gtk.WIN_POS_CENTER)
  20. self.connect("destroy", gtk.main_quit)
  21. self.show_all()
  22. def do_configure_event(self, event):
  23. title = "%s, %s" % (event.x, event.y)
  24. self.set_title(title)
  25. gtk.Window.do_configure_event(self, event)
  26. PyApp()
  27. gtk.main()

当我们移动窗口或调整窗口大小时,X 服务器将发送配置事件。 然后将它们转换为configure-event信号。

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

  1. __gsignals__ = {
  2. "configure-event" : "override"
  3. }

这表明我们将覆盖默认的on_configure_event()方法。

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

在这里,我们将覆盖预定义的do_configure_event()方法。 我们将窗口的 x,y 坐标设置为窗口的标题。 另请注意最后一行。 它显式调用超类do_configure_event()方法。 这是因为它做了一些重要的工作。 尝试对此行添加注释以查看发生了什么。 调整窗口大小无法正常工作。 如果重写默认处理器,则可能会或可能不会调用超类方法。 就我们而言,我们必须这样做。

PyGTK 中的事件和信号 - 图1

图:配置信号

按钮的信号

以下示例显示了各种按钮信号。

buttonsignals.py

  1. #!/usr/bin/python
  2. # ZetCode PyGTK tutorial
  3. #
  4. # This program shows various signals
  5. # of a button widget
  6. # It emits a button-release-event which
  7. # triggers a released singal
  8. #
  9. # author: jan bodnar
  10. # website: zetcode.com
  11. # last edited: February 2009
  12. import gtk
  13. class PyApp(gtk.Window):
  14. def __init__(self):
  15. super(PyApp, self).__init__()
  16. self.set_title("Signals")
  17. self.set_size_request(250, 200)
  18. self.set_position(gtk.WIN_POS_CENTER)
  19. self.connect("destroy", gtk.main_quit)
  20. fixed = gtk.Fixed()
  21. self.quit = gtk.Button("Quit")
  22. self.quit.connect("pressed", self.on_pressed)
  23. self.quit.connect("released", self.on_released)
  24. self.quit.connect("clicked", self.on_clicked)
  25. self.quit.set_size_request(80, 35)
  26. fixed.put(self.quit, 50, 50)
  27. self.add(fixed)
  28. self.show_all()
  29. self.emit_signal()
  30. def emit_signal(self):
  31. event = gtk.gdk.Event(gtk.gdk.BUTTON_RELEASE)
  32. event.button = 1
  33. event.window = self.quit.window
  34. event.send_event = True
  35. self.quit.emit("button-release-event", event)
  36. def on_clicked(self, widget):
  37. print "clicked"
  38. def on_released(self, widget):
  39. print "released"
  40. def on_pressed(self, widget):
  41. print "pressed"
  42. PyApp()
  43. gtk.main()

一个按钮不仅可以发出一种信号。 我们与其中三个一起工作。 clickedpressedreleased信号。 我们还展示了事件信号如何触发另一个信号。

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

我们为所有三个信号注册回调。

  1. self.emit_signal()

在应用启动时,我们发出特定信号。

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

我们发出button-release-event信号。 它以Event对象为参数。 应用启动后,我们应该在控制台窗口中看到"released"文本。 当我们点击按钮时,所有三个信号都被触发。

阻止事件处理器

我们可以阻止信号处理器。 下一个示例显示了这一点。

block.py

  1. #!/usr/bin/python
  2. # ZetCode PyGTK tutorial
  3. #
  4. # This example shows how to block/unblock
  5. # a signal handler
  6. #
  7. # author: jan bodnar
  8. # website: zetcode.com
  9. # last edited: February 2009
  10. import gtk
  11. class PyApp(gtk.Window):
  12. def __init__(self):
  13. super(PyApp, self).__init__()
  14. self.set_title("Blocking a callback")
  15. self.set_size_request(250, 180)
  16. self.set_position(gtk.WIN_POS_CENTER)
  17. fixed = gtk.Fixed()
  18. button = gtk.Button("Click")
  19. button.set_size_request(80, 35)
  20. self.id = button.connect("clicked", self.on_clicked)
  21. fixed.put(button, 30, 50)
  22. check = gtk.CheckButton("Connect")
  23. check.set_active(True)
  24. check.connect("clicked", self.toggle_blocking, button)
  25. fixed.put(check, 130, 50)
  26. self.connect("destroy", gtk.main_quit)
  27. self.add(fixed)
  28. self.show_all()
  29. def on_clicked(self, widget):
  30. print "clicked"
  31. def toggle_blocking(self, checkbox, button):
  32. if checkbox.get_active():
  33. button.handler_unblock(self.id)
  34. else:
  35. button.handler_block(self.id)
  36. PyApp()
  37. gtk.main()

在代码示例中,我们有一个按钮和一个复选框。 当我们单击按钮并且复选框处于活动状态时,我们在控制台中显示"clicked"文本。 复选框从按钮clicked信号中阻止/取消处理器方法。

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

connect()方法返回处理器 ID。 此 ID 用于阻止和取消阻止处理器。

  1. def toggle_blocking(self, checkbox, button):
  2. if checkbox.get_active():
  3. button.handler_unblock(self.id)
  4. else:
  5. button.handler_block(self.id)

这些行使用适当的方法阻止和取消阻止回调。

PyGTK 中的事件和信号 - 图2

图:阻止回调

在 PyGTK 教程的这一章中,我们处理了信号。