本文へスキップします。

本文へ

【全Qt】
【全・Qt】SRAロゴ
H1

FAQ

Q&A:検索

カテゴリー
状態

Q&A:表示

Qt

コア

Qt のクラスを拡張してサブクラスを作成する場合に Q_OBJECT はどのような場合に必要なのでしょうか。

QObject または間接的に QObject を継承しているクラスを継承してサブクラス化をする場合には、いつも Q_OBJECT を記述しておいた方がよいです。 そうしておけば、いろいろな間違いを避けられます。

サブクラスにシグナルとスロットを追加するならば Q_OBJECT の記述が必要です。追加しないならば Q_OBJECT を記述しなくてもコンパイルはできて一応動作はします。 しかし、シグナルやスロットを追加しないからと言って、Q_OBJECT を付けないといろいろな問題が発生します。 翻訳のための tr() がクラス名のコンテキストで翻訳メッセージを検索できなくなります。 qobject_cast がコンパイルエラーになります。metaObject()->className() が正しいクラス名を返さず、ベースクラス名を返してしまいます。

main.cpp:

#include <QCoreApplication>
#include <QTranslator>
#include <QLocale>
#include <QDebug>

class QUnderscoreObject : public QObject
{
    Q_OBJECT

public:
    explicit QUnderscoreObject(QObject* parent = nullptr) : QObject(parent) {
        qDebug() << Q_FUNC_INFO << tr("Hi, there.");
    }
};

class NoQUnderscoreObject : public QObject
{
public:
    explicit NoQUnderscoreObject(QObject* parent = nullptr) : QObject(parent) {
        qDebug() << Q_FUNC_INFO << tr("Hi, there.");
    }
};

翻訳関数 tr() は、クラス名と実引数文字列をキーにして翻訳辞書を検索し翻訳文字列を返します。 QUnderscoreObject コンストラクター内の tr() は、正しく翻訳日本語文字列を返します。 しかし、NoQUnderscoreObject のコンストラクター内の tr() は、ベースクラスの tr() が呼び出されるので、翻訳日本語文字列を返せません。


int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QTranslator translator;

    if (translator.load(":/qunderscore_" + QLocale::system().bcp47Name())) {
        app.installTranslator(&translator);
    }

日本語翻訳辞書をロードしています。


    QUnderscoreObject qUnderscoreObject{};
    qDebug() << "QUnderscoreObject class has a proper class name:"
             << qUnderscoreObject.metaObject()->className();

    NoQUnderscoreObject noQUnderscoreObject{};
    qDebug() << "NoQUnderscoreObject class does not have a proper class name:"
             << noQUnderscoreObject.metaObject()->className();

QMetaObject::className() は、クラス名を返します。QUnderscoreObject の方の metaObject() は、QUnderscoreObject のメタオブジェクトを返すので、className() は 正しいクラス名を返します。 NoQUnderscoreObject の方の metaObject() は、ベースクラスのメタオブジェクトを返すので、className() はベースクラスのクラス名を返してしまいます。


    QObject* object = &qUnderscoreObject;
    qDebug() << "qobject_cast<QUnderscoreObject*>(object):" << qobject_cast<QUnderscoreObject*>(object);

    object = &noQUnderscoreObject;
//  qDebug() << "qobject_cast<NoQUnderscoreObject*>(object):" << qobject_cast<NoQUnderscoreObject*>(object); // Compilation error
}

#include "main.moc"

QObject* 型変数に代入した QUnderscoreObject オブジェクトを QUnderscoreObject* に型変換できます。 しかし、NoQUnderscoreObject オブジェクトの型変換は、コンパイルエラーになります。


QUnderscoreObject::QUnderscoreObject(QObject *) "やあ。"
QUnderscoreObject class has a proper class name: QUnderscoreObject
NoQUnderscoreObject::NoQUnderscoreObject(QObject *) "Hi, there."
NoQUnderscoreObject class does not have a proper class name: QObject
qobject_cast<QUnderscoreObject*>(object): QUnderscoreObject(0x7ffeeec96480)

実行結果です。


/usr/local/qt/Qt/6.2.1/macos/lib/QtCore.framework/Headers/qobject.h:451:5: error: static_assert failed due to requirement
      'QtPrivate::HasQ_OBJECT_Macro<NoQUnderscoreObject>::Value' "qobject_cast requires the type to have a Q_OBJECT macro"
    static_assert(QtPrivate::HasQ_OBJECT_Macro<ObjType>::Value,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:46:64: note: in instantiation of function template specialization 'qobject_cast<NoQUnderscoreObject *>' requested here
  qDebug() << "qobject_cast<NoQUnderscoreObject*>(object):" << qobject_cast<NoQUnderscoreObject*>(object); // Compilation error
                                                               ^
1 error generated.
make: *** [main.o] Error 1

NoQUnderscoreObject の方のコメントを外すとコンパイルエラーになります。


サンプルコード

qunderscoreobject.zip

参考資料

Q_OBJECT ドキュメント
qobject_cast ドキュメント
QMetaObject::className ドキュメント
Linux で QThread を特定の CPU コアで動くようにするにはどのようにすればよいでしょうか。

QThread には特定の CPU コアで動くように設定する方法は用意されていないので、プラットフォーム固有の機能を使う必要があります。


main.cpp:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QElapsedTimer>
#include <sched.h>

class Thread : public QThread
{
    Q_OBJECT

public:
    explicit Thread(int cpuNumber) : QThread(nullptr), cpuNumber(cpuNumber) {}

protected:
    void run() {
        cpu_set_t cpuset;
        CPU_ZERO(&cpuset);
        CPU_SET(cpuNumber, &cpuset);
        const int status = pthread_setaffinity_np(reinterpret_cast<pthread_t>(QThread::currentThreadId()), sizeof(cpu_set_t), &cpuset);

        if (status != 0) {
            qDebug() << "pthread_setaffinity_np:" << strerror(status);
            return;
        }

Linux では pthread_setaffinity_np() で CPU コアにスレッドを割り当てられます。 QThread::currentThreadId() が返す ID は pthread_self() と同じなので、上記のようにすれば QThread で実行されるサブスレッドが実行される CPU コアを変更できます。


        unsigned long usecs;
        switch (cpuNumber) {
        case 0:
            usecs = 1;
            break;
        case 1:
            usecs = 200;
            break;
        default:
            usecs = 100;
            break;
        }

        QElapsedTimer timer;
        timer.start();
        while (true) {
            if (timer.elapsed() >= 10000) {
                break;
            }
            QThread::usleep(usecs);
        }
    }

private:
    int cpuNumber;
};

スレッド処理の CPU 負荷を CPU コアに応じて変えます。 スレッドは、処理を10 秒間実行して終了します。


int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    if (QThread::idealThreadCount() < 2) {
        qWarning() << "Ideal thread count is small.";
        exit(EXIT_FAILURE);
    }

    Thread firstThread{0};
    Thread secondThread{1};

    int livingThreads = 2;
    auto threadFinished = [&]() {
        livingThreads -= 1;
        if (livingThreads == 0) {
            QCoreApplication::quit();
        }
    };

    QObject::connect(&firstThread, &QThread::finished, threadFinished);
    QObject::connect(&secondThread, &QThread::finished, threadFinished);

    firstThread.start();
    secondThread.start();

    return app.exec();
}

#include "main.moc"

QThread::idealThreadCount() で CPU コア数が少なくとも 2 つあるのをチェックしています。 ラムダ関数では、実行中のスレッド数が 0 になったならば QCoreApplication::quit() を呼び出して、イベントループを抜けてプログラムを終了させています。


$ ps -C cpuaffinity -L -o comm,pid,lwp,psr,%cpu
COMMAND             PID     LWP PSR %CPU
cpuaffinity      107483  107483   2  0.0
Thread           107483  107484   0  7.5
Thread           107483  107485   1  2.0
$ LANG=C taskset -pac 107483
pid 107483's current affinity list: 0-3
pid 107484's current affinity list: 0
pid 107485's current affinity list: 1
$

サブスレッドに指定した CPU が割り当てられています。


サンプルコード

cpuaffinity.zip

参考資料

Ubuntu Manpage: pthread_setaffinity_np, pthread_getaffinity_np
QThread::currentThreadId ドキュメント
QThread:: idealThreadCount ドキュメント
QVector と std::vector の変換をするにはどのようにすればよいでしょうか。

Qt 5.14 からは、範囲コンストラクターを使って QVector と std::vector との相互変換ができます。 Qt 5.14 より前は、toStdVector() と fromStdVector() が使えます。 std::copy でも変換できます。

main.cpp:
#include <QVector>
#include <QDebug>

int main()
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
    qDebug() << "Using range constructors.";
    {
        const QVector<int> qVectorInt{1, 2, 3};
        const std::vector<int> stdVectorInt(qVectorInt.cbegin(), qVectorInt.cend());
        auto debug = qDebug();
        for (const auto value : stdVectorInt) {
            debug << value;
        }
    }

    {
        const std::vector<int> stdVectorInt{10, 20, 30};
        const QVector<int> qVectorInt(stdVectorInt.cbegin(), stdVectorInt.cend());
        auto debug = qDebug();
        for (const auto value : qVectorInt) {
            debug << value;
        }
    }

範囲コンストラクターを使った変換です。


#else
    qDebug() << "Using conversion methods.";
    {
        const QVector<int> qVectorInt{1, 2, 3};
        const std::vector<int> stdVectorInt = qVectorInt.toStdVector();
        auto debug = qDebug();
        for (const auto value : stdVectorInt) {
            debug << value;
        }
    }

    {
        const std::vector<int> stdVectorInt{10, 20, 30};
        const QVector<int> qVectorInt = QVector<int>::fromStdVector(stdVectorInt);
        auto debug = qDebug();
        for (const auto value : qVectorInt) {
            debug << value;
        }
    }
