C++
Qt
QtDay 25

QtのCustom Widget Pluginを斬る!

1. 初めに

クリスマスなので?リア充QtのCustom Widget Pluginをばっさり斬ってみたいと思います。

1.1. 開発環境

OSはWindowsとLinuxの2種類を扱います。(macOSは手元に環境がない為、対象外とさせていただきます。ごめんなさい。)
Qt SDK及びQt Creatorは、インストーラ版(MinGW版(Windows)/gcc版(Linux))と自分でコードをビルドした版の2種類。
2×2の組み合わせで4種類を対象とします。
Qt5.9.1とQt5.9.2が元ネタのQt Versionです。

2. Custom Widget Pluginって何?

Custom Widget PluginはQt Designer上において、QWidgetのサブクラスの操作をカスタマイズする機能です。
QWidgetのサブクラスをQt Designerで操作する機能は、Qtに2つ用意されています。
(1)格上げ(Proceed)する機能
(2)Custom Widget Plugin
の2つです。
この内、(1)格上げ(Proceed)する機能については、いろいろなサイトや書籍で扱っていますが、なぜか本稿の対象である(2)Custom Widget Pluginについては、Webサイトで検索してもほとんど情報が出てきません。そこで、本稿で取り上げてみることにしました。

Custom Widget Pluginは、QWidgetのサブクラスに、Extension機能を加えた動的ライブラリです。
このライブラリをQt Creator/Qt Designerが認識する場所(後述)に格納することによって、Qt Creator/Qt Designerが認識します。
Pluginが認識すると、Qt Designerのウィジェットボックスに「Input Widets」や「Display Widgets」と同レベルのグループ(グループ名は自分で設定可能)が表示され、この中に、自分で作成したオリジナルウィジェットが表示されます。もちろん中央部にドラッグできます。
※スクリーンショットが貼れればよかったのですが、環境整えるのが間に合いませんでしたorz

2.1. 特徴を比較する

まずこの2つの機能について、比較してみましょう。

格上げ Custom Widget Plugin
利用のしやすさ 簡単 難しい。また、Qt Designerで利用できるまでに時間が掛かる
Qt Designer上の見た目 QWidgetそのまま変わらない カスタマイズした見た目が反映される
誰向け? QWidgetのサブクラスのコードを持っている人向け。
自分でサブクラスを作って、自分で画面を作る
QWidgetのサブクラスのコードを持っていない第3者
QWidgetのサブクラスを使用する時に必要なファイル .hファイル及び.cppファイル .hファイル及び動的ライブラリファイル(.so or .dll)

2.2. どんな機能があるのか

2.2.1. Extension機能

Creating Custom Widget Extensionsで説明されています。
下記の4つの機能があります。

  • QDesignerTaskMenuExtension
    右クリックで表示されるTaskMenuをカスタマイズするExtensionです。
    例)Task Menu Extension Example

  • QDesignerContainerExtension
    カスタムの複数ページのコンテナを実装する場合に使用するExtensionです。Qt Designerで複数ページのコンテナプラグインのページを追加および削除できる拡張機能が用意されています。
    例)Container Extension Example

  • QDesignerMemberSheetExtension
    オリジナルのシグナル・スロットを追加するExtensionです。
    右クリック→「スロットへ移動...」選択時に表示されるシグナル一覧に、自作Qwidgetサブクラスのシグナルが追加で表示されます。

  • QDesignerPropertySheetExtension, QDesignerDynamicPropertySheetExtension
    プロパティ一覧の項目をカスタマイズするExtensionです。

2.3. 開発の流れ

Custom Widget Pluginを作成し、利用する流れは下記になります。

  • QWidgetのサブクラスを作成(QOpenGLWidgetのようなOpenGL機能付きでも可)
  • 「Custom Widget Plugin」をビルド(ライブラリが作成され、任意の場所にライブラリが移動) ※
  • 実行すると、対象アプリケーションが指定するよう指示される為、qtcreator.exe(Linuxならqtcreator.shも可)を指定して、別のQt Creatorを起動
  • 新しいuiファイル追記ウィジェットプロジェクトを作成
  • uiファイルをダブルクリックし、Qt Desginerを起動
  • 自作Qwidgetサブクラスを中央部にドラッグして操作
  • プロジェクトファイルに「LIBS += -L〇〇 -l〇〇」を追加
  • ビルドして実行

※Custom Widget Pluginのライブラリを格納する場所は、qquickwidget.dll(qquickwidget.so)が格納されている場所と同じ場所です。
2つ以上ある場合は、Toolディレクトリを含む方が正解です。

3. Custom Widget Pluginの問題

ここまでの話だと、Custom Widget Pluginっていう機能はおもしろそうじゃないか、オラわくわくしてきたぞ、と思われる方もいらっしゃるかもしれません。
Custom Widget Pluginの存在に気付いた時は当初、私もその一人でした。
しかし、実際に使用しようとすると・・・厳しい現実が待っていました。

3.1. サンプルが動かない

Qt SDKに用意されているCustom Widget Pluginの一番シンプルなサンプルであるWorld Time Clock Plugin ExampleをインストーラでインストールしたQt SDKで動作させようとすると動作しません。(他のサンプルも同様)
それぞれのOSについて見ていきます。

