Qtでキュートなツールを作る(応用,小ネタ編)


解散 Advent Calendar 2017 21日目、自分の担当は最終回です。
これまで通りQt Designerを使っていきます。

最終回は自分が使っていて、皆さんが知っておくべき事を
適当に書いていきたいと思います。

1.名前を付けて保存

ツールを作っているなら、保存ダイアログは必要になりますよね。
自分で組まなくとも、windowsAPIはそういった物が用意されていますが
勿論Qtにも用意されています。

まずはqfiledialog.hをインクルードしましょう。
ダイアログ操作はこのファイルが必要になります。
読み込みはQFileDialog::getOpenFileName()
保存はQFileDialog::getSaveFileName()を使います。
以下に保存の例を載せます。

QtPractice.cpp
QString fileName = QFileDialog::getSaveFileName(
        this,
        TR("名前を付けて保存"),
        "",
        TR("テキストファイル(*.txt);;バイナリファイル(*.bin)")
    );

windowsAPIを扱った事があればおおよそこれで使い方は分かるかと思います。
1行目は親ウィンドウ(安定のthis)
2行目はウィンドウタイトル
3行目は気にせず空白で
4行目は拡張子の指定です。
表示名(*.拡張子)の形式で入力し、複数指定する場合は間に;;を挟みます。

ちなみに、if(!fileName.isEmpty())でキャンセルされたか
ファイルが選択されたかチェックする事ができますよ。
(if文の中に処理を書きましょう)

ファイルオープンや、出力までQtでできる(Qfile)との事でしたが
イマイチ思うように出力してくれなかったので、結局fopenとfwriteを使いました。
一応、ファイルオープンの際のファイル名は
fileName.toStdString().c_str()でそのまま使えます。

2.プログラムからシグナルスロットをコネクト

前回、Qt Designerからシグナルスロットのコネクトを説明しましたが
操作する部分が多いと画面が大変見づらくなります。

例えば、「ボタンを押したら隣の入力ボックスをリセットする」
というボタンが何個も続いている場合
そのボタンのポインタを構造体配列等で保存し、for文ループでプログラムから
一気にコネクトしてしまった方が早い場合もあります。

使い方は様々ですが、とりあえずその方法がこちらです。

connect(接続元, SIGNAL(シグナル), 接続先, SLOT(スロット); 

ちょっと解説しましょう。
引数1:ui->で指定できる、オブジェクトのポインタです。
引数2:シグナルをここに記述します。前回のシグナル追加を参考にしましょう。
引数3:ui->で指定できる、オブジェクトのポインタです。
引数4:スロットをここに記述します。前回のスロット追加を参考にしましょう。

…ちっともわかりませんね?簡単な例を載せてみます。

このようなウィンドウを即興で作ってみました。
ボタンを押すと、隣の入力ウィンドウの文字列を消す仕組みを作ります。

※前回のuiの定義がポインタになっていませんでしたが
 本来はポインタで使うべきです。今回はポインタとして扱います。
 (お詫びにヘッダーは全文載せます)

QtPractice.h
#pragma once

#include <QtWidgets/QDialog>
#include "ui_QtPractice.h"

/***************************************************************
* 構造体
***************************************************************/

typedef struct
{
    QPushButton *button;
    QTextEdit *text;
}UI_DATA;

/***************************************************************
* マクロ定義
***************************************************************/
#define TR(s) (QString::fromLocal8Bit(s))

#define DATA_SIZE       (3)

/***************************************************************
* クラス
***************************************************************/
class QtPractice : public QDialog
{
    Q_OBJECT

public:
    QtPractice(QWidget *parent = Q_NULLPTR);

    void closeEvent(QCloseEvent *event);

private:
    Ui::QtPracticeClass *ui;

private slots:
    void callMessage();
};
QtPractice.cpp
UI_DATA uiData[DATA_SIZE] = {};

QtPractice::QtPractice(QWidget *parent)
    : QDialog(parent)
{
    ui->setupUi(this);

    //あまりここでやるべき処理では無いですが、飽くまで例なので。
    uiData[0] = {ui->pushButton_2, ui->textEdit};
    uiData[1] = {ui->pushButton_3, ui->textEdit_2};
    uiData[2] = {ui->pushButton_4, ui->textEdit_3};
}

さて、準備はできました。
続いて実際のコネクト処理を書いていきます。

QtPractice.cpp
connect(uiData[roop].button,    //ボタンのポインタ
    SIGNAL(clicked()),  //クリックされたら実行
    uiData[roop].text,  //テキストボックスのポインタ
    SLOT(clear()));     //入力内容削除

こんな感じですね!
引数がある場合はSIGNAL(シグナル())SLOT(スロット()))ここに引数のを入れましょう。
今回は3つのボタンだけなので、普通にQtDesignerからコネクトした方が早いですが
このように同じ処理を大量にコネクトする場合は
一度スクリプトでのコネクトも考えると良いでしょう。