#endif

toStdVector() と fromStdVector() を使った変換です。これらのメソッドは非推奨で、Qt 6 で廃止されています。


    qDebug() << "Using std::copy.";
    {
        QVector<int> qVectorInt{1, 2, 3};
        std::vector<int> stdVectorInt;
        std::copy(qVectorInt.cbegin(), qVectorInt.cend(), std::back_inserter(stdVectorInt));
        auto debug = qDebug();
        for (const auto value : stdVectorInt) {
            debug << value;
        }
    }

    {
        std::vector<int> stdVectorInt{10, 20, 30};
        QVector<int> qVectorInt;
        std::copy(stdVectorInt.cbegin(), stdVectorInt.cend(), std::back_inserter(qVectorInt));
        auto debug = qDebug();
        for (const auto value : qVectorInt) {
            debug << value;
        }
    }
}

std::copy() を使った変換です。前述の 2 つの方法の方が簡潔な変換で、定数にもできます。


サンプルコード

vectorconversion.zip

参考資料

QVector 範囲コンストラクタードキュメント
QVector::toStdVector ドキュメント
QVector::fromStdVector ドキュメント
std::vector::コンストラクタ cpprefjp C++日本語リファレンス
std::copy cpprefjp C++日本語リファレンス
QString と char* を相互に型変換するにはどのようにすればよいでしょうか。

QString を char* に型変換するには、まず QString::toLatin1()、QString::toUtf8() や QString::toLocal8Bit() などで QByteArray 型に変換します。 次に data() または constData() を呼出して、QByteArray オブジェクトに保存されている char* 型または const char* データへのポインタを取出します。

逆に、char* を QString に型変換するには、QLatin1String 型または const char* 型の実引数を取る QString コンストラクターを使うか、QString のスタティックメンバー関数 fromLatin1()、fromLocal8Bit() や fromUtf8() などの関数を使います。const char* 型の実引数を取る QString コンストラクターは、fromUtf8() 関数を使って変換するので、UTF-8 でエンコードされている場合に使えます。

main.cpp:
#include <QCoreApplication>
#include <QLocale>
#include <QDebug>
#include <iostream>

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    const QString nativeCountryName = QLocale::system().nativeCountryName();

QLocale::system() でシステムロケールを取得し、nativeCountryName() を呼び出すと日本語環境では国名を日本語で返します。


    QByteArray ba = nativeCountryName.toLocal8Bit();
    const char* cp = ba.constData();
    std::cout << "toLocal8Bit: " << cp << std::endl;
    QString string = QString::fromLocal8Bit(cp);
    qDebug() << "fromLocal8Bit:" << string;

QString::toLocal8Bit() 関数は、言語環境のエンコードでバイト列を格納した QByteArray オブジェクトを返します。 QByteArray::constData() は、内部のバイト列のアドレスを const char* 型で返します。

QByteArray::constData() を呼ぶ前に QByteArray 型の変数に一旦保存する必用があることに注意しましょう。 つまり、以下のようにすると cp を参照しようしたときには、データを保存している QByteArray オブジェクトが存在しなくなってしまっているので、アプリケーションがクラッシュするからです。

const char* cp = nativeCountryName.toLocal8Bit().toLatin1().data();

逆に、言語環境のエンコードのバイト列 const char* を QString に型変換するには、QString::fromLocal8Bit() を使います。


    ba = nativeCountryName.toUtf8();
    cp = ba.constData();
    std::cout << "toUtf8: " << cp << std::endl;
    string = QString::fromUtf8(cp);
    qDebug() << "fromUtf8:" << string;

QString::toUtf8() 関数は、UTF-8 でエンコードされたバイト列を格納した QByteArray オブジェクトを返します。 逆に、UTF-8 でエンコードされたバイト列を QString に型変換するには、QString::fromUtf8() を使います。


    const QString countryName("Japan");
    ba = countryName.toLatin1();
    cp = ba.constData();
    std::cout << "toLatin1: " << cp << std::endl;
    string = QString::fromLatin1(cp);
    qDebug() << "fromLatin1:" << string;
}

QString::toLatin1() 関数は、Latin-1 でエンコードされたバイト列を格納した QByteArray オブジェクトを返します。 逆に、Latin-1 でエンコードされたバイト列を QString に型変換するには、QString::fromLatin1() を使います。


toLocal8Bit: 日本
fromLocal8Bit: "日本"
toUtf8: 日本
fromUtf8: "日本"
toLatin1: Japan
fromLatin1: "Japan"

Unix や Linux などの UTF-8 環境での実行結果です。


toLocal8Bit: 日本
fromLocal8Bit: "日本"
toUtf8: 譌・譛ャ
fromUtf8: "日本"
toLatin1: Japan
fromLatin1: "Japan"

Windows の SJIS 環境での実行結果です。toUtf8 が文字化けしているのは SJIS 環境で UTF-8 文字列を表示しようとしたためです。


サンプルコード
stringconversion.zip
非 Qt スレッドで生成したサブスレッドから Qt スレッドのスロットを安全に呼び出すにはどのようにすればよいでしょうか。

非 Qt スレッドから QMetaObject::invokemethod() で Qt スレッドのスロットを安全に呼び出せます。

std::thread で生成した非 Qt サブスレッドからメインスレッドのスロットを呼び出すサンプルコードです。

main.cpp:

#include <QCoreApplication>
#include <QMetaObject>
#include <QThread>
#include <QTimer>
#include <QDebug>
#include <iostream>

class Object : public QObject
{
    Q_OBJECT

public:
    Object() : QObject(nullptr) {
    }

public slots:
    void slot(const QString& message) {
        qDebug() << Q_FUNC_INFO;
        std::cerr << "    thread:id: " << std::hex << std::this_thread::get_id() << std::endl;
        qDebug() << "    QThread::currentThreadId():" << QThread::currentThreadId();
        qDebug() << "    message:" << message;
        QCoreApplication::quit();
    }
};

スロットを用意したクラスで、このクラスのオブジェクトのスレッドアフィニティをメインスレッドにし、スロットをサブスレッドから呼び出すようにします。 2 通りの方法で、どのスレッドで実行されているか分かるようにしています。スロットの最後で QCoreApplication::quit() を呼び出してメインイベントループを終了させます。


void worker(QObject* object)
{
    qDebug() << Q_FUNC_INFO;
    qDebug() << "    Starting";
    std::cerr << "    thread:id: " << std::hex << std::this_thread::get_id() << std::endl;
    qDebug() << "    QThread::currentThreadId():" << QThread::currentThreadId();

    qDebug() << "    Invoke slot";
    const QString message = "Hi there.";
    QMetaObject::invokeMethod(object, "slot", Q_ARG(QString, message));

    qDebug() << "    Exiting";
}

std::trhead で生成したサブスレッドで実行する関数です。QMetaObject::invokeMethod() でメインスレッドのオブジェクトのスロットを呼び出します。 QMetaObject::invokeMethod() は、スレッドセーフな QCoreApplication::postEvent() を使って、メインスレッドのイベントループにスロットを呼び出すための内部イベントをポストしています。


int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    qDebug() << Q_FUNC_INFO;
    std::cerr << "    thread:id: " << std::hex << std::this_thread::get_id() << std::endl;
    qDebug() << "    QThread::currentThreadId():" << QThread::currentThreadId();

    Object object;

    std::thread nativeThread(worker, &object);

    const int ret = app.exec();

    nativeThread.join();

    return ret;
}

#include "main.moc"

std::thread で生成した非 Qt サブスレッドで、クラス Object のオブジェクトを渡して worker 関数を動かします。イベントループの終了後に、非 Qt サブスレッドの終了を待つようにしています。


int main(int, char **)
    thread:id: 0x114121dc0
    QThread::currentThreadId(): 0x114121dc0
void worker(QObject *)
    Starting
    thread:id: 0x7000064ac000
    QThread::currentThreadId(): 0x7000064ac000
    Invoke slot
    Exiting
void Object::slot(const QString &)
    thread:id: 0x114121dc0
    QThread::currentThreadId(): 0x114121dc0
    message: "Hi there."

実行結果です。スロットが Qt メインスレッドで実行されているのが分かります。


サンプルコード

stdthreadtomain.zip

参考資料

QMetaObject Struct
QCoreApplication Class
実行中にオブジェクトからそのクラスのオブジェクトを生成できますか。

Q_INVOKABLE を付けたメンバー関数は QMetaObject::invokeMethod() で呼び出しできるようになります。これが Q_INVOKABLE の一般的な使い方です。コンストラクターは特殊メンバー関数で Q_INVOKABLE を付けると、クラスのスタティックメタオブジェクトに対して QMetaObject::newInstance() を呼び出してインスタンス生成ができます。


main.cpp:
#include <QApplication>
#include <QMetaObject>
#include <QPushButton>

class PushButton : public QPushButton
{
    Q_OBJECT

public:
    Q_INVOKABLE PushButton(QWidget* parent = nullptr) : QPushButton(parent) {}
    Q_INVOKABLE PushButton(const QString& text, QWidget* parent = nullptr) : QPushButton(text, parent) {}
    Q_INVOKABLE PushButton(const QIcon& icon, const QString& text, QWidget* parent = nullptr) : QPushButton(icon, text, parent) {}
};

QPushButton を継承した PushButton のコンストラクターに Q_INVOKABLE を付けます。


