PyQtについて
(最終更新:2003年3月17日)


PythonでGUI付きのプログラムを作ると言えば

1にTkinter (Tcl/Tk)
2にPython for Delphi
3にPyQt

というところですが(当社比。根拠無し。というか、自分が経験した順番...^^;)ここではPyQtについて書いていきます。

Qtと言うと、ノルウェイのTROLLTECHという会社の開発した、主にLinux界でよく使われているGUI部品等を持ったツールキットです。 Linuxの2大デスクトップ環境のひとつである KDE はQtをベースに作られているほか、新しいところではSHARPのLinux Zaurusでも採用されています。(こちらはQtopia)
Linuxの2台デスクトップ環境のもうひとつである GNOME のベースとなっているGTKがGPL準拠のフリーソフトであるのに対して、Qtは基本的には商用製品です。 ただしWindows用には Non-Commercial版、X11用には free版も用意されています。 ちなみにWindows用Non-Commercial版はQt2.3、X11用free版は3.1.2(この項執筆時)で、バージョンがちょっと違っているんですよねー...^^;)

もうひとつ、PyQtというのがあります。
こちらは英国のRiverbankで開発されたPython用Qtバインディングというところでしょうか。 以前はフリーだったんだけど、最近見たらCommercial版とNon-Commercial版に別れてましたね。うむ...^^;)
執筆時点での最新バージョンは3.5でした。

GUIはどれがいい?

いや、迷ってしまいますよね、ほんとに。 Pythonに標準で入っているという意味ではTkinterがいちばん身近。 Pythonコード書くだけで動かせてしまう、というところもよし。 ただ、どーもTkはわかりづらい、Tcl/Tkの解説はいっぱいあるけど、これをTkinterではどう書けばよいのか、試行錯誤するのが面倒くさい、という点はありますね。 あと、どうにも、なんて言いますか、インタフェースがカッコよくない(これは好みですけどね...^^;)

一方PyQtは、さすがモダンなツールキットだけあって、いろんなところが洗練されています。 これは見てくれだけではなく、SLOT/SIGNALの考えといい、GUI部品以外にも用意された様々なクラスといい(あっ、この辺はPyQtではなくQtのよいところでした)、よくできてるなー、と思います。 Pythonコードひとつで書けてしまうところはTkinter同様。 ドキュメントに関しては基本的にはTkinterと同じく、あまり無いのですが、でもPyQtではGUI Programming with Python: QT Editionという本をなんとWebで読めてしまうところがすごい。

ただし上の2つはPythonコードひとつで書けるとはいえ、実行環境としてPythonが(PyQtの場合は他にQtおよびPyQtも!)必要であり、作ったプログラムを配布する段では面倒ですね。 一方、Python for Delphi。これはGUIにDelphiが使えますから、その方面での情報はたーくさんあります。 まあ配布という意味では、これも.exeのバイナリだけでなくPythonコード、必要ライブラリ等も含める必要があるわけですが、でも自分の.exe内で自分のところに置いたPythonコード等を参照するよう設定してしまえばよいわけなので(インストール後のファイル数は多くなりますが)制約はあまり大きくないように思います。 ただし、Delphiの世界とPythonの世界の両方があるので、いろいろ面倒くさいことも出てきます。
ま、そういうわけでどれも一長一短と言った感じがしないでもないですね...^^;)

で、Python for Delphiについては、既に書いていますから、ここではDelphiやVBに似たQt DesignerというGUI構築ツールを持つQt、というかPyQtについて書いていきます。
(え?ここまでが前置き?ながーい...^^;)

Qt Designer、いいんだけど...

