どんな時に
先ほど書いた「QTableViewにD&Dをしたい」で、QTableViewをカスタムウィジットDroppableTableViewにし、ファイルのドロップを可能にしました。 しかし、UI部品はすべてQt Designerで管理したいし、しかも.uiファイルをQUiLoaderで読み込んでいるので、カスタムウィジットにできないんじゃないか…と思いました。
結果問題なくできたので、その方法をまとめておきます。
Qt Designerでの操作
まず、カスタムウィジットにしたいウィジットを右クリックし、「格上げ先を指定」を押します。
新しく格上げクラス(いわば派生クラス)を作成する場合には、下側の「新しい格上げされたクラス」を使用します。 「格上げされたクラス名」のところに、実装したカスタムウィジット名を入力します。ヘッダファイルは自動的に入力が進みますが、PySideの場合は関係ないので完全無視で大丈夫です。 入力を終えたら「追加」から「格上げ」を押してください。
これで完成です。別のウィジットを、同じカスタムウィジットに格上げしたい場合は、同じ画面の上側「格上げされたクラス」に追加されているので、そちらを選択してから「格上げ」で大丈夫です。
Python側
さて、ここからが肝です。そのままQUiLoaderで.uiファイルを読み込もうとすると、
"QFormBuilder was unable to create a custom widget of the class 'DroppableTableView'; defaulting to base class 'QTableView'."
という警告メッセージが出力されます。当然、新たに作成したカスタムウィジットも使われません。
QUiLoaderを継承する
調べてみると、c++ - QUiLoader: requirements for loading .ui file with custom widgets? - Stack Overflowという記事がヒットします。 こちらによれば、QUiLoaderを継承し、用意したカスタムウィジットを作成するときの処理をifで分岐して、作ってしまおう、という解決方法でした。 このまま同じように実装すると、あまりきれいな感じではないかもしれないですが、少し改造してモジュール化すれば相当スマートになると思います。 この改造は…また後日やることとして、とりあえずこの記事の通りに実装してみます。
from PySide2.QtUiTools import QUiLoader
class CustomQUiLoader(QUiLoader):
def createWidget(self, className, parent=None, name=''):
if className == 'DroppableTableView':
ret = DroppableTableView(parent)
ret.setObjectName(name)
return ret
return super().createWidget(className, parent, name)
上から4行目あたり、「自分がカスタムで作成したウィジット名でif分岐をかけて、カスタムウィジットであればインスタンスを作って名前をセットして返す、そうでなければデフォルトにお任せ」というフローになっています。
あとは、.uiファイルを読み込むときに、今までQUiLoaderを使っていた部分に、
...
self.ui = CustomQUiLoader().load('test.ui', parent) # parentは省略できる
...
こんな具合にCustomQUiLoaderを差し替えてあげれば、正しく読み込むことができます。
お疲れ様でした。