int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    const auto someButton = new PushButton("OK?");
    someButton->setMinimumSize(someButton->sizeHint());
    QObject::connect(someButton, &PushButton::clicked, &app, &QApplication::quit);
    someButton->show();

    QObject* const someObject = someButton;

PushButton のオブジェクトを QObject のポインター someObject に入れておきます。


    if (qobject_cast<QPushButton*>(someObject)) {
        QObject* const maybeButton = someObject->metaObject()->newInstance(Q_ARG(QString, "Hi three!"));
        Q_ASSERT(maybeButton);
        maybeButton->setProperty("minimumSize", maybeButton->property("sizeHint"));
        QObject::connect(maybeButton, SIGNAL(clicked()), &app, SLOT(quit()));
        QMetaObject::invokeMethod(maybeButton, "show");
    }

    return app.exec();
}

#include "main.moc"

qobject_cast で someObject が QPushButton を継承したクラスか判定し、そのスタティックメタオブジェクトを metaObject() から newInstance() を呼び出して someObject と同じクラスのオブジェクトを生成しています。 metaObject() は仮想関数で、PushButton のスタティックメタオブジェクトが返ります。 qobject_cast によって、QPushButton を継承していると分かっているので、QPushButton のプロパティーの参照と設定をし、スロット show() を QMetaObject::invokeMethod() で呼びたせます。


サンプルコード

construct.zip

参考資料

QMetaObject Struct
Qt 6 の QTextStream で、コーデックを eucJP にして使うにはどのようにすればよいでしょうか。

Qt 5 では、QTextStream に setCodec メソッドで色々なコーデックを指定できました。 しかし、Qt 6 で setCodec メソッドは廃止され、Qt 6 で追加された setEncoding メソッドで、わずかなコーデックのみしか使えません。 Qt 6 で追加された QStringConverter クラスも同様にわずかのコーデックしか扱えません。 QStringConverter で他のコーデックを扱えるようにする予定はありますが、最新の Qt 6.6 で、まだ実装されていません。

回避策は、QTextCodec クラスが Qt5Compat モジュールに残されているので、以降のコードのようにして、生バイト列を変換する方法です。

QFile file("eucJP.txt");
file.open(QIODevice::ReadOnly | QIODevice::Text);
const QByteArray rawContents = file.readAll();
const QTextCodec* const codecOfContents = QTextCodec::codecForName("eucJP");
if (codecOfContents != 0) {
    const QString text = codecOfContents->toUnicode(rawContents);

参考資料

Absence of (supported) codec handling in Qt6 /Stream/Text/ReaderWriter

ウィジェット

片方のウィジェットを 2/3、もう一方を 1/3 の大きさで、スプリッターに配置するにはどのようにすればよいでしょうか。

スプリッターに配置したウィジェットの大きさを設定するには QSplitter::setSizes() を使います。


main.cpp:
#include <QApplication>
#include <QSplitter>
#include <QTextEdit>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    QSplitter splitter;
    splitter.addWidget(new QTextEdit);
    splitter.addWidget(new QTextEdit);
    splitter.setSizes( { 200, 100 } );
    splitter.show();

    return app.exec();
}

設定したい割合に応じた幅を指定して QSplitter::setSize() を呼び出します。


実行結果です。


サンプルコード

sizedsplitter.zip

参考資料

QSplitter::setSizes ドキュメント
ボタンにフォーカス枠を付けないようにできますか。

ボタンにフォーカス枠を描画させないようにするには、QProxyStyle をサブクラス化し、drawControl() を再実装して、ボタンに対して State_HasFocus フラグを落とすようにします


main.cpp:
#include <QApplication>
#include <QProxyStyle>
#include <QStyleOptionButton>
#include <QPushButton>
#include <QLayout>
#include <QDebug>

class Style : public QProxyStyle
{
public:
    Style() {}

    void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget = nullptr) const {
        if (element == CE_PushButton) {
            const QStyleOptionButton* styleOptionButton = qstyleoption_cast<const QStyleOptionButton*>(option);
            if (styleOptionButton) {
                QStyleOptionButton handyStyleOptionButton = *styleOptionButton;
                if (handyStyleOptionButton.state & State_HasFocus) {
                    handyStyleOptionButton.state = handyStyleOptionButton.state ^ State_HasFocus;
                }
                QProxyStyle::drawControl(element, &handyStyleOptionButton, painter, widget);
                return;
            }
        }
        QProxyStyle::drawControl(element, option, painter, widget);
    }

};

QPushButton の場合、State_HasFocus ビットをクリアーして、ベースクラス QProxyStyle の drawControl() を呼び出せば、フォーカス枠を付けないようにできます。


int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    const bool isMacOSStyle = (QString::compare(app.style()->name(), "macos", Qt::CaseInsensitive) == 0);
    app.setStyle(new Style);

    QWidget top;
    const auto topLayout = new QVBoxLayout(&top);

    for (const auto title : { "One", "Two", "Three" }) {
        const auto button = new QPushButton(title);
#if defined(Q_OS_MACOS)
        if (isMacOSStyle) {
            button->setAttribute(Qt::WA_MacShowFocusRect, false);
        }
#endif
        topLayout->addWidget(button);
    }

    top.show();

    return app.exec();
}

macOS スタイルの場合には、スタイルのカスタマイズでキーボードフォーカスを付かないようにできません。 macOS では、ウィジェット属性に Qt::WA_MacShowFocusRect を false にセットしてキーボードフォーカス枠を付かないようにします。


サンプルコード

nofocusrect.zip

参考資料

Styles and Style Aware Widgets ドキュメント
QProxyStyle ドキュメント
QWidget::setAttribute ドキュメント
QTextDocument のテキストのマージンを変更するにはどのようにすればよいでしょうか。

ドキュメント全体へマージンを指定する手順は以降のようになります。

  1. ドキュメンが入っているルートフレームを QTextDocument::rootFrame() を使って取り出します。
  2. QTextFrame::frameFormat() によって QTextFrameFormat を取り出します。
  3. 設定したい値で QTextFrameFormat::setMargin() を呼び出します。

main.cpp:
#include <QApplication>
#include <QTextEdit>
#include <QTextDocument>
#include <QTextFrame>
#include <QLayout>

class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness() : QWidget(nullptr) {
        const QVector<QString> sentences{
            "The QTextEdit is an editor having abundant functions that we can format in rich text.",
            "It can be used to display HTML and Markdown formats."
        };

        const auto topLayout = new QVBoxLayout(this);

        const QVector<QTextEdit*> textEdits{ new QTextEdit, new QTextEdit };
        for (const auto textEdit : textEdits) {
            topLayout->addWidget(textEdit);
            textEdit->setText(sentences.at(0));
            textEdit->append(sentences.at(1));
        }

QTextEdit のインスタンスを 2 つ生成し、それぞれに対して 2 つのテキストブロック (パラグラフ) をルートフレームに追加します。


        // The upper QTextEdit.
        QTextCursor textCursor(textEdits.at(0)->document());
        textCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
        QTextBlockFormat textBlockFormat = textCursor.blockFormat();
        textBlockFormat.setLeftMargin(50);
        textBlockFormat.setTopMargin(50);
        textCursor.setBlockFormat(textBlockFormat);

最初にパラグラフへのマージン設定をしてみます。QTextEdit が抱えるテキストドキュメントを取り出します。次に、そのテキストドキュメントを引数に渡してテキストカーソルを生成し、カーソルとアンカーをドキュメントの末端に移動します。カーソルとアンカーは、2 つ目のテキストブロック内の末端にあります。従って、2 つ目のパラグラフの上と左に 50 ピクセルのマージンが取られます。


        // The lower QTextEdit.
        textCursor = QTextCursor(textEdits.at(1)->document());
        textCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
        QTextFrame* const textFrame = textCursor.document()->rootFrame();
        QTextFrameFormat textFrameFormat = textFrame->frameFormat();
        textFrameFormat.setMargin(25);
        textFrame->setFrameFormat(textFrameFormat);
    }
};

冒頭で説明した全体へのマージン設定です。ルートのテキストフレームにマージンを設定します。ルートフレームのフレームフォーマットを取り出して、setMargin() で上下左右のマージンを設定し、ルートフレームにフレームフォーマットを設定します。両ブロックの左側に 25 ピクセルのマージンが取られます。1 つ目のブロックの上側には 25 ピクセルのマージンが取られます。


実行結果です。


サンプルコード

textmargins.zip

参考資料

QTextDocument::rootFrame ドキュメント
QTextFrame::frameFormat ドキュメント
QTextFrameFormat::setMargin ドキュメント
QPainterPath の直線や曲線に沿ってテキストを描くにはどのようするのでしょうか。

ペインターパスの途中にテキストを描くには、QPainterPath::pointAtPercent() で位置を求めて、その位置にテキストを描画します。


main.cpp:
#include <QApplication>
#include <QWidget>
#include <QPainterPath>
#include <QPainter>

class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness () : QWidget(nullptr) {}

private:
    void paintEvent (QPaintEvent*)
    {
        const QString message("Hello world!");

        const int drawWidth = width() / 100;

        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        QPen pen = painter.pen();
        pen.setWidth(drawWidth);
        pen.setColor("slateblue");
        painter.setPen(pen);

        QPainterPath path(QPointF(0.0, 0.0));

        const QPointF cp1(width() * 0.2, height() * 0.8);
        const QPointF cp2(width() * 0.8, height() * 0.2);

        path.cubicTo(cp1, cp2 , QPointF(width(), height()));

        painter.drawPath(path);

まず、ペインターパスで曲線を描きます。


        QFont font = painter.font();
        font.setPixelSize(drawWidth * 8);
        painter.setFont(font);
        pen.setColor("orangered");
        painter.setPen(pen);

        const double percentIncrease = 1.0 / (message.size() + 1);
        double percent = 0.0;

        for (int i = 0; i < message.size(); i++) {
            percent += percentIncrease;
            const QPointF point = path.pointAtPercent(percent);
            painter.drawText(point, QString(message.at(i)));
        }
    }

};