上にも書いたようにQtをインストールすると、Qt DesignerというGUI作成ツールもインストールされます。 これ、見た目はDelphiやVBなんかに似た感じで、なかなかよさげです。 でも、メニューから選択できるコンポーネント(と呼んでしまってよいのか?)が少なすぎる、Qtの特徴であるSLOT/SIGNALを、ここの中でどう扱えばいいのかわからない(それはドキュメント読まない自分が悪い...^^;)、あと決定的なのは、日本語入れられるんだけど、その後の処理によって化け化けになってしまう、というのがよくない点。
Qtのクラスライブラリって、もっとたーくさんあるのになぜかDesignerから選べるのって、ほんの少ししかないんですよね。 たとえばQCanvasとかQPixmapとかも、ここから選べてほしいわけですよ。
あと化け化け問題について。
Qt Designerでは作成したGUIをXMLに似た(そのもの?).ui というファイルで保存します。 これをuic(Qtの場合)またはpyuic(PyQtの場合)というコマンドでコンパイルして使います。 問題は、ボタンテキスト等で日本語使うと、ここのあたりでエラーが出たり、エラーは出なくても実行して表示されたGUIが文字化けしてしまったりするということです。
で、ちょっと考えてみました。 とりあえずQtの方は忘れることにしてPyQtでの対策作ってみました。
pyuic でPythonコードに変換するときに日本語を使っていないものに関しては

self.TextLabel2.setText(self.tr("TEXT1"))

のようになるのですが、日本語を使ったものでは

self.TextLabel1.setText(self.tr(QString.fromUtf8("テキスト1")))

となります。(文字コードとしてはUTF8が使われている)
QObject.tr は単なる文字列を QStringにして返してくれるので、日本語を入れた場合に QString.trに素のテキストではなくQStringを与えているのが間違いです。 要は self.tr を取っちゃえばOKということでした。
でもこれって pyuic のバグではないかな???

