どぶお/Pythonで遊ぼう!

PyQt4でuiファイルを直接読んで継承する  

QtDesignerを使った開発  

最近広く使われているというQt。これをPythonから使うのがPyQtです。フォームをQtDesignerで作ったときは.uiファイルに出力されますが、これをPythonから読み込むときは以下の2つの方法があるようです。

  1. pyuic4を使って.pyに変換して使う
  2. PyQt4.uic.loadUi()を使って.uiファイルを直接読む

パッケージをpy2exeなどを使って配布する際には.pyにしておかないと不都合があるみたいですが、開発中はいちいち変換するのも面倒なので、できれば.uiを直接読みたいところです。しかし、uiファイルを直接読んだ場合、フォームを継承して使うのがイマイチという問題があります(私が方法を知らないだけかも…)。

そこで、方法を調べていたところ以下の方法にたどり着きました。

これによると開発中は.uiを直接読みつつ、配布の際は.pyにしてしまうという方法が紹介されています。で、この方法はhgviewというプロジェクトで使われているそうなので、ソースを見て、私なりに実装し直してみました。hgviewではmix-inを使って問題を解決していましたが、同じ方法というのも芸がないのでメタクラスを使ってみました。

この記事内のコード片は、パブリックドメインということにしておきますので、使用される方の責任でご自由にお使いください。
ただし、まだPyQtを使い始めて日が浅いので不具合があるかもしれませんのでご了承下さい [huh]

メタクラスを使った実装  

実装例:

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クラスを定義したファイルと同じディレクトリに配置するようにします。

コメント  

コメントなどありましたらお願いします。