Qtでの検索ボックスの作成方法


はじめに

Qtに関する情報にどれほどの需要があるか分かりませんが、作る機会が多いであろう検索ボックスの作り方を、備忘録も兼ねて共有しておきます。

対象

ある程度Qtを触ったことがある人。(signal/slotが分かる人。)
一応Qtを知らない方用に、適宜公式ドキュメントへのリンクをはっています。

開発環境

  • Qt Creator 4.11.2
  • Qt 5.14.2

TL;DR

下の写真のような検索ボックスを実現するにあたり、QGridLayoutを利用して、コンポーネントを重ねることを考えました。ある程度詳しい方はgithubを見ていただく方が速いと思います。

コード

各コンポーネントはQWidgetを継承したSearchBoxクラスのメンバ変数として設定しています。ClickableLabelに関しても自作クラスなので、後ほど解説します。

SearchBox::SearchBox(QWidget *parent)
    : QWidget(parent),
      m_layout(new QGridLayout),
      m_lineEdit(new QLineEdit),
      m_label(new ClickableLabel) // 補足で解説しています。
{
    m_label->setPixmap(QIcon(":/magnifying_glass.png").pixmap(QSize(32, 16)));
    m_label->setFixedWidth(32);
    m_lineEdit->setPlaceholderText("Search");
    m_lineEdit->setTextMargins(32, 0, 0, 0);

    m_layout->setSpacing(0);
    m_layout->addWidget(m_lineEdit, 0, 0);
    m_layout->addWidget(m_label, 0, 0);

    setLayout(m_layout);

    connect(m_label, &ClickableLabel::clicked, this, [&](){ m_lineEdit->setFocus(); });
}

解説

各コンポーネントの詳しい解説は公式ドキュメントを参照してください。QLabelClickableLableの親クラスです。ClickableLableが何か分からなくて気持ち悪い方は、先に補足をご覧ください。
QGdirLayout / QLineEdit / QLabel

手順

1. ClickableLabelにアイコンを設置し、幅を調整する。

m_label->setPixmap(QIcon(":/magnifying_glass.png").pixmap(QSize(32, 16)));
m_label->setFixedWidth(32);

一行目は、ClickableLabelにアイコンを設置するコードです。このコードではQtのresourceシステムを用いてアイコンのパスを設定しているので、見たことがない方は戸惑われるかと思います。詳しくはQt公式をご覧ください。QSizeは用意したアイコンの縦横比に合わせて適宜変更してください。二行目では、ClickableLabel本体の横幅を設定しています。この設定をしないと、QGridLayoutに設置する際に、QLineEditを覆い隠してしまいます。

2. QLineEditのプレースホルダーテキストとマージンを設定する。

m_lineEdit->setPlaceholderText("Search");
m_lineEdit->setTextMargins(32, 0, 0, 0);

一行目は、Qiitaの上部にある検索ボックス中の「キーワードを入力」のように薄いグレーで表示される文字(プレースホルダーテキスト)を設定するコードです。二行目は、一行目で設定したプレースホルダーテキストがlabelと重なるのを避けるためのコードです。引数は順に、左、上、右、下です。(cssの順番がmarginとは異なるので注意してください。)左のマージンの値をClickableLabelの横幅の値に設定することで、ちょうどアイコンの右側にプレースホルダーテキストが表示されるようになります。

3. QGridLayoutのspacingを0に設定し、QLineEditClickableLableを設置する。

m_layout->setSpacing(0);
m_layout->addWidget(m_lineEdit, 0, 0);
m_layout->addWidget(m_label, 0, 0);

setLayout(m_layout);

最後に、準備したコンポーネントを設置して完成です。QGdidLayoutはその名の通り、グリッド状のレイアウトで、addWidgetメソッドに設置するItemrowおよびcolumnを引数に渡してコンポーネントを追加していきます。

gridLayout->addWidget(Item, row, column);

ここで、あえて二つのコンポーネントのrowcolumnを同じ値に設定することでコンポーネントを重ね合わせることができます。3行目のsetLayoutQSearchBoxのメソッドで、その名の通り、レイアウトを設定します。

補足

1. フォーカスに関する問題

以上の解説の通りにコードを書けば、見た目は思い通りのものは作ることができます。ですが、ここでユーザーのことを考えて一工夫してみましょう。試しに、Qiita上部の検索ボックスの虫眼鏡アイコンをクリックしてみてくささい。どうでしょう。検索ボックスにフォーカスしないと思います。ユーザーからすれば、虫眼鏡マークをクリックした時にしたいことは、検索だと思うので,
ちょっとしたことですが不便ですね。実はQtでも、アイコンを表示するために、デフォルトのQLabelを使うと同じ状況に陥ってしまいます。
前置きが長くなりましたが、QLableにちょっとした細工をして、虫眼鏡アイコンをクリックした時でも、検索ボックスにフォーカスするようにしましょう。

2. ClickableLabel

上記の問題を解決するのがClickableLabelクラスです。やっていることは簡単で、デフォルトのQLabelでは検知されない、クリックイベントを検知するように変更するだけです。コードは下の3行で済みます。emitが分からない方は、Qtのsignal/slotという仕組み一度調べてみてください。また、実はこのClickableLableQt wikiというQtに関する情報が集まっているサイトに載っています(Qt wiki ClickableLabel)。

void ClickableLabel::mousePressEvent(QMouseEvent*)
{
    emit clicked();
}

そして、ClickableLabelで検知したクリックを、SearchBox側で検索ボックスのフォーカスと紐付けます。

connect(m_label, &ClickableLabel::clicked, this, [&](){ m_lineEdit->setFocus(); });

こうすることで、虫眼鏡アイコンをクリックしたときにも、検索ボックスにフォーカスするようになります。

以上で、検索ボックスの完成です

参考

stackoverflow - How to place an icon onto a QLineEdit?(リンク先の方法より、この記事で紹介した方法の方が簡潔です)

最後に

まず、初めての投稿なので、どの解説を載せてどの解説をはしょるのかを決めるのに苦労しました。もし、言葉足らずの部分があれば、遠慮なく質問してください!また、間違った情報があれば、指摘していただけると嬉しいです。

QtはちょっとしたGUIを作るのには適しているフレームワークだと思います。興味を持った方は是非トライしてみてください!!