QT CREATOR プラグイン開発: 新しいプロジェクト タイプの追加

Qt Creator では、新しいプロジェクト タイプが [ファイル] -> [新規] メニュー項目に表示されます。プロジェクト タイプを選択するダイアログ ボックスを開いて、必要なプロジェクトを見つけることができます。

Qt Creator の新しいプロジェクト

この章では、上に表示されたダイアログ ボックスに新しいプロジェクト タイプを追加する方法を学習します。

Core::IWizard インターフェイス

Qt Creator は、新しいプロジェクト タイプを追加するための Core::IWizard インターフェイスを提供します。インターフェイスは src/plugins/coreplugin/dialogs/iwizard.h で宣言されています。

class CORE_EXPORT IWizard
    : public QObject
{
    Q_OBJECT
public:
    enum WizardKind {
        FileWizard = 0x01,
        ClassWizard = 0x02,
        ProjectWizard = 0x04
    };
    Q_DECLARE_FLAGS(WizardKinds, WizardKind)

    IWizard(QObject *parent = 0) : QObject(parent) {}
    virtual ~IWizard() {}

    virtual WizardKind kind() const = 0;
    virtual QIcon icon() const = 0;
    virtual QString description() const = 0;
    virtual QString displayName() const = 0;
    virtual QString id() const = 0;

    virtual QString category() const = 0;
    virtual QString displayCategory() const = 0;

    virtual void runWizard(const QString &path, QWidget *parent) = 0;

    // Utility to find all registered wizards
    static QList allWizards();
    // Utility to find all registered wizards of a certain kind
    static QList wizardsOfKind(WizardKind kind);
};

ご覧のとおり、Qt Creator は次のタイプのウィザードをサポートしています。

  • ファイル
  • クラス
  • プロジェクト

Core::IWizard は、上記のウィザードを実装するために実装する必要があるインターフェイスです。

Core::IWizard のシンプルな実装

次に、IWizard インターフェイスを独自に実装して、新しいプロジェクト タイプ「カスタム プロジェクト」を追加し、新しいプロジェクト タイプが「新しいプロジェクト」ダイアログ ボックスに表示できるようにします。

Core::IWizard インターフェイスを実装する

まず、新しいクラス CustomProjectWizard を作成して Core::IWizard を実装します。

#ifndef CUSTOMPROJECTWIZARD_H
#define CUSTOMPROJECTWIZARD_H

#include <coreplugin/dialogs/iwizard.h>

class QIcon;
class QString;
class QWidget;

class CustomProjectWizard : public Core::IWizard
{
public:
    CustomProjectWizard() { }
    ~CustomProjectWizard() { }

    Core::IWizard::WizardKind kind() const;
    QIcon icon() const;
    QString description() const;
    QString displayName() const;
    QString id() const;

    QString category() const;
    QString displayCategory() const;

    void runWizard(const QString &path, QWidget *parent);
};

#endif // CUSTOMPROJECTWIZARD_H

この記事の特典として、Qt 開発学習パッケージと技術ビデオ (C++ 言語の基礎、Qt プログラミングの概要、QT シグナルとスロットのメカニズム、QT インターフェイス開発イメージの描画、QT ネットワーク、QT データベース) を無料で受け取ることができます。プログラミング、QT プロジェクトの実践、QT 組み込み開発、Quick モジュールなど) ↓↓↓↓下記参照↓↓料金受け取り記事下部をクリック↓↓

以下では、各機能の実装について説明します。

関数 kind() は、実装する IWizard インターフェイスのタイプを Qt Creator に伝えるために使用されます。その正当な値は、前述の IWizard::WizardKind 列挙です。この例では、ProjectWizard を返します。

Core::IWizard::WizardKind CustomProjectWizard::kind() const
{
    return IWizard::ProjectWizard;
}

関数 icon() は、プロジェクトを表すアイコンを返し、新しいダイアログ ボックスのリストの左側に表示されます。この例では、Qt Creator アイコンを直接返します。

QIcon CustomProjectWizard::icon() const
{
    return qApp->windowIcon();
}

description()、displayName()、category()、displayCategory()、および id() 関数は、表示名、カテゴリなど、提供する新しいプロジェクト タイプのメタデータを返すために使用されます。このうち、display で始まるいくつかの関数はインターフェース上に表示される文字列に使用されるため、tr() 関数で返す必要があります。