曲線に添って 1 文字ずつ描画します。QPainterPath::pointAtPercen() で文字の位置を求め、その位置に 1 文字描画します。


実行結果です。


サンプルコード

textonpath.zip

参考資料

QPainterPath::pointAtPercent ドキュメント
QSpinBox で入力値が空の状態をデフォルト値として扱うにはどのようにすればよいでしょうか。

QSpinBox では、通常は入力値を空にして表示はしません。しかし、textFromValue() を再実装して、ある条件の場合には空の文字列を返すようにすれば、入力値を空にできます。以降のサンプルコードでは、0 の代わりにに空の文字列を返すようにしています。


main.cpp:
#include <QApplication>
#include <QSpinBox>
#include <QLineEdit>
#include <QLayout>

class SpinBox : public QSpinBox
{
    Q_OBJECT

public:
    explicit SpinBox(QWidget* parent = nullptr)
        : QSpinBox(parent)
    {
        connect(lineEdit(), &QLineEdit::textChanged, [&]() {
                    if (lineEdit()->text().isEmpty()) {
                        setValue(defaultValue);
                    }
                });
    }

入力値を削除キーで空にしたときにデフォルト値になるようにします。


protected:
    QString textFromValue(int value) const override
    {
        if (value == defaultValue) {
            return QString();
        }
        return QSpinBox::textFromValue(value);
    }

このメソッドを再定義して、入力値がデフォルト値の場合に空文字列を返せば、スピンボックスの表示が空になります。


    int valueFromText(const QString& text) const override
    {
        if (text.isEmpty()) {
            return defaultValue;
        }
        return QSpinBox::valueFromText(text);
    }

private:
    int defaultValue = 0;
};

このメソッドを再定義して、入力が空の場合にデフォルト値を返すようにします。


サンプルコード

defaultemptyspinbox.zip

参考資料

QSpinBox::textFromValue ドキュメント
QSpinBox::valueFromText ドキュメント
ラベルに背景色を付けたときに文字色を黒と白のどちらにするか決める方法は Qt に用意されているでしょうか。

Qt に用意されている方法では関数 qGray() を使う方法があります。qGray は QRgb (unsigned int の typedef) 値から灰色を以下の計算式で求めて返します。

(r * 11 + g * 16 + b * 5) / 32

この値を閾値にして文字色に黒と白のどちらを使うかを判断するとほとんどの色でよい結果を得られます。


以降は SVG 色で確認をするサンプルコードです。


main.cpp:
#include <QApplication>
#include <QLabel>
#include <QLayout>
#include <QColor>
#include <QStringList>
#include <QScrollArea>
#include <QScrollBar>
#include <QPalette>

class ColorStack : public QWidget
{
    Q_OBJECT

public:
    explicit ColorStack(const QStringList& colors, QWidget* parent = nullptr);
};

ColorStack::ColorStack(const QStringList &colorNames, QWidget* parent)
    : QWidget(parent)
{
    const auto topLayout = new QVBoxLayout(this);
    topLayout->setSpacing(0);
    topLayout->setContentsMargins(QMargins(0, 0, 0, 0));

    for (const QString& colorName : colorNames) {
        const QColor color(colorName);

        const auto colorLabel = new QLabel(color.isValid() ? colorName : colorName + " (invalid)");
        colorLabel->setAutoFillBackground(true);
        auto colorLabelPalette = colorLabel->palette();
        colorLabelPalette.setColor(QPalette::Window, color);

QLabel に色名を表示し、背景色に色を設定します。


        int gray = 0;
        if (color.isValid()) {
            if (colorName.compare("transparent", Qt::CaseInsensitive) == 0) {
                gray = qGray(colorLabel->palette().color(QPalette::Window).rgb());
            } else {
                gray = qGray(color.rgb());
            }
        }
        if (double(gray) > (255.0 / 2.0)) {
            colorLabelPalette.setColor(QPalette::WindowText, "Black");
        } else {
            colorLabelPalette.setColor(QPalette::WindowText, "White");
        }

        colorLabel->setPalette(colorLabelPalette);
        topLayout->addWidget(colorLabel);
    }
}

背景色の qGray() 値を求め、その値が 255.0/2.0 より大きければテキスト色を黒にし、小さければ白にします。 "transparent" は Qt の SVG 色拡張で透明色です。"transparent" の場合にはウィンドウ背景色を使うようにします。


class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness();
};

Harness::Harness()
    : QWidget(nullptr)
{
    const auto topLayout = new QHBoxLayout(this);

    const auto scrollArea = new QScrollArea;
    scrollArea->setWidgetResizable(true);
    scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea->setWidget(new ColorStack(QColor::colorNames()));

    topLayout->addWidget(scrollArea);
}

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}

#include "main.moc"

ColorStack はアイテム数が多いので、QScrollArea に入れてスクロールして表示されるようにします。


実行結果です。


サンプルコード

colornames.zip

参考資料

QColor Class
QMainWindow は statusBar() を呼び出せば QStatusBar を付けられます。QWidget にステータスバーを付けるにはどのようにすれば良いでしょうか。

まず QWidget を親ウィジェットにして QStatusBar のインスタンスを生成します。次に QMainWindow と違ってレイアウトを自分でする必要があります。要点は 2 つです。

  1. QStatusBar を下端に配置する
  2. コンテンツマージンの左右と下を 0 にする

以下がサンプルコードです。ボタンをクリックすると QStatusBar にメッセージが 5 秒間表示されるようにしています。


main.cpp:
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QStatusBar>
#include <QLayout>

class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness() : QWidget(nullptr)
    {
        auto topLayout = new QVBoxLayout(this);
        int topMargin;
        topLayout->getContentsMargins(nullptr, &topMargin, nullptr, nullptr);
        topLayout->setContentsMargins(0, topMargin, 0, 0);
        topLayout->addStretch();

        auto showMessageButton = new QPushButton("Show Message");
        auto buttonLayout = new QHBoxLayout;
        buttonLayout->addStretch();
        buttonLayout->addWidget(showMessageButton);
        buttonLayout->addStretch();
        topLayout->addLayout(buttonLayout);

        topLayout->addStretch();

        statusBar = new QStatusBar(this);
        statusBar->setSizeGripEnabled(true);
        connect(showMessageButton, &QPushButton::clicked,
                [&]() {
                    statusBar->showMessage("Hi there!", 5000);
                });
        topLayout->addWidget(statusBar);
    }

private:
    QStatusBar* statusBar;
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}

#include "main.moc"

サンプルコード

statusbar.zip
ラベルをクリック可能にするにはどのようにすればよいですか。

ラベルをクリック可能にするには、ラベルを継承して通常のラベルにはないクリック処理の実装が必要です。


clickablelabel.h:
#pragma once

#include <QLabel>

class ClickableLabelPrivate;

class ClickableLabel : public QLabel
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(ClickableLabel)
    Q_PROPERTY(int value READ value WRITE setValue)

public:
    explicit ClickableLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
    explicit ClickableLabel(const QString& text, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
    ~ClickableLabel();

    int value() const;
    void setValue(int value);

signals:
    void clicked(int value = 0);
    void pressed(int value = 0);
    void released(int value = 0);

protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;

    // Returns true if pos is inside of a label, otherwise false.
    virtual bool hitTest(const QPoint& pos) const;

private:
    bool mousePressed; // True while the left button is pressed, otherwise false.
    bool mouseInside;  // True while a mouse pointer is inside of a label, otherwise false.
    ClickableLabelPrivate* d_ptr;
};

ヘッダーでは、クリック処理に必要なメソッドやシグナル、インベントハンドラーの再実装、ヒットテストを宣言しています。

clickablelabel.cpp:
#include "clickablelabel.h"
#include <QMouseEvent>
#include <QDebug>

class ClickableLabelPrivate
{
public:
    ClickableLabelPrivate() : value(0) {}

    int value;
};

pimplイディオム(pointer to implementation)として知られるテクニックで、クラスのプライベート実装の詳細を隠蔽するために使用されます。

ClickableLabel::ClickableLabel(QWidget* parent, Qt::WindowFlags f)
    : QLabel(parent, f), mousePressed(false), mouseInside(false), d_ptr(new ClickableLabelPrivate)
{
}

ClickableLabel::ClickableLabel(const QString& text, QWidget* parent, Qt::WindowFlags f)
    : QLabel(text, parent, f), mousePressed(false), mouseInside(false), d_ptr(new ClickableLabelPrivate)
{
}

ClickableLabel::~ClickableLabel()
{
    delete d_ptr;
}

int ClickableLabel::value() const
{
    Q_D(const ClickableLabel);

    return d->value;
}
void ClickableLabel::setValue(int value)
{
    Q_D(ClickableLabel);

    if (d->value == value) {
        return;
    }

    d->value = value;
}

コンストラクターやゲッター、セッターの実装です。value メンバー変数は、インスタンスに付属する情報として用意しましたが、int ではなく QVariant にすると用途が広がります。このサンプルコードでは、value の値が変更されたことを通知するシグナルは用意していません。必要に応じて用意してください。

void ClickableLabel::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton && hitTest(event->pos())) {
        mousePressed = true;
        mouseInside = true;
        emit pressed(value());
    }

    QLabel::mousePressEvent(event);
}

