PyGTK上のタイマー処理について
PyGTK上で、ある時間が経過したときに何かの処理をさせたい、あるいは一定間隔で繰り返し処理をさせたいといったときにはタイマーの処理を行うことで実現できるのだが、まだまとめていなかったのでここで扱うことにする。
(2009/8/10)コード中の短い範囲内における実行時間を測定するにはtimeitモジュールを用いる。ここで扱っているのはGLibのメインループの仕組み上で動作するもの。
使用する関数について
- gobject.timeout_add()で実行までの時間/実行間隔となるミリ秒数と呼び出す関数(タイムアウト関数)名を指定することでタイマーが設定され、その時間が経過すると指定した関数が呼ばれる
- 関数側では戻り値にTrueを指定すると処理が繰り返され、Falseを指定するとその後は実行されなくなる・return文がない場合もFalse指定時と同様
- gobject.timeout_add()の戻り値を保存しておきgobject.source_remove()でこれを指定することでもタイマーが解除できる
例
簡単な例
下のコードを実行し、ボタンを押してから5秒が経過するとダイアログが出る。処理は一度だけ行われる。
#! /usr/bin/python # -*- encoding: utf-8 -*- import sys try: import pygtk pygtk.require("2.0") except: pass try: import gtk import gobject except: print >> sys.stderr, "Error: PyGTK is not installed" sys.exit(1) class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) # ショートカットキー(アクセラレータ) self.accelgroup = gtk.AccelGroup() self.add_accel_group(self.accelgroup) # メニュー項目 self.item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accelgroup) self.menu_file = gtk.Menu() self.menu_file.add(self.item_quit) self.item_file = gtk.MenuItem("_File", True) self.item_file.set_submenu(self.menu_file) self.menubar = gtk.MenuBar() self.menubar.append(self.item_file) # 開始ボタン self.button = gtk.Button("_Start") # 垂直ボックス self.vbox = gtk.VBox() self.vbox.pack_start(self.menubar, False, False, 0) self.vbox.pack_start(self.button, True, True, 0) # ウィンドウ self.set_title("test") self.set_size_request(80, 50) self.add(self.vbox) # シグナル self.connect("delete_event", gtk.main_quit) self.button.connect("clicked", self.on_button_clicked) self.item_quit.connect("activate", gtk.main_quit) def on_button_clicked(self, widget): """ ボタンが押されたときの処理 """ # タイマーを設定 # widgetは追加のユーザデータとして渡している gobject.timeout_add(5000, self.timeout, widget) widget.set_sensitive(False) # ボタンを操作できなくする def timeout(self, user_data): """ 設定されたタイマーが時間になると実行される処理 """ dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE, message_format="Time is up!") dlg.set_title("timeout") dlg.run() dlg.destroy() widget = user_data widget.set_sensitive(True) # ボタンの状態を戻す class TimerTest: def main(self): win = MainWindow() win.show_all() gtk.main() if __name__ == "__main__": app = TimerTest() app.main()
タイムアウト関数の戻り値のテスト
上のコードと似ているが、カウントダウン中にボタンに残り秒数を表示する。タイムアウト関数を繰り返し呼び出していて、残り時間に応じて戻り値(つまり、この後も繰り返すかどうか)を変えている。
#! /usr/bin/python # -*- encoding: utf-8 -*- import sys try: import pygtk pygtk.require("2.0") except: pass try: import gtk import gobject except: print >> sys.stderr, "Error: PyGTK is not installed" sys.exit(1) class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) # ショートカットキー(アクセラレータ) self.accelgroup = gtk.AccelGroup() self.add_accel_group(self.accelgroup) # メニュー項目 self.item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accelgroup) self.menu_file = gtk.Menu() self.menu_file.add(self.item_quit) self.item_file = gtk.MenuItem("_File", True) self.item_file.set_submenu(self.menu_file) self.menubar = gtk.MenuBar() self.menubar.append(self.item_file) # 開始ボタン self.button = gtk.Button("_Start") # 垂直ボックス self.vbox = gtk.VBox() self.vbox.pack_start(self.menubar, False, False, 0) self.vbox.pack_start(self.button, True, True, 0) # ウィンドウ self.set_title("test") self.set_size_request(80, 50) self.add(self.vbox) # シグナル self.connect("delete_event", gtk.main_quit) self.button.connect("clicked", self.on_button_clicked) self.item_quit.connect("activate", gtk.main_quit) def on_button_clicked(self, widget): """ ボタンが押されたときの処理 """ # 残り時間 self.remain = 5 widget.set_label(str(self.remain)) # タイマーを設定 # widgetは追加のユーザデータとして渡している gobject.timeout_add(1000, self.timeout, widget) widget.set_sensitive(False) # ボタンを操作できなくする def timeout(self, user_data): """ 設定されたタイマーが時間になると実行される処理 """ widget = user_data self.remain -= 1 widget.set_label(str(self.remain)) if self.remain > 0: return True # 繰り返す else: dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE, message_format="Time is up!") dlg.set_title("timeout") dlg.run() dlg.destroy() widget.set_label("_Start") widget.set_sensitive(True) # ボタンの状態を戻す return False # 繰り返さない class TimerTest2: def main(self): win = MainWindow() win.show_all() gtk.main() if __name__ == "__main__": app = TimerTest2() app.main()
gobject.source_remove()による解除
カウントダウン中にボタンを押すとそこでタイマーが解除され、ダイアログが出る。
#! /usr/bin/python # -*- encoding: utf-8 -*- import sys try: import pygtk pygtk.require("2.0") except: pass try: import gtk import gobject except: print >> sys.stderr, "Error: PyGTK is not installed" sys.exit(1) class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) # タイマーID self.timeout_id = None # ショートカットキー(アクセラレータ) self.accelgroup = gtk.AccelGroup() self.add_accel_group(self.accelgroup) # メニュー項目 self.item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accelgroup) self.menu_file = gtk.Menu() self.menu_file.add(self.item_quit) self.item_file = gtk.MenuItem("_File", True) self.item_file.set_submenu(self.menu_file) self.menubar = gtk.MenuBar() self.menubar.append(self.item_file) # 開始ボタン self.button = gtk.Button("_Start") # 垂直ボックス self.vbox = gtk.VBox() self.vbox.pack_start(self.menubar, False, False, 0) self.vbox.pack_start(self.button, True, True, 0) # ウィンドウ self.set_title("test") self.set_size_request(80, 50) self.add(self.vbox) # シグナル self.connect("delete_event", gtk.main_quit) self.button.connect("clicked", self.on_button_clicked) self.item_quit.connect("activate", gtk.main_quit) def on_button_clicked(self, widget): """ ボタンが押されたときの処理 """ if self.timeout_id == None: # 残り時間 self.remain = 5 widget.set_label(str(self.remain)) # タイマーを設定 # widgetは追加のユーザデータとして渡している self.timeout_id = gobject.timeout_add(1000, self.timeout, widget) else: # タイマーの解除 gobject.source_remove(self.timeout_id) dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE, message_format="Timer removed!") dlg.set_title("remove timer") dlg.run() dlg.destroy() widget.set_label("_Start") self.timeout_id = None def timeout(self, user_data): """ 設定されたタイマーが時間になると実行される処理 """ widget = user_data self.remain -= 1 widget.set_label(str(self.remain)) if self.remain > 0: return True # 繰り返す else: dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE, message_format="Time is up!") dlg.set_title("timeout") dlg.run() dlg.destroy() widget.set_label("_Start") self.timeout_id = None return False # 繰り返さない class TimerTest3: def main(self): win = MainWindow() win.show_all() gtk.main() if __name__ == "__main__": app = TimerTest3() app.main()
関連: 関数がglibモジュールに移行?
先述の関数はPyGObject*1のバージョン2.16系では「glib」モジュールになっていてglib.timeout_add()やglib.source_remove()となっているが、「gobject」モジュールからも使用することはできる。*2
また、バージョン2.16系では秒単位で扱えるglib.timeout_add_seconds()もあり
[引用] http://web.archive.org/web/20110518173053/http://developer.gnome.org/pygobject/stable/glib-functions.html#function-glib--timeout-add-seconds より
the function should cause less CPU wakeups, which is important for laptops' batteries.
省電力性にも優れているようだ。
使用したバージョン:
- Python 2.5.2
- PyGObject 2.15.4
- PyGTK 2.13.0