Qtでキュートなツールを作る(Hello,World!編)


解散 Advent Calendar 2017 15日目です。
引き続きQt Designerを使っていきますが
今回からいよいよ本格的にプログラミングを始めます。

最初は基本中の基本
ボタンを押すとHello,World!のメッセージを出す所までやりましょう。

なお、毎回黒塗りしていると面倒なので、
今回からプロジェクト名をQt Practiceにしました。

1.ボタンを設置

まず最初に驚いたのは、編集がとても直感的!
コンボボックスの設定もダブルクリックでできると思わず
プロパティの中を必死で探していました…
(VSリソースエディタに慣れ過ぎて、逆に直感的でわからない例ですね)

2.ボタンを押して関数を呼び出す

ボタンを設置して、VSではマクロ定義がresource.hに定義されますね。
Qtでは
ヘッダーファイルにUi::(プロジェクト名) ui;と定義されているはずです。
このポインタを使って操作していきます。

しかし、ボタンが押されたという動作はどうやって取れば良いのか
これが恐らくQtとVS(リソースエディタ)の最大の違いだと思います。

Qtにはシグナルスロットという概念があり、それぞれ
シグナル:オブジェクトに何らかのアクションがあった事を通知する
スロット:シグナルから発したアクションを受け取る
という意味があります。
恐らく、皆さんはスロットを追加していく事が大半ではないかと思います。

単純に表示のON,OFF等の簡単な事なら既存の物で十分ですが
ボタンを押されたら何か特別な処理を行う場合は自分で足す必要があります。

早速追加していきましょう。
新しくプロジェクトを作成すると、このように基本的部分は
Qt側が自動的に作成してくれます。
そこに、ドラッグで示したような

QtPractice.h
private slots:
    void callMessage();

このようにprivate slots:と宣言して
その下に自身の関数を追加します。
もちろん実際の関数はcpp内で記述する必要があります。

※赤線が出てますね。Qtではボタン設置等した後の情報更新が遅めです。
 自分でも明確な対処法がわかりませんが
 プロジェクトを全て閉じて、再度開いたり、クリーンビルドしたら
 すぐに治る場合もあったりとよくわかりません。

3.シグナルとスロットの接続

関数を作ったら早速シグナルとスロットを接続してみましょう。
赤い四角で囲ったアイコンをクリックすると、接続モードになります。
矢印を引っ張ると、オブジェクト内と外では先端が変化しますね。

今回はボタンを押して、他オブジェクトと直接干渉しない動作をしますので
下の外に向かって矢印を引っ張りましょう。

追加の手順は慣れれば簡単です。
今回はクリックしたらすぐ動作を取るので
シグナルはclicked()、スロットを追加するので①の編集を押しましょう。
②の緑の+を押すと、新しく関数を追加できますので
そこで先ほど定義したcallMessage()を追加します。
すると④にあるようにスロットへ追加されますので、これを選べばOKです!

4.メッセージウィンドウを呼び出す

呼び出しの構造を作ったので、メッセージを出しましょう。
という訳で、メッセージウィンドウを作ります!
なんて必要は全くありません。
VSにも標準であったように、Qtにも実はちゃっかり標準で搭載されています。
自分はそれに気が付けずに作ってしまいました。

まずはcppにqmessagebox.hをインクルードしましょう
これを定義するだけで簡単にメッセージボックスが扱えます。
メッセージボックスは以下の関数で呼び出せます。
QMessageBox::information(this, "インフォ", "青い丸に白のiマーク");
QMessageBox::warning(this, "注意", "黄色い三角に黒の!マーク");
QMessageBox::critical(this, "警告", "赤い丸に白の×マーク");
QMessageBox::question(this, "確認", "YESとNOのボタン");

基本的に、(親クラス(thisで大丈夫です), ウィンドウタイトル, メッセージ)
という構成になっています。

ただ単にOKを押させるだけでも、3種類(上三つ)
下の確認ボタンはint型で結果が帰ってきます。(詳細は後述)

それでは早速メッセージボックスを追加します。
questionではなければ何でも良いですが、今回はinformationを使います。

QtPractice.cpp
void QtPractice::callMessage()
{
    QMessageBox::information(this, "テスト", "Hello,World!");
}

さて、実行してみましょう……?

見事に文字化けしていますね。
それもそのはず、Qtの文字列は基本的にQStringという特殊な文字列を使い
文字コードによっても文字化けが発生しうるのです。

それを回避するために、このマクロを定義しておくと物凄く便利です。
この先も絶対使って行きますので、絶対今すぐ使うべきです。
めっちゃ使うので、main.hに定義してしまっても良いです。

#define TR(s) (QString::fromLocal8Bit(s))

このマクロを紹介していたこちらのサイトではPythonを使っていますが
このマクロはC++でも共通して使えます。
詳しく解説すると混乱すると思うので、おまじない的に使って行きましょう。

QtPractice.cpp
void QtPractice::callMessage()
{
    QMessageBox::information(this, TR("テスト"), TR("Hello,World!"));
}

そして実行してみると……

文字化けが回避できましたね!
これで今回の目標は達成できました。

折角なので、+αまでやってみましょう。

5.×ボタンを押されたら…?

当然、ウィンドウは終了すべきです。
Qtには勿論デフォルトで搭載されており、押せば勝手に閉じてくれます。
しかしツールとして、一旦はユーザーに確認する処理を作るべきです。

実はQtは終了前にcloseEventという処理を行っており
そこに介入することが出来ます。
早速ヘッダーに追加してみましょう。
QCloseEventは後々使用しますが、ウィンドウ終了に関するイベントを
操作する為のポインタ受け取りだと思ってください。

なお、コードを書く前にqevent.hをインクルードしましょう。
これが無くてはQCloseEventを扱えません。

QtPractice.h
public:
    QtPractice(QWidget *parent = Q_NULLPTR);
    void closeEvent(QCloseEvent *event);    //←追加

続いて関数の内部を作っていきます。
QMessageBox::questionはint型で帰って来るものの
結果自体はQMessageBox内部にあるマクロ定義で判定を取ります。

先程のQCloseEventはここで使用します。
event->accept()を呼ぶと、そのまま終了処理が進みます。
event->ignore()を呼ぶと、終了処理が中断されます。
以下が例です。

QtPractice.cpp
void QtPractice::closeEvent(QCloseEvent* event)
{
    int res = QMessageBox::question(this, TR("確認"), TR("終了してよろしいですか?"));

    if (res == QMessageBox::Yes)
    {
        event->accept();
    }
    else
    {
        event->ignore();
    }
}

実は、event->accept()を呼ばなくても勝手に終了するのですが
一応様式美的に呼ばなきゃいけない気がするので呼びましょう。
(追記:accept()はどうやらウィンドウ終了処理の呼び出しのようなので、必要なさそうです)
試しに実行して、×ボタンを押してみましょう。

6.次回予告

今回は密度が濃くて、その上細かく書きすぎると2倍ぐらい膨れそうなので
省略に省略を重ねましたが、付いて来れたでしょうか?

解散 Advent Calendar 2017 の自分の担当は次回で最後ですので
最終回は自分が使ってみてつまずいた所や、これ便利じゃん!
と感じた所をピックアップして紹介したいと考えております。