QString CustomProjectWizard::description() const
{
    return "A custom project";
}

QString CustomProjectWizard::displayName() const
{
    return tr("Custom Project");
}

QString CustomProjectWizard::id() const
{
    return "CustomProjectID";
}

QString CustomProjectWizard::category() const
{
    return "GalaxyWorld";
}

QString CustomProjectWizard::displayCategory() const
{
    return tr("GalaxyWorld");
}

ユーザーが「CustomProject」カテゴリを選択すると、runWizard() 関数が呼び出されます。この関数は、新しいプロジェクト、ファイル、またはクラスを作成するために必要な情報をユーザーに尋ねるために、ダイアログ ボックスまたは QWizard オブジェクトを表示する必要があります。この例では、この関数はダイアログ ボックスを表示するだけです。

void CustomProjectWizard::runWizard(const QString &path, QWidget *parent)
{
    Q_UNUSED(path);
    Q_UNUSED(parent);

    QMessageBox::information(parent, "Custom Wizard Dialog", "Hi there!");
}

プラグインを提供する

この時点で、カスタム プロジェクトに必要なコードが完成しました。次に、前と同じようにプラグイン パッケージを提供する必要があります。

bool CustomProjectWizardPlugin::initialize(const QStringList& args, QString *errMsg)
{
    Q_UNUSED(args);
    Q_UNUSED(errMsg);

    addAutoReleasedObject(new CustomProjectWizard);

    return true;
}

Qt Creator カスタム プロジェクト

 

事前定義された IWizard 実装: Core::BaseFileWizard

Qt Creator は、IWizard インターフェイスのデフォルト実装 (Core::BaseFileWizard クラス) を提供しています。このクラスは、
IWizard インターフェイスのすべての関数のデフォルト実装を提供し、独自の固有の仮想関数を追加します。このクラスを利用するには、クラスを継承し、そのクラス内の 1 つ以上の関数をオーバーライドする必要があります。

Core::GeneratedFile と Core::GeneratedFiles

一般に、新しいウィザード (IWizard の実装) はユーザーにさまざまなプロンプトを提供し、最終的に 1 つ以上のファイルを生成します。Core::GeneratedFile クラスは、生成されるファイルを抽象化するのに役立ちます。Core::BaseFileWizard のサブクラスで、自動生成されたファイルごとに Core::GeneratedFile クラスのインスタンスを作成できることをすぐに学びます。Core::GeneratedFile は coreplugin/basefilewizard.h で宣言されています。

class CORE_EXPORT GeneratedFile
{
public:
    enum Attribute { // Open this file in editor
                     OpenEditorAttribute = 0x01,
                     // Open project
                     OpenProjectAttribute = 0x02,
                     /* File is generated by external scripts, do not write out,
                      * see BaseFileWizard::writeFiles() */
                     CustomGeneratorAttribute = 0x4
                   };
    Q_DECLARE_FLAGS(Attributes, Attribute)

    GeneratedFile();
    explicit GeneratedFile(const QString &path);
    GeneratedFile(const GeneratedFile &);
    GeneratedFile &operator=(const GeneratedFile &);
    ~GeneratedFile();

    // Full path of the file should be created, or the suggested file name
    QString path() const;
    void setPath(const QString &p);

    // Contents of the file (UTF8)
    QString contents() const;
    void setContents(const QString &c);

    QByteArray binaryContents() const;
    void setBinaryContents(const QByteArray &c);

    // Defaults to false (Text file).
    bool isBinary() const;
    void setBinary(bool b);

    // Id of editor to open the file with
    QString editorId() const;
    void setEditorId(const QString &k);

    bool write(QString *errorMessage) const;

    Attributes attributes() const;
    void setAttributes(Attributes a);

private:
    QSharedDataPointer m_d;
};

typedef QList GeneratedFiles;

Core::BaseFileWizard サブクラスによって生成する必要があるファイルは、Core::GeneratedFile クラス オブジェクトによって表されます。このクラスには、生成する必要があるファイルの 3 つの主要なプロパティが含まれています。

  1. ファイル名 (および絶対パス)
  2. ファイルの編集に必要なエディタのタイプ。有効な値は次のとおりです: CppEditor::Constants::CPPEDITOR_KINDGenericProjectManager::Constants::PROJECT_KINDGit::Constants:: GIT_COMMAND_LOG_EDITOR_KINDGit::Constants:: C_GIT_COMMAND_LOG_EDITOR
  3. 文書の内容

