試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

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

*1:「pygobject」「python-gobject」「libpyglib2.0_0」などの名前でパッケージになっている

*2:いつまで使用できるかは分からないので、既に新しいバージョンのPyGObjectがインストールされていれば「glib」で書くほうがよいかも