春休み突入、わたすけです。
皆さん、Qtをご存知でしょうか?KDE Plasmaに用いられているアレです。
最近、Qtを使って色々プログラムを書くことが多くなってきたので、今回はその途中で見つけた小ネタみたいなものを紹介していきたいと思います。
ちなみに記事のネタバレをすると、 ”一言で言えば「ドキュメントを読め」” です。
注意
Qt 5.15.2で検証しています。PythonではなくC++です。Qt、検索してるとやけにPython
知っている人からしたら当然のことばかりかもしれないし、普通に間違ってる可能性があります。
Qtなにもわからないので、ぜひ何か教えて下さい。
また、実行時のスクショはAlter Linux on WSLでビルドし、Windows側にVcXSrvを構築して撮っています。WSLでGUIソフト開発はとても快適で良いですね。もしやろうと思っているのであれば、絶対にファイアウォールの設定を間違えないように注意してください。
Qtのつかいかた
ぼんやりとした見出しですね。よくないです。
Qtを用いたプログラムを書くとき、ネットにあるのは大抵Qt Creatorを前提とした記事です。Qt Creator、便利ですよね。ただ自分はあまり好きではない(というよりVSCodeのほうが好き)なので、Qt Creatorが作ってくれるようなテンプレートに頼らず、cmake等で1から書いています。
自分が作ったQtのテンプレートはここ(GitHub)にあるので割愛するとして、ここではそのテンプレートを作る時に色々躓いた部分というか、やらないといけないのにやってなかった部分を紹介します。
mainかどこかでQCoreApplication系のオブジェクトを作る
以下はQt Creatorが作ってくれるmain.cppです。
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
QApplication
がありますね。これはQGuiApplicationを継承したクラスで、そのQGuiApplicationはQCoreApplicationを継承したクラスです。
これが何なのかというと、要するにイベントループをしてくれるものです。CUIアプリケーション開発の場合はQCoreApplication、QWidget(QPushButtonとかその類)を用いないGUIアプリケーション開発の場合はQGuiApplication、QWidgetを使うならQApplicationを使います。
CUIとQtで開発してるとき、「GUIじゃないからQApplicationいらんやろ!w」と思ってこれを書かず、マルチスレッドにしようとしたところで躓いてしまいました。マルチスレッドにしようと思ってなければ気づいてなかったのでかなり危険ですね(他人事)
クラスはQObjectを継承する
これよくわかってないんですが、やっとくといいらしいですね(怒られそう)
例は以下のとおりです。
class HogeClass : public QObject {
Q_OBJECT
public:
void hello() {
qDebug() << "Hello";
}
};
こんな感じに、class行の下にQ_OBJECTマクロを置かないといけないらしいです。これはmoc(Meta-Object Compiler)ってやつです。
Q_OBJECTは以下のように展開されます。
QMetaObjectというのは構造体で、オブジェクトのメタ情報を取得してくれるとかなんとか書いてありましたがよくわかんないので理解を諦めます。
詳細はここに書いてあるので、読んでください。
さて、このQ_OBJECTが書かれたファイル、普通にビルドしようとしてもうまくいきません。cmakeならqt_wrap_cpp
を使ってmeta object compilerを呼び出してあげないといけないそうです。moc呼び出しを忘れてはいけません(1敗)。
ということで、とりあえずこの2点を抑えておけばかけるよ~といった感じです。GUIを作る際はさっきのmain.cppみたいに書けばOKです。
ウィジェットの表示ってどうやるの
ウィジェット、ありますよね。QPushButtonやQLabelといったやつです。実際このQLabelってどうやって表示すればいいの?と思ったこと、ありませんか?
これは3つくらい種類があります。やっていきましょう。
widget->show()する
こういう感じのやつです。
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QLabel* label = new QLabel(QString("hello"));
label->show();
return a.exec();
}
実行すると、このような画面が出てきます。
この方法、すでにあるウィンドウに追加するわけではなく、そのウィジェットを1つのウィンドウとして表示するので、ラベルやボタンをshow()で表示するのはあんまり無いと思います。
レイアウトに追加する
以下は例です。
#include <QApplication>
#include <QLabel>
#include <QLayout>
#include <QWidget>
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
QWidget window;
QVBoxLayout* layout = new QVBoxLayout;
QLabel* label = new QLabel(QString("hello"));
layout->addWidget(label);
window.setLayout(layout);
window.show();
return a.exec();
}
実行結果はこんな感じ。
8行目でウィンドウを作って、9行目で作った縦方向のレイアウトに”hello”と書かれたラベル(10行目で作ったもの)を追加し、そのレイアウトをウィンドウにセットして表示するという流れです。
ウィジェットを親として渡す
例です。
#include <QApplication>
#include <QLabel>
#include <QWidget>
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
QWidget window;
QLabel* label = new QLabel(QString("hello"), &window);
window.show();
return a.exec();
}
こんな感じの画面が出てきます。最初のshow()を直接呼び出すやつと似ていますね。
ドキュメントによると、QLabelのコンストラクタは2つあります。
今回使ったのは QLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags())
のほうで、第1引数には表示したい文字列、第2引数には親要素となるQWidget(ウィンドウ)を指定しています。このように、親要素が指定されたウィジェットはその親要素のウィンドウ内に表示されるらしいです。
位置・サイズの調整はsetGeometry(x, y, w, h)で行います。
いろいろなマクロ
以下は、またもやQt Creatorが作ってくれるmainwindow.hの中身です。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
Q_OBJECTは前述のとおりですが、QT_(BEGIN | END)_NAMESPACE というものがあります。定義を見ても空のマクロで、なんだこれ?と思って調べていました。
つまるところ、後から全ての名前空間をもう一つの名前空間で包みたい(?)時に使うといいらしいです。便利そうですごいですね。自分は使ってないですが。
また、QT_(BEGIN | END)_HEADERというものも(Qt6: remove me.
などという不穏なコメント付きで)存在します。ただ、これはよくわかりませんでした。いかがでしたか? Wikiを見る限りはQt自体のソースコードで用いられるものだと思います。
文字列はtr()で囲む
QLabel("Load from file")
みたいなものがあったとき、QObjectを継承したクラス内であればQLabel(tr("Load from file"));
って書くことが出来ます。クラス外(例えばmain関数内とか)であればQCoreApplication::translate("label_1", "Load from file", nullptr);
って感じらしいです。第1引数にはわかりやすい任意の文字列を渡してください。
で、これ何がいいのかというと、翻訳することが出来ます。ソースコードからtrで囲まれた文字列をlupdate
みたいなコマンドで抽出して翻訳ファイルを作ってかんたんに多言語対応ができます。
翻訳されるはずがないテキスト(例えばファイルパス)は囲む必要がないですが、とりあえずボタンとかラベルとかで文字列が出てきたらtrで囲んでおきましょう。
ドキュメントを見る
ドキュメントを見ましょう。見ましょう。
めちゃくちゃ参考になるので、とりあえず読みましょう。英語ですが、Google翻訳をかけて読みましょう。
今回伝えたかったのはこれでした。英語だからと言って逃げると大切な情報を逃してしまいますね(自戒)。
おわりに
なんか開発中は「こんな書き方があるのか!!すごい!!共有しよ!!!!」って思ってるのに、いざ記事書くとあんまり書くことないですね。役に立てたら幸いです。
Qtは便利ですが、まだまだ全然わからないことだらけなので、今後もドキュメント見ながら頑張ろうと思います・・・。
コメント