3.1.1. Windowsでの動作

結論から言いますと、Windows(MinGW)インストーラ版では、Custom Widget Pluginは動作しません。
これは、「Qt CreatorをビルドするコンパイラとCustom Widget Pluginをビルドするコンパイラが異なると動作しない」という制約に引っかかってしまう為です。
手元のQt5.9.2だと、Qt CreatorはVisualStudio2015でビルドされていますが、VisualStudioはインストールしていない環境なので、Qt SDKはMigGW版であり、Custom Widget Pluginをビルドする環境もMinGW版です。

解決策としては、

  • Qt Creatorのコードをダウンロードして自分でMinGWでビルドする
  • VisualStudioコンパイラ版のQt SDKを使用する。

のどちらかにするしか対応方法はないようです。
最初から全てコードを落としてきてビルドした環境であれば問題なく動作します。

3.1.2. Linuxでの動作

Linuxのインストーラ版は、サンプルコードそのままでは動作しませんが、プロジェクトファイルを書き換えれば動作します。
何が問題かというと、Qt独自のビルド環境変数「QT_INSTALL_PLUGINS」の示す先がQt Designerが認識する場所を指していないことが問題です。
自分でQtをビルドした場合は、「QT_INSTALL_PLUGINS」に正しい値がセットされるので問題は発生しません。
※インストーラ版と自分ビルド版では、ディレクトリ構成が異なります。自分ビルド版ではToolディレクトリは存在しません。従って、インストーラ化した時の何かにバグがあるのではないかと予想しています。

また、もう1つ別の観点で問題があります。
プロジェクトファイルの書き方についてです。
ここにあるように

target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target

のように記述すればよいとのことですが、これはQMakeをシェル上から実行した場合のみ有効となる記述方法で、Qt Creator上の操作からビルドしても上記の設定は認識してくれません。
そこで、代案として私が考えたのが、「DESTDIR」に直接Custom Widget Pluginライブラリを格納するパスを記述する方法です。
QtのVersionが上がった時に、パスがいちいち変わる可能性があるので、微妙と言えば微妙ですが、Qt Creator上で操作できるという観点において、公式サイトの記述よりましだと思っています。

3.2. プロパティExtensionが微妙すぎる

2.2.1.の説明で、あえてぼかして説明しましたが、QDesignerPropertySheetExtensionはQDesignerMemberSheetExtensionと異なり、プロパティを追加することはできません。
例えば、QPushButtonをQt Designer上に張り付けると、「QObject」、「QWidget」、「QAbstractButton」、「QPushButton」の各クラスのプロパティが全て表示されます。
この動作のイメージで、QWidgetのサブクラスのプロパティExtensionを作成したら、「QObject」、「QWidget」、「QWidgetのサブクラス」の3つのクラスのプロパティが表示されるイメージになると思いますが、残念ながらQDesignerPropertySheetExtensionのサブクラスに、自分で定義したプロパティしか表示できません。「QObject」、「QWidget」のプロパティを表示したかったら、「自分で定義する」しか方法がありません。
※これは、QDesignerPropertySheetExtensionのメンバ関数が全て純粋仮想関数なのが原因かと思います。(要は設計ミス)デフォルト実装はQDesignerPropertySheetExtensionの中でしておく方がよかったのではないと思います。Qt SDK側は恐らくd-pointer化したPrivateクラスでデフォルトの実装を行っているはずで、なぜか公開した側のクラスはかなり締め付けられています。

ちなみに、QObjectの「objectName」プロパティとQWidgetの「geometry」プロパティはQDesignerPropertySheetExtensionのサブクラスで再定義しておく必要があるようです。存在しないとQt Designerで変な挙動を起こします。
他にも必須プロパティがあるのか、Qtの中の人に問い合わせたこともありますが、知らないと回答されました。。。

3.3. Qt5.9.1までは・・・

Qt5.9.1までは、QDesignerMemberSheetExtensionとQDesignerPropertySheetExtensionを1つのPluginに組み込むとQDesignerMemberSheetExtensionが正しく動作しません。
http://bugreports.qt.io/browse/QTBUG-62936/
が原因らしいです。
Qt5.9.2では直っていますが、正直なぜ、こんなド正常の動作にバグがあるのか理解できません。
(他のQt SDKのサンプルはこれでもかという位リッチなのに)Custom Widget Pluginのサンプルは充実していないことから、窓際的な扱いをされているのでしょうか?

4. まとめ

いろいろと大変なCustom Widget Pluginですが、やっぱり、Qt Designer上でペタペタ貼り付けて画面を作るのは楽しいものです。
様々な障害を乗り越えて、「Custom Widget Pluginを使いたい!」と言ってくださる方がいらっしゃいましたら、ぜひコメントください。
Custom Widget Pluginの作り方の記事を書きたくなるかもしれません。

5. 最後に

今年のQt Advent Calendarは、当初なかなか枠が埋まらなくて、「読専」だった私もデビューしてしまいました。
調子に乗って3つも書いてしまいました。
来年も自分に続いてくれるNew Challengerをお待ちしています。

それでは、みなさま良いお年を!