PyQt4でuiファイルを直接読んで継承する
QtDesignerを使った開発
最近広く使われているというQt。これをPythonから使うのがPyQtです。フォームをQtDesignerで作ったときは.uiファイルに出力されますが、これをPythonから読み込むときは以下の2つの方法があるようです。
- pyuic4を使って.pyに変換して使う
- PyQt4.uic.loadUi()を使って.uiファイルを直接読む
パッケージをpy2exeなどを使って配布する際には.pyにしておかないと不都合があるみたいですが、開発中はいちいち変換するのも面倒なので、できれば.uiを直接読みたいところです。しかし、uiファイルを直接読んだ場合、フォームを継承して使うのがイマイチという問題があります(私が方法を知らないだけかも…)。
そこで、方法を調べていたところ以下の方法にたどり着きました。
- pyuic4 vs uic.loadUI - http://www.riverbankcomputing.com/pipermail/pyqt/2010-September/027970.html
これによると開発中は.uiを直接読みつつ、配布の際は.pyにしてしまうという方法が紹介されています。で、この方法はhgviewというプロジェクトで使われているそうなので、ソースを見て、私なりに実装し直してみました。hgviewではmix-inを使って問題を解決していましたが、同じ方法というのも芸がないのでメタクラスを使ってみました。
この記事内のコード片は、パブリックドメインということにしておきますので、使用される方の責任でご自由にお使いください。
ただし、まだPyQtを使い始めて日が浅いので不具合があるかもしれませんのでご了承下さい
メタクラスを使った実装
実装例:
import os, types from PyQt4 import QtCore, QtGui, uic class MyQtGuiMeta(QtCore.pyqtWrapperType): @staticmethod def should_rebuild(uifile, pyfile): return not os.path.isfile(pyfile) or ( os.path.isfile(uifile) and \ (os.path.getmtime(pyfile) < os.path.getmtime(uifile)) ) def __new__(cls, name, bases, attr): # ここで.uiファイルを作成して読み込む _path = os.path.dirname(__file__) uifile = os.path.join(_path, attr["_uifile"]) pyfile = uifile.replace(".ui", "_ui.py") if cls.should_rebuild(uifile, pyfile): # .uiファイルの方が新しければ_ui.pyファイルを作成 uic.compileUi(uifile, open(pyfile, "w")) try: # モジュールとして読み込む modname = os.path.splitext(os.path.basename(uifile))[0] + "_ui" modname = "qt4.%s" % modname mod = __import__(modname, fromlist=["*"]) classnames = [x for x in dir(mod) if x.startswith("Ui_")] if len(classnames) != 1: raise ValueError("Can't determine ui class to use in %s" % modname) ui_class = getattr(mod, classnames[0]) except ImportError: ui_class, base_class = uic.loadUiType(uifile) # ベースクラスにセット bases += (ui_class,) return QtCore.pyqtWrapperType.__new__(cls, name, bases, attr) class MyMainWindow(QtGui.QMainWindow): __metaclass__ = MyQtGuiMeta _uifile = "myfromage.ui" def __init__(self): super(MyMainWindow, self).__init__() self.setupUi(self) if __name__ == "__main__": app = QtGui.QApplication(sys.argv) wnd = MyMainWindow() wnd.show() sys.exit(app.exec_())
ここで定義しているMyQtGuiMetaクラスを実装であるMyMainWindow.__metaclass__に、.uiファイルを_uifileに指定すれば.uiファイルを読み込んでウィンドウが作成されます。なお、この例では.uiファイルはMyQtGuiMetaクラスを定義したファイルと同じディレクトリに配置するようにします。
コメント
コメントなどありましたらお願いします。