QMLからQt付属の標準アイコンを使う方法

QtQuick+QMLでGUIのアプリケーションを作ってみようと試行錯誤しています。アプリ作成にあたってファイルの保存やファイルのオープンなど,一般的なダイアログをツールバーに表示させるアイコン画像がほしくなりました。

あまりにも一般的なものなので,これくらい標準で付属しているかなと思って調べてみました。Qt自体には付属しているのですが,それをQMLから簡単にアクセスする方法が見つかりませんでした。

今回はQMLからQtに付属している標準アイコン画像にアクセスする方法を調べたので,その方法を記します。

Ubuntu 16.04のQt 5.10+QtCreator 4.5で確認しました。

Qt付属の標準アイコン

Qt付属の標準アイコンについては以下のページで解説されており,分かりやすかったです。

Qt デフォルトで使用可能な組み込みアイコン QStyle::StandardPixmaps – unstable diary

Qtにはデフォルトで組み込まれているアイコンがあります。それらのアイコンはQStyle::StandardPixmapのenumで定義されています。具体的には以下のようなC++のコードでアクセスできるようです。

QIcon icon = QApplication::style()->standardIcon( QStyle::SP_TitleBarMenuButton );

オープンや保存,ごみ箱など一般的な用途で使うアイコンは一通り揃っているようです。

QMLからのアクセス方法

Qtの標準アイコンはC++のライブラリーとしてのみ存在しており,Qt 5.10(QtQuick 2.9)の段階ではQMLから直接アクセスする方法がありません

せっかく付属しているのだから,なんとかアクセスする方法がないか調べたところ,以下のやりとりを見つけました。

@Violet-Giraffe
you can simply create your custom QQuickImageProvider to do so
Map the names to the QStyle::StandardPixmap enum and convert the QIcon to a QPixmap/QImage.

Can QApplication::style()->standardIcon(…) be used on a QML button? Do 2.0 buttons even support icons? | Qt Forum

QQuickImageProviderクラスを経由すれば,QMLからアクセス可能なようです。実際にQtQuickImageProvierのマニュアルをみながらやったところうまくできました。他に情報がほとんどみあたらなかったので手順を説明します。

大きく以下の4ステップとなります。

QMLからQt標準アイコンのアクセス手順
  1. QQuickImageProviderを継承したクラスを用意
  2. 継承クラスでrequstPixmap()をオーバーライドし,標準画像を返却
  3. QQmlEngineのインスタンスのaddImageProvider()でアクセスIDを指定し,用意したクラスを指定
  4. QML側で"image://アクセスID/画像名"で参照

サンプルソース

実際に動作するサンプルソースを示して説明します。以下の.pro, main.cpp, main.qmlを用意します。Qt標準アイコンを使うにあたって,特に注意が必要な箇所を強調しています。

QEditor.pro
QT += quick widgets
CONFIG += c++11

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += main.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include <QQuickImageProvider>
#include <QCommonStyle>

class StandardPixmapProvider : public QQuickImageProvider
{
public:
    StandardPixmapProvider()
        : QQuickImageProvider(QQuickImageProvider::Pixmap)
    {
    }

    QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
    {
        std::map<QString, QStyle::StandardPixmap> iconMap = {
            {"DialogOpenButton", QStyle::SP_DialogOpenButton},
            {"DialogSaveButton", QStyle::SP_DialogSaveButton},
        };

        auto icon = QCommonStyle().standardPixmap(iconMap[id]);
        *size = icon.size();  // original image size

        if      (requestedSize.isValid()   ) icon = icon.scaled(requestedSize);
        else if (requestedSize.width()  > 0) icon = icon.scaledToWidth(requestedSize.width());
        else if (requestedSize.height() > 0) icon = icon.scaledToHeight(requestedSize.height());

        return icon;
    }
};


int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    engine.addImageProvider(QLatin1String("standard"), new StandardPixmapProvider);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Controls 1.5
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.3

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("QEditor")

Action { id: open text: "&Open" shortcut: StandardKey.Open iconSource: "image://standard/DialogOpenButton" } Action { id: save text: "&Save" shortcut: StandardKey.Save iconSource: "image://standard/DialogSaveButton" } toolBar: ToolBar{ RowLayout { ToolButton { action: open } ToolButton { action: save } } } menuBar: MenuBar { Menu { title: qsTr("&File") } Menu { title: qsTr("&Edit") } Menu { title: qsTr("&Help") } } TextEdit { id: textEdit focus: true } }