void ClickableLabel::mouseReleaseEvent(QMouseEvent* event)
{
    if (mousePressed && event->button() == Qt::LeftButton) {
        if (hitTest(event->pos())) {
            emit released(value());
            emit clicked(value());
        }
        mousePressed = false;
        mouseInside = false;
    }

    QLabel::mouseReleaseEvent(event);
}

void ClickableLabel::mouseMoveEvent(QMouseEvent* event)
{
    if (mousePressed && hitTest(event->pos()) != mouseInside) {
        mouseInside = !mouseInside;
        if (mouseInside) {
            emit pressed(value());
        } else {
            emit released(value());
        }
    }

    QLabel::mouseMoveEvent(event);
}

クリックや、プレス、リリースを処理するためのイベントハンドラーの再実装です。マウスボタンイベントを適宜処理して、シグナルを送信しています。

bool ClickableLabel::hitTest(const QPoint& pos) const
{
    return rect().contains(pos);
}

ラベルの内側でマウスボタンを操作したかどうかを判断するヒットテストの実装です。


main.cpp:
#include "clickablelabel.h"
#include <QApplication>
#include <QFrame>
#include <QPushButton>
#include <QLayout>

class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness();

private slots:
    void enableDisableChange();

private:
    ClickableLabel* clickableLabel;
    ClickableLabel* invisibleLabel;
    QPushButton* enableDisableButton;
};

Harness::Harness()
    : QWidget(0)
{
    auto visibleLabelDescription = new QLabel("Visible ->");
    auto invisibleLabelDescription = new QLabel("Invisible ->");

    clickableLabel = new ClickableLabel("Clickable");
    invisibleLabel = new ClickableLabel();
    invisibleLabel->setMinimumSize(64, 12);

    auto clickedButton = new QPushButton("Clicked");
    clickedButton->setFocusPolicy(Qt::NoFocus);
    auto pressedButton = new QPushButton("Pressed");
    pressedButton->setFocusPolicy(Qt::NoFocus);
    auto releasedButton = new QPushButton("Released");
    releasedButton->setFocusPolicy(Qt::NoFocus);

    auto lineFrame = new QFrame;
    lineFrame->setFrameShape(QFrame::HLine);
    lineFrame->setFrameShadow(QFrame::Sunken);
    lineFrame->setLineWidth(1);
    lineFrame->setMidLineWidth(0);

    enableDisableButton = new QPushButton("Disable");

    auto testLabelLayout = new QGridLayout;
    testLabelLayout->addWidget(visibleLabelDescription, 0, 0);
    testLabelLayout->addWidget(invisibleLabelDescription, 1, 0);
    testLabelLayout->addWidget(clickableLabel, 0, 1);
    testLabelLayout->addWidget(invisibleLabel, 1, 1);
    testLabelLayout->addWidget(clickedButton, 0, 2);
    testLabelLayout->addWidget(pressedButton, 1, 2);
    testLabelLayout->addWidget(releasedButton, 2, 2);

    auto topLayout = new QVBoxLayout;
    topLayout->addLayout(testLabelLayout);
    topLayout->addWidget(lineFrame);
    topLayout->addWidget(enableDisableButton, 0, Qt::AlignCenter);

    setLayout(topLayout);

    connect(clickableLabel, &ClickableLabel::clicked, clickedButton, &QPushButton::animateClick);
    connect(clickableLabel, &ClickableLabel::pressed, pressedButton, &QPushButton::animateClick);
    connect(clickableLabel, &ClickableLabel::released, releasedButton, &QPushButton::animateClick);

    connect(invisibleLabel, &ClickableLabel::clicked, clickedButton, &QPushButton::animateClick);
    connect(invisibleLabel, &ClickableLabel::pressed, pressedButton, &QPushButton::animateClick);
    connect(invisibleLabel, &ClickableLabel::released, releasedButton, &QPushButton::animateClick);

    connect(enableDisableButton, &QPushButton::clicked, this, &Harness::enableDisableChange);
}

void Harness::enableDisableChange()
{
    if (enableDisableButton->text() == "Enable") {
        clickableLabel->setEnabled(true);
        invisibleLabel->setEnabled(true);
        enableDisableButton->setText("Disable");
    } else {
        clickableLabel->setEnabled(false);
        invisibleLabel->setEnabled(false);
        enableDisableButton->setText("Enable");
    }
}

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}

#include "main.moc

テストコードです。表示されるラベルと見えない隠しラベルを用意し、操作にされたときにボタンをアニメーションするようにしました。

以下はテストのスクリーンショットです。

clickablelabel-enabled.png clickablelabel-disabled.png

サンプルコード

clicablelabel.zip
QLabel に複数行を表示したときに行間を調整するにはどのようにすればよいでしょうか。

QLabel には改行により複数行を表示したときに行間を調整する機能用意されていません。バグトラッカーには改善要望として QTBUG-33901 でこの機能の要望が登録されています。

簡単な方法は、HTML 記法でリッチテキストを使う方法で line-height を使って行間を調整する方法です。以下のように QLabel のテキストを記述します。

<p style="line-height:150%;">1 行目</p>
<p style="line-height:150%;">2 行目</p>
<p style="line-height:100%;">3 行目</p>

実行結果です。


QLabel の代わりに独自ウィジェットを作るか QTextEdit を QLabel のように見せて使う方法があります。前者については Qt のサンプルコード Elided Label Example が実装の参考になります。後者については以降のサンプルコードを参考にして使うか QTextEdit のサブクラスで同様な処理を行います。


main.cpp:
#include <QApplication>
#include <QLabel>
#include <QTextEdit>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include <QFontMetrics>
#include <QtMath>
#include <QLayout>

class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness() : QWidget(nullptr) {
        QPalette thisPalette = palette();
        thisPalette.setColor(QPalette::Window, QColor("red"));
        setPalette(thisPalette);

QLabel と QTextEdit の大きさを見てわかるようにするために背景色を赤色にしておきます。

        auto label = new QLabel;
        label->setAutoFillBackground(true);
        label->setText("First\nSecond\nThird\nFourth");

比較参考のための QLabel です。

        textEdit = new QTextEdit;
        textEdit->setFrameShape(QFrame::NoFrame);
        QPalette textEditPalette = textEdit->palette();
        textEditPalette.setColor(QPalette::Base, textEditPalette.color(QPalette::Window));
        textEdit->setPalette(textEditPalette);
        textEdit->setReadOnly(true);

QTextEdit の枠線を消し、背景色をウィンドウの通常背景色にし、読み込み専用に設定して QLabel と同じ外観にします。

        QTextCursor cursor = textEdit->textCursor();

        cursor.insertText("First");

        QTextBlockFormat blockFormat = cursor.blockFormat();
        blockFormat.setLineHeight(200, QTextBlockFormat::ProportionalHeight);
        cursor.setBlockFormat(blockFormat);

行高を 200% つまり二倍の高さに変更します。

        cursor.insertBlock();
        cursor.insertText("Second");

ここまでで一行目と二行目の高さが二倍の高さになります。

        cursor.insertBlock();
        blockFormat.setLineHeight(100, QTextBlockFormat::ProportionalHeight);
        cursor.setBlockFormat(blockFormat);

行高を 100% つまり元の高さに変更します。

        cursor.insertText("Third");
        cursor.insertBlock();
        cursor.insertText("Fourth");

三行目と四行目は通常の高さになります。

        auto topLayout = new QVBoxLayout(this);
        topLayout->addWidget(label);
        topLayout->addWidget(textEdit);
        topLayout->addStretch();

        connect(textEdit->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
                this, &Harness::textEditSizeChange);

QTextEdit 内のテキストサイズが変更されたときに QTextEdit の高さをテキストサイズの高さにするためのシグナルとスロットの接続です。

    }

private:
    QTextEdit* textEdit;

private slots:
    void textEditSizeChange(const QSizeF size) {
        const int newHeight = qCeil(size.height());
        if (textEdit->height() != newHeight) {
            textEdit->setFixedHeight(newHeight);
        }
    }

QTextEdit の高さを QTextEdit 内のテキストサイズの高さに設定しています。

};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}

#include "main.moc"

実行結果です。


サンプルコード

linespacing.zip

参考資料

It would be nice to have an editable line spacing feature in QLabel
Elided Label Example
QTextDocument
Label QML Type

ウィジェットではなく Qt Quick Controls の Label では lineHeight プロパティで QTextDocument の QTextBlockFormat::ProportionalHeight と同様に行高を調整できます。

QML/Qt Quick

重ね合わせる方法以外に Rectangle のボーダー幅を 1 ピクセルより細くする方法はありますか。

高解像度ディスプレーでは Rectangle の border.pixelAligned プロパティーを false に設定し boder.width に 1 より小さい値を設定することで 1 ピクセルよりも細いボーダーにできます。 Rectangle のドキュメントには border.width プロパティーの型は int と なっていますが、実際には real です。border.pixelAligned プロパティーはリファレンスに記載されていません。


Rectangle {
    id: withOverlap

    width: 100
    height: 100

    color: "black"

    Rectangle {
        x: 1 / Screen.devicePixelRatio
        y: x

        width: withOverlap.width - (2 / Screen.devicePixelRatio)
        height: width

        color: "red"
    }
}

重ね合わせによって、1 ピクセルより細いボーダーを付ける方法です。


Rectangle {
    id: withPixelAligned

    width: 100
    height: 100

    color: "red"
    border.width: 1 / Screen.devicePixelRatio
    border.pixelAligned: false
}

border.pixelAligned を false にすることで 1 ピクセルより細いボーダーを付ける方法です。


サンプルコード

thinborderrectangle.zip

参考資料

