QtのGUIがなんか重いという問題
Qtを始めてデバッグなんかをしていたら、どうも昔に比べて思いなーと思っていた。それはメニューバーの反応が遅かったり、Windowサイズを調整すると再描画がされなかったり、というような現象でJavaのGUIアプリケーションを触っているようなイメージだった。
しかし、サンプルのアプリケーションではさくさく動くし見た目もC#のWPFアプリケーションのようなモダンなデザインなのだ。
どうしてだろうとmain.cppやらmain.qmlやら設定ファイルやらを見比べていたら、一つの関数が違っていた。
// #include <QGuiApplication> #include <QApplication> #include <QQmlApplicationEngine> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // ここを // QGuiApplication app(argc, argv); // こうする QApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QLatin1String("qrc:/main.qml"))); return app.exec(); }
するとこれが、
こうなった。見た目ではあまり変化がないように思うかもしれないが、レスポンスが向上しウィンドウサイズの変更に機敏に反応してくれるようになった。
なぜだろうと、調べてみるとQApplicationはQGuiApplicationを継承していて、Wigetsの操作を提供していると書いていた(ここ)。
使用しているMenuBarやTreeViewがWidgetsということだろうか?それでも、遅いながらも動いているのはおかしいということにならないだろうか。main関数内のたった1行だけを変更するだけで、このように変化する理由がわからない。
参考
QFileSystemModelを見る(QML)
Qtのモジュールはドキュメントがしっかりしてるけど、
使うとなるといまいちどうやって使えばいいのかわからない……
いちいち日本語にまとめるのは時間がかかりすぎるんだけど
勉強し始めということで、ファイルを扱う"QFileSystemModel"についてだけとりあえず見ていく。
QMLとC++の連携がいまいちわからないから、これを通して学びたいところ。
基本
QFileSystemModelはこのクラスを基底として継承し拡張したクラスを用いることがほとんどあるが、継承せずとも使用することはできる。
main関数内でこのように書く。これメモリ解放してないけどいいんだろうか。QMLのTreeViewでファイル一覧を読み込むために必要なオブジェクトはここで渡す。
//QML側でfilesystemmodelという名前でQFileSystemModelを読み込めるようにバージョン1.0として登録 qmlRegisterUncreatableType<QFileSystemModel>("filesystemmodel", 1, 0, "FileSystemModel", "Cannot create a FileSystemModel instance."); //オブジェクト生成 QFileSystemModel *model = new QFileSystemModel(); //ディレクトリを読み込む これをしないと読み込まれない model->setRootPath(QDir::homePath()); //任意でファイルのフィルタリングができる(ここでは現ディレクトリ「.」を排除している) model->setFilter(QDir::NoDot | QDir::AllDirs | QDir::Files); //QMLで読み込めるように追加 engine.rootContext()->setContextProperty("fileSystemModel", model); engine.rootContext()->setContextProperty("rootPathIndex", model->index(model->rootPath()));
QMLにて読み込む。setContextPropertyで登録した変数が使用できる。modelはきっとQFileSystemModel以外にもあって、標準化されているんだろうなー。
property alias filelist: filelist TreeView { model: fileSystemModel rootIndex: rootPathIndex }
qmlRegisterUncreatableTypeで登録したクラスを使うときは、このように指定した名前、バージョンでimportする。 関係ないけど、onActivatedで急にindex変数が使えることに違和感を覚える。TreeViewのドキュメント見ると書いてあるんだけどさ。
import filesystemmodel 1.0 filelist.onActivated : { var url = fileSystemModel.data(index, FileSystemModel.~~~~~~~) Qt.openUrlExternally(url) }
これで最低限のファイル一覧を読み込むことができる。
機能拡張
しかし、これではファイルリストが表示されるだけで、ファイルサイズや最終更新日などの情報は得られない。そこで、QFileSystemModelを継承して独自の機能を追加する。
もちろん継承したクラスで実装した公開メソッドはqmlRegisterUncreatableTypeによってQMLから使えるから、それを活用して情報を得るようにする。
これについては、Qtの公式サンプルを見れば良いと思う。というかほとんどここの情報から学んだから必見。main.cppに継承したクラスが実装されていて、まとまっている。
Qt Quick Controls - File System Browser Example
まとめ
QMLとC++のクラスを紐付けることによってデータの受け渡しをするみたいだ。これはRubyの拡張ライブラリみたいなバインディングと同じなんだね。
C++ではQMLで欲しい情報を得るために継承によって公開メソッドを作り、QML側でメソッドによりset,getをするというのが基本。QML側ではidをコンポーネントごとに割り当てられるからオブジェクト間の連携は楽にできそう。
Qtはバージョンによる違いとかQML前後の情報で混乱しがちだなあ。
参考
Qtでの".ui.qml"と".qml"
Qt CreatorではQMLを記述するためにファイルを新規作成すると、
.ui.qmlと.qmlが自動生成される。
.ui.qml
動作ではなくUI部分だけを作成するものである。
自動生成すると「main.qml」なら「MainForm.ui.qml」というような名前になる。
ここにスクリプト(onClicked {}とか)を書くとエラーは吐くがビルドは通る。
しかし、GUIによるデザインができなくなる。
いずれにせよ赤い下線が表示されるのは気持ち悪いし、デザインと動作がひとつになるのはメンテナンスに欠ける気もするので、ここには書かない。
.qml
動作部分を記述するファイルである。ここに主にスクリプトを書いていくことになる。
いままで、この辺をまったく知らないので、全部.ui.qmlに書いてしまっていた。
しかもわざわざ.qmlを削除してまで…… 馬鹿だ。
参考
QObjectを継承するとエラーでビルドできない [undefined reference to vtable for ~]
これで一日が潰れたorz
vtableというのはQt特有のものではなく、
仮想関数テーブル(virtual method table)というものであるらしい。
調べると(wikipeida)ポリモーフィズムを実現するために内部で実装されていることだけわかった。
コンストラクタのオーバライドの際にエラーが発生しているので、元々のクラスが認識されていないのだろうか???
原因がよくわからないが、解決方法はわかった。
一度、qmakeをすることだ。
ビルド(B) -> qmakeの実行
この作業を一度行うと、その後はエラーが出てこなかった。
Qtについての理解が薄いから何が何だかわからないよ。
参考サイトを見ると、この現象についての詳細が書かれている。