各種スクリプトのマルチスレッドでQtを使用する
Qtは多くのウィンドウシステムの例にもれずイベントドリブンのGUIを採用しているので、ユーザが何かを入力するまで待っているのが基本となる。
一般的なツールであればそれだけで良いが、ゲームなどのようにユーザの入力を待ちながら別の処理を行いたいこともある。
そんなときは、スレッドを利用するのが最も楽である。
以下に各種スクリプトによる簡単な例を示す。
- 例の内容
- 1秒間に1回「count n」と表示し続けながら、ボタンが押されたら「Push!」と表示する。
Rubyの場合
#!/usr/bin/ruby require "qte" include Qte require "qpe" include Qpe class MainWindow < QWidget def initialize(*args) super vb = QVBoxLayout.new(self) b1 = QPushButton.new("Push!", self) vb.addWidget(b1) @e1 = QMultiLineEdit.new(self) vb.addWidget(@e1, 1) connect(b1, QSIGNAL("clicked()"), "b1clicked") Thread.new { count = 1 while true sleep(1) append("count #{count}") count += 1 end } end def append(text) @e1.append(text) @e1.setCursorPosition(@e1.numLines - 1, 0); end def b1clicked append("Push!") end end def main(argv) app = QPEApplication.new(argv) mw = MainWindow.new mw.setCaption(argv[0]) app.showMainDocumentWidget(mw) mw.show return app.exec end main([$0] + ARGV)
Pythonの場合
!/usr/bin/python import sys from qt import * from qtpe import * from threading import * from time import * class MainWindow(QWidget): SUPER = QWidget def __init__(self, *args): self.SUPER.__init__(self, *args) vb = QVBoxLayout(self) b1 = QPushButton("Push!", self) vb.addWidget(b1) self.e1 = QMultiLineEdit(self) vb.addWidget(self.e1, 1) self.connect(b1, SIGNAL("clicked()"), self.b1clicked) self.closed = False self.thr = Thread(target = self.thread_proc) self.thr.start() def append(self, text): self.e1.append(text) self.e1.setCursorPosition(self.e1.numLines() - 1, 0) def b1clicked(self): self.append("Push!") def thread_proc(self): count = 1 while not self.closed: sleep(1) self.append("count %d" % count) count += 1 def closeEvent(self, ce): self.closed = True self.thr.join() self.SUPER.closeEvent(self, ce) def main(argv): app = QPEApplication(argv) mw = MainWindow() mw.setCaption(argv[0]) app.showMainDocumentWidget(mw) mw.show() app.exec_loop() main(sys.argv)
Perlの場合
とても残念だが、Perlではスレッドが5.8以降から実装されているため、Perl 5.6.1向けPerlQt/Embeddedでは上記のRubyやPythonのようなコードが書けない。
そこで、Windows 3.1でやっていたように、イベントループを回しながら処理を行う方法を紹介する。
#!/usr/bin/perl package MainWindow; use strict; no strict 'refs'; use Qt; use Time::HiRes qw(usleep); our @ISA = qw(Qt::Widget); use Qt::slots qw( b1clicked() ); sub new { my $self = shift->SUPER::new(@_); my $vb = new Qt::VBoxLayout($self); my $b1 = new Qt::PushButton('Push!', $self); $vb->addWidget($b1); $self->{e1} = new Qt::MultiLineEdit($self); $vb->addWidget($self->{e1}, 1); $self->connect($b1, 'clicked()', 'b1clicked()'); return $self; } sub append { my $self = shift; my $text = shift; my $e1 = $self->{e1}; $e1->append($text); $e1->setCursorPosition($e1->numLines() - 1, 0); } sub b1clicked { my $self = shift; $self->append("Push!"); } sub thread_modoki { my $self = shift; my $sec = time(); my $count = 1; while (!$app->{quit}) { $app->processEvents(); usleep(1); my $now = time(); next if $now == $sec; $sec = $now; $self->append("count $count"); $count++; } } sub super { my $self = shift; my $name = shift; $name = "$ISA[0]::$name"; &$name($self, @_); } sub closeEvent { my $self = shift; $app->{quit} = 1; $self->super('closeEvent', @_); } package main; use strict; use Qt;; $app->setTextCodec("eucJP"); my $mw = new MainWindow; $mw->setCaption($0); $app->showMainDocumentWidget($mw); $mw->show(); $mw->thread_modoki();
usleepはマイクロ秒単位でスリープする関数である(Time::HiResを参照)。
1マイクロ秒の僅かなスリープにより、CPUの空き時間が0%になることを防ぐ(実測では40%空き)。