Rectangle QML Type
Screen QML Type
QML のコードで Qt のバージョンを判定するにはどのようにすればよいですか。

QML には Qt のバージョンを取得する機能が用意されていません。C++ のマクロ QT_VERSION_STR や QT_VERSION、関数 qVersion() のいずれかを使って、その値を QML 側で参照できるようにします。

QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("QT_VERSION_STRING", QT_VERSION_STR);

QT_VERSION_STR は文字列で "5.15.2" や "6.3.0" という値です。Text タイプなどで以下のようにして Qt のバージョンを扱えます。

Text {
    text: QT_VERSION_STRING
}

数値で判定するには、バージョン文字列を数値に変換する JavaScript 関数を用意します。

function qtVersionNumberFromString(versionString) {
    let [major, minor, bugfix] = versionString.split(".");
    return Number(major.padStart(2, "0") + minor.padStart(2, "0") + bugfix.padStart(2, "0"));
}

この関数は Qt のバージョン文字列を変換して 10 進数で 51502 や 60300 という値にします。以下のようにして判定できます。

if (qtVersionNumberFromString(QT_VERSION_STRING) < 60000) {
    ...
} else {
    ...
}

参考資料

QQmlContext::setContextProperty ドキュメント
Qt Quick Controls の Button の長押し時間を変更するにはどのようにすればよいですか。

Qt Quick Controls の Button のデフォルトの長押し時間は 800 ミリ秒です。長押し時間を変更するには QStyleHints の mousePressAndHoldInterval プロパティーの設定値を変更します。

以下がサンプルコードです。ボタンを押したままにすると 1.5 秒で Button の onPressAndHold ハンドラーが呼び出されます。


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

int main(int argc, char** argv)
{
    QGuiApplication app(argc, argv);

    // 長押し時間 (ミリ秒) を設定
    const int pressAndHoldInterval = 1500; // 1.5秒
    app.styleHints()->setMousePressAndHoldInterval(pressAndHoldInterval);
Rectangle で円形図形を作り MouseArea を適用すると円の外側でも反応してしまいます。円以外にも図形の内側だけで反応するようするにはどのようにすればよいでしょうか。

Qt 5.11 で Item に追加された containmentMask にマスクオブジェクトを設定すると MouseArea や Pointer Handler のマウスやタップをマスクできます。マスクオブジェクトとは以下のメソッドを実装した QObject です。

Q_INVOKABLE QObject::contains(const QPoint& point)

QObject のサブクラスでこのメソッド実装してタイプ登録をし MouseArea の containmentMask にマスクとして設定します。Qt 5.10 で導入された Qt Quick Shapes もマスクとして使え、こちらは簡単に不定形マスクを設定できます。

まず、C++ で実装した円形マスクのサンプルコードです。

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

class CircleMask : public QObject
{
    Q_OBJECT

    Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)

public:
    explicit CircleMask(QObject* parent = 0) : QObject(parent) {}

    ~CircleMask() {}

    qreal radius() const {
        return d.radius;
    }

    void setRadius(qreal radius)
    {
        if (d.radius == radius) {
            return;
        }
        d.radius = radius;
        emit radiusChanged(radius);
    }

    Q_INVOKABLE bool contains(const QPointF& point) const
    {
        const QPointF center(d.radius, d.radius);
        const QLineF line(center, point);
        return line.length() <= d.radius;
    }

signals:
    void radiusChanged(int radius);

private:
    struct {
        qreal radius;
    } d;
};

int main(int argc, char** argv)
{
    QGuiApplication app(argc, argv);

    qmlRegisterType<CircleMask>("Mask", 1, 0, "CircleMask");

    QQmlApplicationEngine engine;
    engine.load(QUrl("qrc:/main.qml"));

    return app.exec();
}

#include "main.moc"

contains() に渡って来た点が円の内側かを判定しているオブジェクトを QML 側で使えるようにしています。以下のように MouseArea の containmentMask に CircleMask オブジェクトを設定すれば円の内側だけでマウスに反応するようにできます。


main.qml:
import QtQuick 2.11
import QtQuick.Window 2.10
import Mask 1.0

Window {
    id: window

    visible: true

    minimumWidth: 300
    minimumHeight: 300

    color: "white"

    Rectangle {
        id: circle

        width: window.width/2; height: width
        anchors.centerIn: parent

        radius: width/2
        color: "red"
        border.width: circleMouseArea.containsMouse ? 4 : 0
        border.color: "firebrick"

        MouseArea {
            id: circleMouseArea

            anchors.fill: parent

            hoverEnabled: true
            containmentMask: CircleMask {
                radius: circle.radius

                onRadiusChanged: (radius) => {
                    print("radius:", radius);
                }
            }

            onPressed: {
                parent.color = "green";
            }

            onReleased: {
                parent.color = "red";
            }
        }
    }
}

Qt Quick Shapes を使えば QML だけで実現できます。以下のサンプルコードを実行すると半円の内側だけでマウスに反応します。


shapemousearea.qml:
import QtQuick 2.11
import QtQuick.Shapes 1.11
import QtQuick.Window 2.10

Window {
    id: window

    visible: true

    minimumWidth: 300
    minimumHeight: 300

    color: "white"

    Shape {
        id: halfCircle

        readonly property real radius: 60

        anchors.fill: parent

        layer.enabled: true
        layer.smooth: true
        layer.textureSize: Qt.size(2*width, 2*height)
        vendorExtensionsEnabled: false

        containsMode: Shape.FillContains

        ShapePath {
            id: halfArch

            strokeWidth: -1
            fillColor: "green"

            startX: halfCircle.width/2 - halfCircle.radius
            startY: halfCircle.height/2 - halfCircle.radius

            PathArc {
                id: halfPathArc

                x: halfCircle.width/2 + halfCircle.radius
                y: halfCircle.height/2 + halfCircle.radius
                radiusX: halfCircle.radius
                radiusY: halfCircle.radius
            }
        }
    }

    MouseArea {
        id: halfCircleMouseArea

        anchors.fill: halfCircle

        containmentMask: halfCircle

        onPressed: {
            halfArch.fillColor = "red";
        }

        onReleased: {
            halfArch.fillColor = "green";
        }
    }
}

Shape の containsMode プロパティーに Shape.FillContains を設定すると図形の内側だけで反応します。MouseArea の containmentMask には halfCircle を設定しているので簡潔でわかり易くなっています。


サンプルコード

maskedmousearea.zip
QML でプロパティーや関数をプライベートにするにはどのようにすればよいでしょうか。

QML は、プロパティーや関数を C++ のようにプライベートにできません。完全ではありませんが部分的にプライベート化する方法が用いられていますす。QtObject を配置し、その中にプロパティーや関数を閉じ込めます。サンプルコードは以下のようになります。


Circle.qml:
import QtQuick 2.15

Rectangle {
    id: root

    property real diameter: d.defaultDiameter

    QtObject {
        id: d

        readonly property real defaultDiameter: 50.0
        readonly property color defaultColor: "red"

        function center() {
            return Qt.point(x + width / 2, y + height / 2);
        }

        function setCenter(point: point) {
            const pointInParent = mapToItem(parent, point.x, point.y);
            x = pointInParent.x - radius;
            y = pointInParent.y - radius;
        }
    }

    width: diameter
    height: diameter
    radius: diameter / 2
    color: d.defaultColor
    border {
        width: 0.0;
        color: "grey";
    }

    MouseArea {
        id: mouseAreaOfRoot

        anchors.fill: parent

        onPressed: (mouse) => {
            d.setCenter(Qt.point(mouse.x, mouse.y));
            border.width = 1.0;
        }

        onPositionChanged: (mouse) => {
            d.setCenter(Qt.point(mouse.x, mouse.y));
        }

        onReleased: (_) => {
            border.width = 0.0;
            print(d.center());
        }
    }
}

QtObject 内に配置したプロパティーや関数は、Circle タイプを使う QML ファイルからは直接参照できなくなります。ただし、Circle 内に配置したタイプからは d を参照してアクセスできることに注意が必要です。


main.qml:
import QtQuick 2.15

Item {
    width: 400
    height: 400

    signal clicked()

    Circle {
        x: 100
        y: 100
        diameter: 100
    }

    Circle {
        x: 180
        y: 225
        color: "green"
    }

    Circle {
        x: 250
        y: 160
        diameter: 30
        color: "blue"
    }

Circle を使うサンプルコードです。


サンプルコード

pseudo-private.zip
画像ファイルを読み込み、画像を加工後にファイルに保存するにはどのようにすればよいですか。

画像ファイルを読み込み、枠を描画して、ファイルに保存してみましょう。

まず、Image に画像ファイルを読み込みます。 Canvas 要素上に元の画像を描画し、灰色の枠を描画します。枠線の色や幅は、ctx.strokeStyle と ctx.lineWidth で変更できます。 Canvas 要素自体をキャプチャし、Canvas に描画された枠線も含まれた画像を保存します。 以下は、Canvas.grabToImage() を使用して Canvas 要素をキャプチャし、ファイルに保存しています。


putborder.qml:
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    id: window

    visible: true

    width: 640
    height: 480

    title: "Draw Frame on Image and Save"

    Image {
        id: image

        source: "path_to_your_image"
        visible: false
    }

    Canvas {
        id: canvas

        anchors.fill: image

        onPaint: {
            var ctx = getContext('2d');

            ctx.drawImage(image, 0, 0);

            ctx.strokeStyle = "grey";
            ctx.lineWidth = 1;

            ctx.beginPath();
            ctx.moveTo(0, 0);
            ctx.lineTo(image.width, 0);
            ctx.lineTo(image.width, image.height);
            ctx.lineTo(0, image.height);
            ctx.lineTo(0, 0);
            ctx.stroke();
        }
    }

    Button {
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter

        text: "Save Image and Quit"

        onClicked: {
            canvas.grabToImage(function(result) {
                result.saveToFile("result.png");
                Qt.quit();
            });
        }
    }
}
Rectangle の任意の四隅を丸める方法はありますか。