次の内容を含む C++ ファイルを生成する必要があるとします。

#include <iostream>

int main()
{
    cout << "Hello World\n";

    return 0;
}

Core::GeneratedFile を次のように使用します。

#include <coreplugin/basefilewizard.h>
#include <cppeditor/cppeditorconstants.h>

Core::GeneratedFile genFile("C:/Path/To/Source.cpp");
genFile.setEditorId(CppEditor::Constants::CPPEDITOR_ID);
genFile.setContents(
                     "#include <iostream>\n"
                     "\n"
                     "int main()\n"
                     "{\n"
                     "    cout << \"Hello World\n\";\n"
                     "    \n"
                     "    return 0;\n"
                     "}"
                    );
genFile.write(NULL);

アイテムモデルクラスウィザード

データ項目モデル ファイルを自動的に生成するのに役立つ新しいクラス ウィザードを提供するとします。このファイルは次のデータに基づいています。

  • モデルクラス名。
  • 親クラス名 (QAbstractItemModel、QAbstractListModel、または QAbstractTableModel のいずれかになります)。
  • ヘッダファイル名。
  • ソースコードのファイル名。

次に、この「データ項目モデル」クラスウィザードを Qt Creator に提供します。

ステップ 1: クラス ウィザード インターフェイスを設計する

まず、Qt Designer を使用して、前述したさまざまなデータをユーザーから収集するための単純なインターフェイスを設計する必要があります。

ModelClassPage UI

このインターフェイスの名前は ModelNamePage.ui です。

ステップ 2: クラス ウィザード ページを実装する

次に、この UI インターフェイスを Qt クラスにインポートして、インターフェイス データを取得できるようにします。まず、必要なデータを保存するための構造を設計する必要があります。

struct ModelClassParameters
{
    QString className;
    QString headerFile;
    QString sourceFile;
    QString baseClass;
    QString path;
};

次に、ウィザード ページを表すクラスを宣言します。これは、ユーザーが必要なコンテンツを入力するために設計したインターフェイスを使用できるように、前に作成した UI クラスをロードするために使用されます。

#ifndef MODELNAMEPAGE_H
#define MODELNAMEPAGE_H

#include <QWizardPage>

struct ModelClassParameters
{
    QString className;
    QString headerFile;
    QString sourceFile;
    QString baseClass;
    QString path;
};

namespace Ui {
class ModelNamePage;
}

class ModelNamePage : public QWizardPage
{
    Q_OBJECT

public:
    explicit ModelNamePage(QWidget *parent = 0);
    ~ModelNamePage();

    void setPath(const QString& path);
    ModelClassParameters parameters() const;

private slots:
    void onClassNameChange(const QString & name);

private:
    Ui::ModelNamePage *ui;
    QString path;
};

#endif // MODELNAMEPAGE_H

コンストラクターとデストラクターはどちらも非常に単純です。

ModelNamePage::ModelNamePage(QWidget *parent) :
    QWizardPage(parent),
    ui(new Ui::ModelNamePage)
{
    ui->setupUi(this);
}

ModelNamePage::~ModelNamePage()
{
    delete ui;
}

関数 setPath() は、ファイル パスを保存するために使用されます。

void ModelNamePage::setPath(const QString& path)
{
    this->path = path;
}

スロット関数 onClassNameChange() は、ユーザーが入力したクラス名に基づいて、対応するヘッダー ファイルとソース コード ファイルの名前を自動的に生成します。

void ModelNamePage::onClassNameChange(const QString &name)
{
    ui->headerEdit->setText(name + ".h");
    ui->implEdit->setText(name + ".cpp");
}

最後に、関数parameters()を使用してModelClassParametersインスタンスを返します。これは実際にユーザーが入力したデータを返します。

ModelClassParameters ModelNamePage::parameters() const
{
    ModelClassParameters params;
    params.className = ui->classNameEdit->text();
    params.headerFile = ui->headerEdit->text();
    params.sourceFile = ui->implEdit->text();
    params.baseClass = ui->baseClassBox->currentText();
    params.path = path;
    return params;
}

コア::ベースファイルウィザード

Core::BaseFileWizard クラスは coreplugin/basefilewizard.h ファイルで宣言されています。

