Qtブログ(日本語)

Qt Widgets から Qt Quick へ:アプリケーション移行記 - 第1部

作成者: Qt Group 日本オフィス|Feb 1, 2026 10:00:00 AM
本記事はゲスト執筆者 Kevin Ottens による「Qt Widgets to Qt Quick, An Application Journey Part 1」の抄訳です。

二つのソフトウェアアーキテクチャの物語

私は20年以上にわたりQtを断続的に使用してきました。その間、Qt Widgetsが進化し、その後 Qt Quick が登場するのを目の当たりにしました。これはQt内で二つの異なるグラフィックススタックが利用可能になったことを意味します。

もちろん、それぞれ長所と短所がありますが、既存のQt WidgetsコードをQt Quickに移行する場合、最善の進め方は何でしょうか?この問いに対する明確な答えが公の議論で見つからなかったため、QtWS25で講演を行いました。

本日より、この短い連載を開始し、読者の皆様がご自身のペースで読める長文コンテンツを提供します。広くQtユーザーコミュニティの役に立てば幸いです。

「Qt WidgetsからQt Quickへの移植」問題の核心は、ソフトウェアアーキテクチャに関する議論にあります。enioka Haute Couture の多言語開発者兼技術リーダーとして、私は顧客を通じて様々な状況に遭遇してきましたが、当社にはソフトウェアアーキテクチャ設計の確かなノウハウがあります。

この最初の記事では、Qtアプリケーションで使用されるソフトウェアアーキテクチャパターンに関する私の見解を提示します。

Qt Widgetsアプリケーションの典型的なソフトウェアアーキテクチャ

以下に、Qt Widgetsアプリケーションの典型的なソフトウェアアーキテクチャを可視化する方法を示します。

考察

当然ながら、この図で示した各タイプのインスタンスは複数存在すると想定すべきです。ここでは3つのクラスが相互作用していますが、実際のアプリケーションではこのパターンが繰り返し適用されるでしょう。このアーキテクチャパターンはほぼModel-View-Controller(MVC)に相当します。後述しますが、この概念は本稿の考察において重要です。まずは各構成要素を検討しましょう。

まず、モデルクラスは従来のQObjectサブクラスまたは値型です。アプリケーション内にはおそらく多数存在します。これらは主に、シグナルの送信やウィジェットからの直接呼び出しを通じてウィジェット(コントローラー)と相互作用します。モデルクラスとウィジェットクラス間には複雑な関係が存在する可能性が考えられます。

次にビュー部分です。これもC++コードですが、おそらく生成されたものです。多くの場合、Qt Designer(スタンドアロン版または Qt Creator 組み込み版)を用いてビューの内容をグラフィカルに設計します。これにより.uiファイルが生成され、uic によって対応するC++コードが生成されます。

最後にコントローラー部分があります。これは対応するビューを作成します(ビューごとに1つのウィジェットが存在する場合やその逆の場合など、極めて稀なケースを除く)。コントローラーとビュー間の通信は双方向のシグナルとスロットを使用します。Qtのウィジェットベースアプリケーションでは、これらのクラスがQWidgetを継承するため、ここではそのようなコントローラーも「ウィジェット」と呼びます。

このため、私はこのソフトウェアアーキテクチャをMVCではなく「ひねりを加えたMVC」と呼んでいます。通常のMVCアプリケーションでは、コントローラとビュー間、あるいはコントローラとGUIスタック間の結合はこれほど強くありません。

簡略化されたコード

このようなアプリケーションの場合、コードの大部分はウィジェット/コントローラに集中し、おそらく以下のような形になります。


class Widget : public QWidget {
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr): ui{std::make_unique()} {
        ui->setupUi(this);
        // Probably a few connects between this and ui to slots manipulating m_model
    }

    void setModel(Model *model) {
        // Maybe a few connects from model to slots impacting ui
        m_model = model;
    }
private:
    std::unique_ptr ui;
    Model m_model = nullptr;
};

 

「Model」の存在と「uic」によって生成された「Ui::View」を前提とすると、上記のコードはモデル、ビュー、コントローラを接続するために最低限必要なものです。

もちろん、実際にすべてを統合するには、おそらくどこかに以下のような記述が必要になるでしょう。


auto model = new Model(parent); // Or called a function to get a pointer to an instance
auto widget = new Widget(parent);
widget->setModel(model);
widget->show();

Qt Quickアプリケーションの典型的なソフトウェアアーキテクチャ

では、Qt Quickアプリケーションの典型的なソフトウェアアーキテクチャはどうでしょうか? 関与するコンポーネントの可視化がこちらです。



考察

再び、この図で説明されている各クラスのタイプについて、複数のインスタンスを想定する必要があります。典型的なQt Quickアプリケーションでは、このクラスの相互作用パターンが何度も繰り返されます。

このアーキテクチャパターンはモデル・ビュー・プレゼンター(MVP)パターンに類似しています。このパターンの各要素を検討しましょう。

まずモデルクラスです。MVCの場合と同様、これらは従来のQObjectサブクラスまたは値型です。これらのクラスはプロキシ(時に「サービス」とも呼ばれる)と、主にシグナルの発行やプロキシからの直接呼び出しによって相互作用します。この通信の側面は、先に説明したモデル/コントローラ間の通信と類似しており、同様にモデルクラスとプロキシクラス間には複雑な関係が存在する可能性が高いです。

