本文へスキップします。

本文へ

【全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 ドキュメント
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 文字列を表示しようとしたためです。


サンプルコード
@@.zip
参考資料
@@
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++日本語リファレンス
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 ドキュメント
非 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

ウィジェット

片方のウィジェットを 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

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

モデルビュー

画像

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

プライベートクラス 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 Creator

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

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

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