class CORE_EXPORT BaseFileWizard : public IWizard
{
    Q_DISABLE_COPY(BaseFileWizard)
    Q_OBJECT

public:
    virtual ~BaseFileWizard();

    // IWizard
    virtual WizardKind kind() const;
    virtual QIcon icon() const;
    virtual QString description() const;
    virtual QString displayName() const;
    virtual QString id() const;

    virtual QString category() const;
    virtual QString displayCategory() const;

    virtual void runWizard(const QString &path, QWidget *parent);

    // Build a file name, adding the extension unless baseName already has one
    static QString buildFileName(const QString &path,
                                 const QString &baseName,
                                 const QString &extension);

    // Sets some standard options on a QWizard
    static void setupWizard(QWizard *);

    // Read "shortTitle" dynamic property of the pageId
    // and apply it as the title of corresponding progress item
    static void applyExtensionPageShortTitle(Utils::Wizard *wizard, int pageId);

protected:
    typedef QList WizardPageList;

    explicit BaseFileWizard(const BaseFileWizardParameters &parameters,
                            QObject *parent = 0);

    // Overwrite to create the wizard dialog on the parent, adding
    // the extension pages.
    virtual QWizard *createWizardDialog(QWidget *parent,
                                    const QString &defaultPath,
                                    const WizardPageList &extensionPages) const = 0;

    // Overwrite to query the parameters from the dialog and generate the files.
    virtual GeneratedFiles generateFiles(const QWizard *w,
                                         QString *errorMessage) const = 0;

    /* Physically write files. Re-implement (calling the base implementation)
     * to create files with CustomGeneratorAttribute set. */
    virtual bool writeFiles(const GeneratedFiles &files, QString *errorMessage);

    /* Overwrite to perform steps to be done after files are actually created.
     * The default implementation opens editors with the newly generated files. */
    virtual bool postGenerateFiles(const QWizard *w,
                                   const GeneratedFiles &l,
                                   QString *errorMessage);

    // Utility that returns the preferred suffix for a mime type
    static QString preferredSuffix(const QString &mimeType);

    // Utility that performs an overwrite check on a set of files. It checks if
    // the file exists, can be overwritten at all and prompts the user.
    enum OverwriteResult { OverwriteOk,  OverwriteError,  OverwriteCanceled };
    OverwriteResult promptOverwrite(const QStringList &files,
                                    QString *errorMessage) const;

    // Utility to open the editors for the files whose attribute is set accordingly.
    static bool postGenerateOpenEditors(const GeneratedFiles &l,
                                        QString *errorMessage = 0);

private:
    BaseFileWizardPrivate *m_d;
};

BaseFileWizard クラスは IWizard インターフェイスを実装し、いくつかの新しい関数を追加します。

  • createWizardDialog – runWizard() 関数によって表示されるウィザードを提供するには、この関数をサブクラスによってオーバーライドする必要があります。parent パラメーターは、関数によって返される QWizard の親クラスとして使用する必要があります。defaultPath パラメーターは、生成されたファイル拡張子Pages パラメータ。ウィザードによってデフォルトで表示されるすべてのページ
  • generateFiles – この関数は、ユーザーがウィザードの [完了] ボタンをクリックした後に自動的に呼び出されます。この関数の実装では、必要に応じて Core::GeneratedFile クラスのインスタンスを作成する必要があります。
  • postGenerateFiles – この関数は、generateFiles() が返った後に呼び出されます。サブクラスは、この関数をオーバーライドすることで、必要なことを何でも実行できます。

以下では、BaseFileWizard を使用して独自のウィザードを実装します。

#ifndef MODELCLASSWIZARD_H
#define MODELCLASSWIZARD_H

#include <coreplugin/basefilewizard.h>
#include <QMap>

class ModelClassWizard : public Core::BaseFileWizard
{
    Q_OBJECT
public:
    ModelClassWizard(const Core::BaseFileWizardParameters &parameters,
                     QObject *parent = 0);
    ~ModelClassWizard();
    QWizard *createWizardDialog(QWidget *parent,
                                const QString &defaultPath,
                                const WizardPageList &extensionPages) const;
    Core::GeneratedFiles generateFiles(const QWizard *w,
                                       QString *errorMessage) const;
private:
    QString readFile(const QString& fileName,
                     const QMap& replacementMap) const;
};

#endif // MODELCLASSWIZARD_H

私たちのコンストラクターとデストラクターは単純です。