Qt Quickの場合、ビューコンポーネントは大きく異なります。C++コードの代わりにQMLコードを使用します。これはグラフィカルエディタで作成することも可能ですが、手動で作成されるケースがより一般的です。ビューはプロキシの作成または既存プロキシの使用も担当します。これは「ひねりを加えたMVC」で見たコントローラがビューを作成する責任とは逆の役割です。

最後にプロキシ部分があります。プロキシは対応するモデルを見つける手段を必要とします。プロキシとビュー間の通信にはプロパティバインディングとビューからの直接メソッド呼び出しが使用されます。

Simplified Code

簡略化したコードは以下のような形になります。


class Proxy : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString modelId READ modelId WRITE setModelId NOTIFY modelIdChanged)
    Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)
    QML_ELEMENT
public:
    using QObject::QObject
    // Getter and setters for the properties above

private:
    // Locate or create the model parts we need based on modelId
    Model *model() const;
};

 

Qtウィジェットの場合との顕著な違いは、今回のプロキシが単純なQObjectである点です。QMLランタイムと統合され、そのインターフェースがQML言語で利用可能ですが、GUIスタックとの強固な結合はありません。

ビューのQMLコードは次のようになります。


import QtQuick 2.0 as QQ
// Assuming Proxy has been registered in the com.enioka.hc.app namespace
import com.enioka.hc.app 1.0 as App

QQ.Item {
    App.Proxy {
        id: proxy
        modelId: "whatWeNeed"
    }
    QQ.Text {
        anchors.centerIn: parent
        text: proxy.value
    }
}

 

このビューはプロキシを作成し、modelIdプロパティを使用して関連するモデルオブジェクトを見つけるために必要な情報を渡します。GUIは表示のためにproxy.valueプロパティにもバインドされます。

動作するアプリケーションを得るには、main関数内で以下のような処理が必要です。


QQmlApplicationEngine engine;
// Assuming the View.qml has been registered in the com.enioka.hc.app module
engine.loadFromModule("com.enioka.hc.app", "View");

その次は?

Qt WidgetsとQt Quickアプリケーションは一般的に異なるソフトウェアアーキテクチャを持つことを確認しました。ここから導き出せる結論を見てみましょう。

まず明らかなのは、Qt Widgetsベースのアプリケーションの典型的なソフトウェアアーキテクチャが再利用性を妨げていることです。この「ひねりを加えたMVC」はウィジェット構成パターンに過度に特化しています。これは利点もあり、大規模なGUI内でこうしたウィジェットを素早く組み立てられるからです。しかし、その代償として、ビジネスロジックの相当部分がGUIに閉じ込められてしまいます。このロジックを異なる文脈(例えばQt Quickへの移植時)で再利用する必要が生じた際、問題が顕在化するのは必然です。

偏見はあるものの、Qt Quickアプリケーションが採用するソフトウェアアーキテクチャパターンの方が優れていると私は考えます。これにより、ビューとプロキシ間の結合度が大幅に低減されます。少なくとも残存する結合は適切な方向性(ビューがプロキシを認識するが、逆は成立しない)を持っています。このMVPアーキテクチャパターンは将来性が高く、同一のプロキシ群に異なるGUIを接続することを可能にします。

Qt Widgetsアプリケーションの状況は残念ですが、なぜこのような状態に至ったのでしょうか?結局のところ、彼らが「ひねりを加えたMVC」パターンに従う必要はなかったのです。歴史的経緯から、我々は集団的にこの手法を採用することに終わったのです。当初はQt Widgetsしか存在せず、より良い手法を知らなかった(私もその一員です)。さらに、公式のQt Widgetsドキュメントやチュートリアルは「ひねりを加えたMVC」パターンに従う例しか示していません。小規模な例では問題ありませんが、これまで見てきたように、アプリケーションをスケールアップする際には時間の試練に耐えられないのです。

幸い、現状を打破する道筋は見えてきました。

次回予告

本シリーズ次回では、従来の「ひねりを加えたMVC」アーキテクチャからMVPへ、レガシーQt Widgetsアプリケーションを責任を持って移行する方法を解説します。プロジェクトへの影響を最小限に抑えるため、事前の準備を徹底的に行います。

 

enioka Haute Coutureについて

あらゆる業種でITシステムへの依存度が高まっています。しかし多くの企業が、この重要な資産の管理権を失っています。

enioka Haute Couture は、プロジェクトの領域・組織・タイミングのいずれに起因する複雑なソフトウェア開発の習得を専門としています。顧客チームと密接に連携し、オーダーメイドのソフトウェアソリューションを開発します。ソフトウェア開発においては、顧客が管理権を保持できるよう、eniokaは一歩踏み込んだ取り組みを行います。

eniokaは顧客チームが適切な組織体制・技術・ツールを採用する支援を行います。さらに、適切な場合にはフリー&オープンソースソフトウェアプロジェクトの立ち上げ支援や既存プロジェクトとの連携も提供します。

enioka Haute Couture は、お客様がシステムを完全に管理しながら開発ニーズを実現する、信頼できるパートナーです。ベンダーやサービスプロバイダーへの依存から解放されます。