Qt 6.6 までは、四隅いずれかを指定して角を丸める機能はなく、以下のような工夫をするか他の方法を取る必要がありました。

Rectangle {
    id: topLeftCornerRoundedRectangle

    x: 50
    y: 200
    width: 200
    height: 40

    color: "blue"

    Rectangle {
        id: clippingTopLeftCornerRectangle

        width: 20
        height: 20

        color: root.color

        Rectangle {
            id: circleAtLeft

            width: 40
            height: 40

            radius: 20

            color: "blue"
        }
    }
}

Qt 6.7 では、4 つのプロパティー bottomLeftRadius、bottomRightRadius、topLeftRadius、topRightRadius が追加されて、任意の角を丸めることができます。

Qt for Python

モデルビュー

QListView や QTableView、QTreeView などのビューでのドラグドロップはどのようにすればよいのでしょうか。

QAbstractListModel にドラグ&ドロップを利用できるようにするために以下のAPI が用意されています。

QStringList mimeTypes() const
QMimeData* mimeData(const QModelIndexList& indexes) const
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)

これらのメソッドを再実装すれば、どのビューでもドラグ&ドロップできるようになります。

以降のサンプルコードでビューのドラグ&ドロップ実装の要点を説明します。使用したモデルは Qt に登録されている国名リストで、文字列リストモデルの一般的な実装です。テーブルモデルとツリーモデルも同様にして実装できます。


main.cpp:
#include <QApplication>
#include <QAbstractListModel>
#include <QListView>
#include <QTableView>
#include <QTreeView>
#include <QMimeData>

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    explicit StringListModel(const QStringList& list, QObject* parent = nullptr)
        : QAbstractListModel(parent), list(list) {
    }

    int rowCount(const QModelIndex& parent = QModelIndex()) const {
        Q_ASSERT(!parent.isValid());
        return list.count();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
        if (!index.isValid()) {
            return QVariant();
        }

        if (index.row() < 0 || index.row() >= list.size()) {
            return QVariant();
        }

        if (role == Qt::DisplayRole) {
            return list.at(index.row());
        } else {
            return QVariant();
        }
    }

    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const {
        if (role != Qt::DisplayRole) {
            return QVariant();
        }

        if (orientation == Qt::Horizontal) {
            Q_ASSERT(section == 0);
            return QString("Country");
        } else {
            return QString("Country %1").arg(section + 1);
        }
    }
    Qt::ItemFlags flags(const QModelIndex &index) const {
        if (!index.isValid()) {
            return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;
        }

        return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
    }

Qt::ItemIsDragEnabled と Qt::ItemIsDropEnabled の各フラグビットを立ててアイテムをドラグ&ドロップ可能にします。

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) {
        Q_UNUSED(role);

        if (index.isValid()) {
            list.replace(index.row(), value.toString());
            emit dataChanged(index, index);
            return true;
        }

        return false;
    }

    bool insertRows(int position, int rows, const QModelIndex& parent) {
        Q_ASSERT(!parent.isValid());

        beginInsertRows(QModelIndex(), position, position + rows - 1);
        for (int row = 0; row < rows; ++row) {
            list.insert(position, "");
        }
        endInsertRows();

        return true;
    }

    bool removeRows(int position, int rows, const QModelIndex& parent) {
        Q_ASSERT(!parent.isValid());

        beginRemoveRows(QModelIndex(), position, position + rows - 1);
        for (int row = 0; row < rows; ++row) {
            list.removeAt(position);
        }
        endRemoveRows();

        return true;
    }
    QStringList mimeTypes() const {
        return QStringList() << contryListMimeTypeName;
    }

国名のリストを MIME タイプ x-text/x-plain で扱うようにします。

    QMimeData* mimeData(const QModelIndexList& indexes) const {
        QStringList list;
        foreach (const QModelIndex& index, indexes) {
            list << data(index).toString();
        }

        QMimeData* const mimeData = new QMimeData();
        mimeData->setData(contryListMimeTypeName, list.join(",").toUtf8());
        return mimeData;
    }

ドラグ&ドロップするアイテムがひとつの場合にはそのまま、複数の場合にはカンマ区切り文字列にしてから QByteArray に変換しています。QMiMeData に MIME タイプ contryListMimeTypeName (x-text/x-plain) で変換データを入れて戻り値にします。

    bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
        if  (canDropMimeData(data, action, row, column, parent)) {
            const QStringList items = QString(data->data(contryListMimeTypeName)).split(",");

            if (parent.isValid() && items.count() == 1) {  // (A)
                setData(parent, items.at(0));
            } else {  // (B)
                int itemCount = 0;
                for (auto const& item : qAsConst(items)) {
                    if (row < 0) {  // (C)
                        insertRows(rowCount(), 1, QModelIndex());
                        setData(index(rowCount() - 1, 0, QModelIndex()), item);
                    } else {  // (D)
                        insertRows(row + itemCount, 1, QModelIndex());
                        setData(index(row + itemCount, 0, QModelIndex()), item);
                    }
                    ++itemCount;
                }
            }

            return true;
        }

        return false;
    }

ドロップされたときに呼出されます。

(A) アイテム上にドロップした場合です。parent の有無でアイテム上へのドロップかどうかを判断できます。

(B) アイテム上以外にドロップした場合です。row が非負ならばアイテム間か最初のアイテムの上、最後のアイテムの下のいずれかにドロップした場合で、row が -1 ならばアイテムの親とみなせるビュー上の場所にドロップされた場合と判断できます。

(C) アイテムの親とみなせるビュー上の場所にドロップされた場合で、モデルの最後にアイテムを追加しています。

(D) アイテム間か最初のアイテムの上、最後のアイテムの下のいずれかにドロップした場合で、その行位置にアイテムを追加しています。

private:
    QStringList list;
    const QString contryListMimeTypeName = "x-text/x-plain";
};

ドラグ&ドロップで使う独自 MEMI タイプです。

QStringList availableCountryNames()
{
    static QStringList countries;

    if (countries.isEmpty()) {
        for (int countryIndex = QLocale::AnyCountry + 1; countryIndex <= QLocale::LastCountry; ++countryIndex) {
            countries << QLocale::countryToString(static_cast<QLocale::Country>(countryIndex));
        }
        countries.sort();
    }

    return countries;
}
#include <QDebug>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    StringListModel model(availableCountryNames());

    QListView list;
    QTableView table;
    QTreeView tree;

    using ViewToTitleMapping = std::tuple<QAbstractItemView*, QString>[];
    for (auto const& item : ViewToTitleMapping{ { &list, "List View" },
                                                { &table, "Table View" },
                                                { &tree, "Tree View" } }) {
        auto const view = std::get<0>(item);
        auto const title = std::get<1>(item);
        view->setSelectionMode(QAbstractItemView::MultiSelection);
        view->setModel(&model);
        view->setWindowTitle(title);
        view->setDragEnabled(true);
        view->setAcceptDrops(true);
        view->show();
    }

リストモデルをリストビューとテーブルビュー、ツリーリストビューに設定しています。これでどのビューでもドラグ&ドロップできることを確かめられます。

    return app.exec();
}

#include "main.moc"

サンプルコード

dndmodel.zip
QFileSystemModel を QTreeView に設定して使っています。ツリービューが表示されたときにツリーが展開され、各列幅が表示内容に合わされた状態にするにはどのようにすればよいでしょうか。

ツリーを展開するには QTreeView::expandToDepth() を使い、各列幅を表示内容に合わせて広げるには QTreeView::resizeColumnToContents() を使います。 これらの関数はツリービューに設定するモデルにデータがロードされた後に呼び出す必要があります。QFileSystemModel のデータのロード完了を通知するシグナル QFileSystemModel::directoryLoaded を利用してこれらの関数を呼び出せば、ツリービューを展開して、項目を列幅に合わせて表示できます。


main.cpp:
#include <QApplication>
#include <QTreeView>
#include <QFileSystemModel>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    QTreeView tree;
    tree.resize(800, 600);

    QFileSystemModel model;

    QObject::connect(&model, &QFileSystemModel::directoryLoaded, [&]() {
        tree.expandToDepth(1);
        for (int column = 0; column < tree.model()->columnCount(); ++column) {
            tree.resizeColumnToContents(column);
        }
    });

    tree.setModel(&model);
    tree.show();

    model.setRootPath(QDir::homePath());

    return app.exec();
}

サンプルコード

treeviewdecoration.zip
QTreeView に QFileSystemModel を設定して使っています。空のディレクトリーで枝分かれの +/- 表示をしないようにするにはどのようにすればよいでしょうか。

QFileSystemModel::hasChildren() をオーバライドし、空ディレクトリーの場合に false を返すようにすれば枝分かれの +/- を非表示にできます。

以下が枝分かれに関するサンプルコードです。QFileSystemModel::hasChildren() は QAbstractItemModel::hasChildren() は再実装しているので適宜必要な判断をするようにできます。


main.cpp:
#include <QApplication>
#include <QTreeView>
#include <QFileSystemModel>
#include <QDebug>

class FileSystemModel : public QFileSystemModel
{
public:
    FileSystemModel(QObject* parent = nullptr)
        : QFileSystemModel(parent)
    {
    }

    bool hasChildren(const QModelIndex& parent) const
    {
        QDir dir(filePath(parent));
        dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
        return (dir.count() != 0);
    }
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    QTreeView tree;