これらのサンプルソースを実行すると以下の画面が表示されます。保存アイコンとファイルを開くためのアイコンがツールバーに表示されています。

Qt標準アイコンの使用例

手順詳細

実装の手順を説明します。

QMLからのアクセス

main.qmlでは,以下のようにしてmain.cppで提供しているリソースにアクセスしています。

        iconSource: "image://standard/DialogOpenButton"

このURIは以下の3構成となっています。

  1. QQuickImageProviderからのリソースにアクセスするために,URIのスキーマにimage://を指定。
  2. ImageProvider追加時に指定したアクセスID(standard)を指定。
  3. リソース名(DialogOpenButton)を指定。
requestPixmap()の実装

main.cppでQQuickImageProvider(#include <QQuickImageProvider>)を継承したStandardPixmapProviderクラスを用意しています。その後,requestPixmap()をオーバーライドしています。以下ではrequestPixmap()の実装について説明します。

requestPixmap()の引数は以下の役割を持っています。

requestPixmapの引数
引数 説明
id QMLで指定したリソース名
size 提供する画像の元サイズを指定
requestedSize QMLから要求されたサイズ
enumとリソース名の対応付け

冒頭でまず以下を記述しています。

        std::map<QString, QStyle::StandardPixmap> iconMap = {
            {"DialogOpenButton", QStyle::SP_DialogOpenButton},
            {"DialogSaveButton", QStyle::SP_DialogSaveButton},
        };

idにQMLから指定されたリソース名(最後スラッシュ以降の部分)が渡されます。しかし,Qtでは標準アイコンをenumの数値で管理しているため,直接紐付けることができません。そこで,あまりいいやり方ではありませんが,一度mapで手動で割当を作って対応しています。利用する標準アイコン分だけmapに項目を追加します。

アイコンの取得

続いて標準アイコンの取得です。これは以下のコードで実現します。

        auto icon = QCommonStyle().standardPixmap(iconMap[id]);

ネット上では,以下のコードでアクセスするサンプルが多かったです。

QIcon icon = QApplication::style()->standardIcon( QStyle::SP_TitleBarMenuButton );

しかし,この方法はQt 4までの方法のようで,Qt 5になってAPIが変わったのかうまくできませんでした。

そこで,QStyleクラスにアクセスするためにQCommonStyle()(#include <QCommonStyle>)を一時オブジェクトとして使用しました。また,requestPixmapはQPixmap型を返却する必要があるので,変換の手間を省くためQIcon型を返すstandardIconではなく,standardPixmapを採用しました。

サイズの調整

以下ではサイズの調整をしています。

        auto icon = QCommonStyle().standardPixmap(iconMap[id]);
        *size = icon.size();  // original image size

        if      (requestedSize.isValid()   ) icon = icon.scaled(requestedSize);
        else if (requestedSize.width()  > 0) icon = icon.scaledToWidth(requestedSize.width());
        else if (requestedSize.height() > 0) icon = icon.scaledToHeight(requestedSize.height());

まず,引数のsizeには返却するもともとの画像サイズを指定するようにマニュアルに書いてあったので,用意したアイコン画像のサイズを使丁しています。

その後,requestedSizeにQML側でheightやwidthプロパティで指定したサイズが入ってくるとのことで,有効な値が入っていれば反映させています。

アクセスIDの設定

requestPixmap()の実装が完了したら,後はQMLのエンジンに追加するだけです。以下のコードを記述します。

    engine.addImageProvider(QLatin1String("standard"), new StandardPixmapProvider);

ここで指定したstandardが今回用意したStandardPixmapProviderで提供するリソースのアクセスID(image://直後の部分)となります。

まとめ

QMLからQt付属の標準アイコンにアクセスする方法を解説しました。ネット上ではほとんど情報がなかったので,うまく実現するのに手間取ってしまいました。今後の自作アプリではこの方法でQMLからQt付属の標準アイコンを活用していきたいと思います。

ただし,この方法には一つ心残りがあります。mapでリソース名とenumを対応付けるのがやり方として良くないなと思っています。調べていたら,Qt Style Sheet(QSS)で名前でアクセスする方法を見つけました。Qt自体勉強始めたばかりで,QSSはよくわからなかったので今後挑戦してみます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です