ModelClassWizard::ModelClassWizard(const Core::BaseFileWizardParameters &parameters,
                                   QObject *parent)
    : Core::BaseFileWizard(parameters, parent)
{
}

ModelClassWizard::~ModelClassWizard()
{
}

関数 createWizardDialog() は非常に単純な QWizard を作成します。最初のページとして先ほど作成した ModelNamePage を使用し、他のデフォルト ページを追加します。

QWizard* ModelClassWizard::createWizardDialog(QWidget *parent,
                                              const QString &defaultPath,
                                              const WizardPageList &extensionPages) const
{
    // Create a wizard
    QWizard* wizard = new QWizard(parent);
    wizard->setWindowTitle("Model Class Wizard");
    // Make our page as first page
    ModelNamePage* page = new ModelNamePage(wizard);
    int pageId = wizard->addPage(page);
    wizard->setProperty("_PageId_", pageId);
    page->setPath(defaultPath);
    // Now add the remaining pages
    foreach (QWizardPage *p, extensionPages) {
        wizard->addPage(p);
    }
    return wizard;
}

関数 readFile() はファイルを読み取り、その内容を文字列として返します。文字列を返す前に、関数は 2 番目のパラメータとして提供された置換文字テーブルを使用して文字列を変更する必要があります。

QString ModelClassWizard::readFile(const QString& fileName,
                                   const QMap& replacementMap) const
{
    QFile file(fileName);
    file.open(QFile::ReadOnly);
    QString retStr = file.readAll();
    QMap::const_iterator it = replacementMap.begin();
    QMap::const_iterator end = replacementMap.end();
    while(it != end) {
        retStr.replace(it.key(), it.value());
        ++it;
    }
    return retStr;
}

この関数は次のような関数を実装します。 ファイルsample.txtがあるとします。

#ifndef {
   
   {UPPER_CLASS_NAME}}_H
#define {
   
   {UPPER_CLASS_NAME}}_H

#include <{
   
   {BASE_CLASS_NAME}}>

struct {
   
   {CLASS_NAME}}Data;

class {
   
   {CLASS_NAME}} : public {
   
   {BASE_CLASS_NAME}}
{
    Q_OBJECT

public:
    {
   
   {CLASS_NAME}}(QObject* parent=0);
    ~{
   
   {CLASS_NAME}}();

    int rowCount(const QModelIndex& parent) const;
    QVariant data(const QModelIndex& index, int role) const;

private:
    {
   
   {CLASS_NAME}}Data* d;
};

#endif // {
   
   {UPPER_CLASS_NAME}}_H

{ {xyz}} はプレースホルダーとして機能します。次のコード スニペットがあるとします。

QMap replacementMap;
replacementMap["{
   
   {UPPER_CLASS_NAME}}"] = "LIST_MODEL";
replacementMap["{
   
   {BASE_CLASS_NAME}}"] = "QAbstractListModel";
replacementMap["{
   
   {CLASS_NAME}}"] = "ListModel";

QString contents = readFile("Sample.txt", replacementTable);

実行後のコンテンツ文字列の内容は次のようになります。

#ifndef LIST_MODEL_H
#define LIST_MODEL_H

#include <QAbstractListModel>

struct ListModelData;

class ListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    ListModel(QObject* parent=0);
    ~ListModel();

    int rowCount(const QModelIndex& parent) const;
    QVariant data(const QModelIndex& index, int role) const;

private:
    ListModelData* d;
};

#endif // LIST_MODEL_H

すごいですね?実際の戦略は同様です。このテンプレート ファイルを提供し、プレースホルダーをユーザーが入力した情報に置き換えることによって、対応するファイルを生成します。

次に、関数generateFiles()の実装を見てみましょう。この関数は 2 つの Core::GeneratedFile オブジェクトを作成し、それらに正しいデータを割り当ててから返します。