ちなみに、オブジェクトではなくウィンドウに対してコネクトしたい場合は
thisを使う事で実現が可能です。

3.画面遷移をしたい

アプリケーションのインストール画面はボタン一つで画面が切り替わり
新しいウィンドウを開くことなく次々と画面遷移します。

リソースエディタを扱っていた時は、新しくウィンドウを展開させるしか
画面遷移をさせる方法がありませんでしたが
Qtにはなんとデフォルトでその手伝いをしてくれる機能があります。

それがstackedWidgetです。
QtDesignerから早速使ってみましょう。

用意した空間にこれまでのオブジェクトを入れるとこんな感じになります。
編集するときは右上の▲マークをクリックする事でページを切り替えられますが
実行画面ではこのマークは勿論消えてくれます。

先程は画面遷移と書きましたが、実際はこのように
部分的に表示を変えるという扱い方ができます。
画面全体に広げれば、勿論画面遷移に使えますよ。

注意点としては、画面遷移に使う場合は
画面が一番大きくなる時のサイズに合わせるという事です。
画面サイズが変わっても、この範囲は広がりませんから
先に合わせておく必要があります。
(スクリプトで勿論広げられますが、最初から最大サイズにした方が楽です)

それと、シグナルスロットのコネクトの際に反応してしまうので
邪魔になるのは覚悟しておいてください。

画面の切り替えは以下のスクリプトで可能です。
ui->stackedWidget->setCurrentIndex(ページ番号(int));
なお、画面サイズが変わる場合は別途書く必要があります。
setFixedSize(QSize(横, 縦));
上を推奨しますが、下の画面位置移動と同時にセットする事もできます。
setGeometry(x軸, y軸, 横, 縦);
※x軸y軸は画面左上を起点とした絶対座標です。
 0,0を指定した場合はメニューバーが上に埋もれて移動できなくなるので
 座標指定には注意が必要です。

4.モードレスダイアログの罠

これはここまで来れた方なら、すぐ解決できる程度の罠ですので
小ネタ程度に「そんな事もあるんだなぁ」と見てください。

例えば、ドッキング機能などを使ってモードレスダイアログを生成した際
メインウィンドウが消されると同時に、これらのウィンドウも消えるべきです。

しかし、大抵の場合は親ウィンドウから動的に生成して
親ウィンドウのデストラクタで一緒に削除するべきですが
main.cppを見てわかる通り、ウィンドウは意図してdeleteしない限り
プログラムが完全終了するまで消えません。(=デストラクタが呼ばれません)

つまり、親ウィンドウをcloseさせても動的に作成したウィンドウは残るため
親ウィンドウの閉じるボタンが押されたと同時に子ウィンドウも消す必要があります。

前回のcloseEventの処理と同時に、子ウィンドウも削除してしまいましょう。

最後に

お疲れさまでした。というか疲れました。
Qtはまだまだ日本語のリファレンスが足りておらず
その上に更新が早く、一回一回の変更も大きいので
この情報もすぐに古くなっていくと思います。

しかし、きちんと探せば奥深い事まで扱えて
慣れればきっと皆さんのツール制作の役に立つと思います。
自分ももし、後々「これは共有したい!」と思ったら
また新しく備忘録的に記事を書くかもしれません。
(一番の目当てだったスクロールバーもまだなので……)

とりあえず、自分のQt記事はこれで一旦はお終いですので
ただただこの記事が皆さんの役に立つ事を願っております!
それでは良いツール製作ライフを!
そして作った優秀なツールを自分に提供してください。

こちらも、もしかしたら続編を書くかもしれませんし
書かないかもしれません。