なーんてこと考えながら、よーくpyuicのコマンドラインオプションを見てみると、-trなんてオプションが見あたりました。 あ、これでなんとかなるかも...
で、考えついでにPyQtで作ってみました。 題して「pyuic_helper」。コードはこんな感じです。
(注意:実際のソースは漢字コードに UTF-8 が使われていますが、ここではシフトJISになっています。)

   1: # Form implementation generated from reading ui file 'pyuic_helper.ui'
   2: #
   3: # Created: Fri Mar 7 13:36:05 2003
   4: #      by: The Python User Interface Compiler (pyuic)
   5: #
   6: # WARNING! All changes made in this file will be lost!
   7: 
   8: import os
   9: import re
  10: import sys
  11: from qt import *
  12: 
  13: def wrap_tr(s):
  14:   if type(s) == type(''):
  15:     s = QObject.tr(s)
  16:   return s
  17: 
  18: class pyuic_helper_Form(QDialog):
  19:   def __init__(self,parent = None,name = None,modal = 0,fl = 0):
  20:     QDialog.__init__(self,parent,name,modal,fl)
  21: 
  22:     if name == None:
  23:       self.setName('Form1')
  24: 
  25:     self.resize(356,163)
  26:     self.setCaption(wrap_tr("pyuic helper"))
  27: 
  28:     self.TextLabel1 = QLabel(self,'TextLabel1')
  29:     self.TextLabel1.setGeometry(QRect(20,10,75,12))
  30:     self.TextLabel1.setText(wrap_tr(QString.fromUtf8("入力 ui ファイル")))
  31: 
  32:     self.TextLabel2 = QLabel(self,'TextLabel2')
  33:     self.TextLabel2.setGeometry(QRect(20,70,78,12))
  34:     self.TextLabel2.setText(wrap_tr(QString.fromUtf8("出力 py ファイル")))
  35: 
  36:     self.InputFileName = QLineEdit(self,'InputFileName')
  37:     self.InputFileName.setGeometry(QRect(10,30,250,22))
  38: 
  39:     self.OutputFileName = QLineEdit(self,'OutputFileName')
  40:     self.OutputFileName.setGeometry(QRect(10,90,250,22))
  41: 
  42:     self.InputReferButton = QPushButton(self,'InputReferButton')
  43:     self.InputReferButton.setGeometry(QRect(270,30,80,22))
  44:     self.InputReferButton.setText(wrap_tr(QString.fromUtf8("参照...")))
  45:     self.InputReferButton.setAutoDefault(1)
  46:     self.InputReferButton.setDefault(0)
  47: 
  48:     self.OutputReferButton = QPushButton(self,'OutputReferButton')
  49:     self.OutputReferButton.setGeometry(QRect(270,90,80,22))
  50:     self.OutputReferButton.setText(wrap_tr(QString.fromUtf8("参照...")))
  51:     self.OutputReferButton.setAutoDefault(1)
  52: 
  53:     self.ExecButton = QPushButton(self,'ExecButton')
  54:     self.ExecButton.setGeometry(QRect(180,130,80,22))
  55:     self.ExecButton.setText(wrap_tr(QString.fromUtf8("実行")))
  56:     self.ExecButton.setAutoDefault(1)
  57: 
  58:     self.CloseButton = QPushButton(self,'CloseButton')
  59:     self.CloseButton.setGeometry(QRect(270,130,80,22))
  60:     self.CloseButton.setText(wrap_tr(QString.fromUtf8("閉じる")))
  61: 
  62:     self.connect(self.InputReferButton, SIGNAL('clicked()'), self.InputFileOpen)
  63:     self.connect(self.OutputReferButton, SIGNAL('clicked()'), self.OutputFileOpen)
  64:     self.connect(self.ExecButton, SIGNAL('clicked()'), self.Exec)
  65:     self.connect(self.CloseButton, SIGNAL('clicked()'), self.close)
  66: 
  67:     self.InputFile = None
  68:     self.OutputFile = None
  69: 
  70:   def InputFileOpen(self):
  71:     name = QFileDialog.getOpenFileName(QString.null, QString('ui file (*.ui);;All files (*.*)'))
  72:     self.InputFileName.setText(name)
  73: 
  74:   def OutputFileOpen(self):
  75:     defname = ''
  76:     name = str(self.InputFileName.text())
  77:     if name != None:
  78:       defname = re.sub('.ui$', '.py', name)
  79:     name = QFileDialog.getSaveFileName(QString.fromUtf8(defname), QString('py file (*.py);;All files (*.*)'))
  80:     self.OutputFileName.setText(name)
  81: 
  82:   def Exec(self):
  83:     cmd = 'C:/Python22/pyuic.exe'
  84:     input = str(self.InputFileName.text())
  85:     output = str(self.OutputFileName.text())
  86:     args  = (cmd, '-tr', 'wrap_tr', '-o', output, input)
  87:     os.spawnv(os.P_WAIT, cmd, args)
  88:     data = open(output).read()
  89:     f = open(output, 'w')
  90:     f.write('def wrap_tr(s):\n  if type(s) == type(\'\'):\n    s = QObject.tr(s)\n  return s\n\n')
  91:     f.write(data)
  92:     f.close()
  93: 
  94: if __name__ == '__main__':
  95:   a = QApplication(sys.argv)
  96:   w = pyuic_helper_Form()
  97:   a.setMainWidget(w)
  98:   w.show()
  99:   a.exec_loop()
 100: 
 101: 

使い方としては、Qt Designerで作成したUTF-8コードの日本語入りの .ui ファイル及び出力のPythonコードファイル名を指定して実行すれば、pyuicを実行して、さらに化け化け部分を直してくれます。
まあ、pyuicコマンドをフルパスで埋め込んでいたり、エラー対策がほとんどなかったり、はっきり言って、やっつけで作った手抜き作ではありますが、もしも使ってみようと思う奇特な方がいらっしゃるようであれば、こちらからどうぞ。
pyuic_helper.pyw

一応、簡単ではありますが、PyQtのサンプルとしてもお使いいただけるかと...^^;)