Core::GeneratedFiles ModelClassWizard::generateFiles(const QWizard *w,
                                                     QString *errorMessage) const
{
    Q_UNUSED(errorMessage);
    Core::GeneratedFiles ret;
    int pageId = w->property("_PageId_").toInt();
    ModelNamePage* page = qobject_cast(w->page(pageId));
    if(!page) {
        return ret;
    }
    ModelClassParameters params = page->parameters();
    QMap replacementMap;
    replacementMap["{
   
   {UPPER_CLASS_NAME}}"] = params.className.toUpper();
    replacementMap["{
   
   {BASE_CLASS_NAME}}"] = params.baseClass;
    replacementMap["{
   
   {CLASS_NAME}}"] = params.className;
    replacementMap["{
   
   {CLASS_HEADER}}"] = QFileInfo(params.headerFile).fileName();
    Core::GeneratedFile headerFile(params.path + "/" + params.headerFile);
    headerFile.setEditorId(CppEditor::Constants::CPPEDITOR_ID);
    Core::GeneratedFile sourceFile(params.path + "/" + params.sourceFile);
    sourceFile.setEditorId(CppEditor::Constants::CPPEDITOR_ID);
    if(params.baseClass == "QAbstractItemModel") {
        headerFile.setContents(readFile(":/ModelClassWizard/ItemModelHeader",
                                        replacementMap));
        sourceFile.setContents(readFile(":/ModelClassWizard/ItemModelSource",
                                        replacementMap));
    } else if(params.baseClass == "QAbstractTableModel") {
        headerFile.setContents(readFile(":/ModelClassWizard/TableModelHeader",
                                        replacementMap));
        sourceFile.setContents(readFile(":/ModelClassWizard/TableModelSource",
                                        replacementMap));
    } else if(params.baseClass == "QAbstractListModel") {
        headerFile.setContents(readFile(":/ModelClassWizard/ListModelHeader",
                                        replacementMap));
        sourceFile.setContents(readFile(":/ModelClassWizard/ListModelSource",
                                        replacementMap));
    }
    ret << headerFile << sourceFile;
    return ret;
}

プラグインの実装

前と同様に、プラグイン実装クラスは、initialize() 関数の内容のみを置き換えます。

bool CustomProjectWizardPlugin::initialize(const QStringList& args, QString *errMsg)
{
    Q_UNUSED(args);
    Q_UNUSED(errMsg);

    Core::BaseFileWizardParameters params;
    params.setKind(Core::IWizard::ClassWizard);
    params.setIcon(qApp->windowIcon());
    params.setDescription("Generates an item-model class");
    params.setDisplayName("Item Model");
    params.setCategory("GalaxyWorld");
    params.setDisplayCategory(tr("GalaxyWorld"));

    addAutoReleasedObject(new ModelClassWizard(params, this));

    return true;
}

最後にプラグインをテストしましょう。

このウィザードはファイル タイプであるため、最初にプロジェクトを開く必要があります。

テスト用にプロジェクトを開く

次に、プロジェクト名を右クリックして新しいファイルを追加すると、プラグインによって提供されるファイル タイプが表示されます。

アイテムモデルの新規ファイルの追加

下の [選択...] ボタンをクリックして、私たちが設計したユーザー入力インターフェイスをポップアップ表示し、MyItemModel に入力すると、次のヘッダーと実装が自動的に変更されることがわかります。

入力項目 モデルデータ

「次へ」をクリックして新しいインターフェースを表示します。このページはデフォルトでシステムによって追加されます。私たちが作成した関数の最後のループを覚えていますか? デフォルトのパスに注意してください。Qt Creator がすでにそれを作成しています。

プロジェクトにファイルを追加

「完了」をクリックすると、ファイルが生成されたことがわかります。

結論

これは「Qt Creator プラグイン開発」の最終章です。これまでの内容から、Qt Creator プラグイン開発はそれほど不可能ではないことがわかりますが、唯一の問題はドキュメントにあり、Qt Creator プラグイン開発のドキュメントは常に不明瞭でした。最新バージョンでは大幅に改善されましたが、学習に関してはまだ十分ではありません。この記事が新しいアイデアを呼び込む一助になれば幸いですので、皆さんも Qt Creator の開発活動に参加して、この IDE を一緒に改善していきましょう。

皆様、これからも応援よろしくお願いします!

この記事の特典として、Qt 開発学習パッケージと技術ビデオ (C++ 言語の基礎、Qt プログラミングの概要、QT シグナルとスロットのメカニズム、QT インターフェイス開発イメージの描画、QT ネットワーク、QT データベース) を無料で受け取ることができます。プログラミング、QT プロジェクトの実践、QT 組み込み開発、Quick モジュールなど) ↓↓↓↓下記参照↓↓料金受け取り記事下部をクリック↓↓

おすすめ

転載: blog.csdn.net/hw5230/article/details/132716902