    FileSystemModel model;

    tree.setModel(&model);
    tree.show();

    model.setRootPath(QDir::homePath());

    return app.exec();
}

サンプルコード

treeviewemptydirectory.zip

参考資料

QFileSystemModel Class
ビューに表示される数値を編集可能にしたとき、スピンボックスに表示される数値を右寄せにするにはどのようにすればよいでしょうか。

スピンボックスを編集可能にしたときにキー入力できる部分は QLineEdit です。この QLineEdit のインスタンスを探して表示方法を設定します。QObject::findChild() を使ってオブジェクト名が "qt_spinbox_lineedit" のスピンボックスを探せます。このオブジェクト名は複合的なウィジェットの構成部品に付けてあるオブジェクト名で、Qt のソースコードを読むと見つけられます。ドキュメントには記載されていません。

デリゲートのエディター生成部分の実装例です。

main.cpp:
class CountryDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    ...
    QWidget* createEditor (QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override
    {
        if (index.column() == 1) {  // 2 列目が数値
            QWidget* const editor = QStyledItemDelegate::createEditor(parent, option, index);
            editor->setAutoFillBackground(true);  // 背景が透けて見えないようにする
            if (const auto spinBox = qobject_cast<QSpinBox*>(editor)) {
                // スピンボックスの QLineEdit を見つけて右寄せにする
                if (const auto lineEdit = spinBox->findChild<QLineEdit*>("qt_spinbox_lineedit")) {
                    lineEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
                }
            }
            return editor;
        }

        const auto editor = new QComboBox(parent);
        editor->installEventFilter(const_cast<CountryDelegate*>(this));
        return editor;
    }
    ...
}

参考資料

QObject::findChild
QStyledItemDelegate::createEditor

画像

画像を青や赤などに色付けするにはどのようにすればよいでしょうか。

プライベートクラス QPixmapColorizeFilter が使えます。プライベートクラスは、仕様が変更され削除されることがあるので注意してください。

プライベートクラスを使用するにはプライベートクラス用のヘッダーファイルが必要です。 オンラインインストーラーでインストールした場合は、プライベートクラス用のインクルードファイルはインストールされます。 OS に付属するバイナリパッケージでは、プライベートヘッダーが別パッケージの場合があるので、例えば Ubuntu では以下のようにしてインストールします。

# apt install qtbase5-private-dev

colorize.pro

QT += widgets widgets-private

プライベートクラスを使うためには CONFIG 変数に widgets-private を追加する必要があります。


main.cpp:
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QPixmap>
#include <QFileInfo>
#include <QTimer>
#include <private/qpixmapfilter_p.h>
#include <QDebug>

QPixmapColorizeFilter クラスのプライベートヘッダーをインクルードします。


class Harness : public QWidget
{
    Q_OBJECT

public:
    explicit Harness(const QString& fileName, const QColor& colorizeColor = QColor(7+50, 142, 17+50));
    ~Harness();

protected:
    void paintEvent(QPaintEvent* event);

private:
    struct HarnessPrivate {
        QString fileName;
        QPixmapColorizeFilter* colorizeFilter;
        QPixmap pixmap;
        QColor colorizeColor;
    } *d;
};

色付けした画像をウィジェットに描画して表示するためのクラスです。


Harness::Harness(const QString& fileName, const QColor& colorizeColor)
    : QWidget(nullptr), d(new HarnessPrivate)
{
    d->fileName = fileName;
    d->colorizeColor = colorizeColor;
    d->colorizeFilter = new QPixmapColorizeFilter(this);
    if (!d->pixmap.load(fileName)) {
        qWarning().noquote() << QString("%1: Cannot load").arg(fileName);
        return;
    }

QPixmapColorizeFilter のインスタンスを生成し、元の画像ファイルをを pixmap に読み込んでおきます。


    QTimer::singleShot(0, [&]() {
        QPixmap screenShot = grab();
        screenShot = screenShot.scaled(screenShot.size() / screenShot.devicePixelRatio());
        const QFileInfo fileInfo(d->fileName);
        const QString baseFileName = fileInfo.baseName();
        screenShot.save(QString("%1-converted.png").arg(baseFileName));
    });

ウィジェットに描画した結果をファイル名に -converted を付けて保存します。QWidget::grab() は、ウィジェットに描画された内容を QPixamp にして返します。 高解像度の場合には、ウィジェットへの描画結果の大きさは元の画像と違うので、QPixmap::devicePixeRatio() で割ったサイズを QPixmap::scaled() に指定して、元の画像の大きさにしてから保存します。


void Harness::paintEvent(QPaintEvent* event)
{
    Q_UNUSED(event);

    QPainter painter(this);
    d->colorizeFilter->setColor(d->colorizeColor);
    d->colorizeFilter->draw(&painter, QPointF(0.0, 0.0), d->pixmap);
}

色付けの色を setColor() で設定し、QPixmapColorizeFilter::draw() でウィジェットに色付けして描画します。 色付け強度を setStrength(qreal strength) で設定でき、1.0 が最も強度が高く、0.0 が色付けなしです。


画像ファイル名と色を SVG カラー名または "#rrggbb" のように指定して実行します。

$ colorize logo.png red
$ colorize logo.png "#ff0000"

> colorize.exe logo.png red
> colorize.exe logo.png "#ff0000"

サンプルコード

colorize.zip

参考資料

qpixmapfilter_p.h ソースコード
qpixmapfilter.cpp ソースコード
QPixmap::devicePixelRatio ドキュメント
アイコン画像ファイルから無効化画像を作成する方法はありますか。

Qt でアイコン画像ファイルから無効化 (グレーアウト) 画像を作成するには、QPixmap と QIcon クラスを利用する方法があります。まず、アイコン画像ファイルを QPixmap に読み込み、その後、QIcon の機能を使用して、このピクスマップの無効化ピックスマップを生成します。

アイコン画像ファイルを QPixmap に読み込みます。ファイルパスが正しいこと、そして画像ファイルにアクセスできることを確認してください。

const QPixmap originalPixmap("path/to/your/image.png");

次に、QPixmap から QIcon オブジェクトを作成します。QIcon クラスは、無効状態を含む様々なモードと状態で、元の画像を変換して描画できます。

const QIcon icon(originalPixmap);

アイコンに無効状態で描画されたピクスマップを要求できます。このとき必要なピクスマップのサイズを指定しなければなりません。通常は、元のピクスマップのサイズを使用しますが、必要に応じて異なるサイズを指定することもできます。

const QSize pixmapSize = originalPixmap.size();
const QPixmap disabledPixmap = icon.pixmap(pixmapSize, QIcon::Disabled);

この disabledPixmap は、元の画像をグレーアウトしたものとなり、QLabel やカスタムウィジェットの一部としてなど、QPixmap が受け入れられる場所であればどこでも使用できます。


以降は、画像を読み込み、その無効化画像を作成し、元の画像と無効化画像の両方を表示する簡単な例です。

main.cpp:
#include <QApplication>
#include <QWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QIcon>
#include <QDebug>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    if (app.arguments().count() != 2) {
        qWarning() << "Usage: disabledicon file.";
        return -1;
    }

    // The orignal image file
    const QString iconFileName = app.arguments()[1];

    QWidget window;
    const auto topLayout = new QHBoxLayout(&window);

    // Load the original pixmap
    const QPixmap originalPixmap(iconFileName);
    if (originalPixmap.isNull()) {
        qWarning() << "Bad image file";
        return -1;
    }

    // Create a QIcon from the pixmap
    const QIcon icon(originalPixmap);

    // Generate the disabled pixmap
    const QSize pixmapSize = originalPixmap.size();
    const QPixmap disabledPixmap = icon.pixmap(pixmapSize, QIcon::Disabled);

    // Display both pixmaps
    const auto originalLabel = new QLabel;
    originalLabel->setPixmap(originalPixmap);
    topLayout->addWidget(originalLabel);

    const auto disabledLabel = new QLabel;
    disabledLabel->setPixmap(disabledPixmap);
    topLayout->addWidget(disabledLabel);

    window.show();

    return app.exec();
}

この例では、QWidget と水平レイアウト (QHBoxLayout) を使用して、QLabel ウィジェットを使い、元の画像のピクスマップと無効化画像を並べて表示しています。

参考資料

QIcon Class

ネットワーク

Qt Creator

Windows

Linux

macOS

ライセンスモデルに関する FAQ

QtのLGPL版を利用するとソースコードを開示しないといけないのでしょうか。
開発したアプリケーション部分のソースコードを開示する必要はありません。
Qt自身に手を加えるような使い方をした場合に、改変部分のソースコードの開示義務が発生します。
Qtのライブラリにスタティックリンクした場合も改変とみなされ、該当部分が開示対象となります。
その他のLGPLの留意点は以下のページをご覧ください。
LGPL詳細
QtのLGPL版を利用しているのですが、技術的な問合せなどは可能でしょうか。
SRAでは、LGPL版ユーザ様向けのサポートを提供しています。詳細は以下のページをご覧ください。
LGPLサポート詳細

Qtの商用版とLGPL版を併用することは可能でしょうか。
同一プロジェクト内で、Qtの商用版を使用する開発者がいる場合に、他の開発者がLGPL/GPL版のみを使用することはできません。
なお、Qtを使用して作成されたソースコードに共通部分がある場合は、同一プロジェクトとみなされます。
商用版からLGPL版、またはLGPL版から商用版への移行は可能でしょうか。
LGPL 版で開発をした後に商用版に移ることは原則としてできませんが、商用版からLGPL版への移行は可能です。