[[どぶお/Pythonで遊ぼう!]]
 
 * PyQt4でuiファイルを直接読んで継承する [#kafe28fa]
 ** QtDesignerを使う [#x7bc4b56]
 ** QtDesignerを使った開発 [#x7bc4b56]
 最近広く使われているという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というプロジェクト>http://www.logilab.org/project/hgview]]で使われているそうなので、ソースを見て、私なりに実装し直してみました。hgviewではmix-inを使って問題を解決していましたが、同じ方法というのも芸がないのでメタクラスを使ってみました。
 ~&color(blue){この記事内のコード片は、パブリックドメインということにしておきますので、使用される方の責任でご自由にお使いください。};~
 &color(red){ただし、まだPyQtを使い始めて日が浅いので不具合があるかもしれませんのでご了承下さい};&huh;
 
 ** メタクラスを使った実装 [#e5668302]
 実装例:
  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クラスを定義したファイルと同じディレクトリに配置するようにします。
 
 ** コメント [#dbaeee8f]
 コメントなどありましたらお願いします。
 
 #comment