みなさんこんにちは! QtのQComboBoxのようなウィジェットのデータモデルを作成する方法と作成する方法を、2つの方法で共有したいと思います。 記事の最後に、データベースのコンボボックスを1行のコードで埋めるソリューションが表示されます。
メソッド番号1。 完全に手動のモデル作成
QtのすべてのデータモデルはQAbstractItemModelの子孫でなければなりません。 個人的に、私の練習では、コンボボックスは常にSQLデータベースからの列挙を表示しました。 これらは、性別、国、国籍、およびユーザーが1つの項目を選択する必要のあるその他のリストでした。 そのため、モデルを作成するとき、私は常に2つの並行タスクがありました。
- ユーザーが判読できるアイテム名を作成する方法は?
- 読み取り可能なアイテムを、データベースに書き込む必要があるキーに接続する方法は?
念のため、だれかが理解できない場合は、違いを説明します。 最初の段落は、人間が読める段落の名前です。 私の例では、国籍を選択するためのコンボボックス。 「ロシア語」、「ベルギー語」、「ノルウェー語」などの単語があります。 プログラムのユーザーが画面に表示するもの。 2番目は、プログラムがデータベースに書き込むものです。 条件付きで「サービス値」。 私の例では、「ロシア語」、「ベルギー語」、「ノルウェー語」のタイプの文字列がデータベースに書き込まれます。 これにより、不必要なトラブルなしに、ユーザーに表示されるアイテムの名前を変更できます。 たとえば、国籍の名前を減らすことにより、コンボボックスの幅を減らすタスクを提供しました。 「ロシア語」ではなく「ロシア語」と表示する必要があります。 この場合、ユーザーに表示されるテキストを落ち着いて変更し、タスクを閉じます。 データベースに直接書き込む場合、コンボボックスに表示されるもの。 「ロシア語」->「Rus」を変更すると、データベースの手順を書くように強制されます。 古い名前を新しい名前に変換するため。 エンドユーザーデータベースで選択された国籍が失われないこと。 要するに、各アイテムの2つの記述された名前(人間が読める形式、公式)。 サポートされているコードを作成することをお勧めします。
計画を実施するため。 まず、自分で決定する必要があるQAbstractItemModelメソッドを調べる必要があります。
- QModelIndex QAbstractItemModel :: index(int row、int column、const QModelIndex&parent = QModelIndex())const
- QModelIndex QAbstractItemModel :: parent(const QModelIndex&index)const
- int QAbstractItemModel :: columnCount(const QModelIndex&parent = QModelIndex())const
- int QAbstractItemModel :: rowCount(const QModelIndex&parent = QModelIndex())const
- QVariant QAbstractItemModel :: data(const QModelIndex&index、int role = Qt :: DisplayRole)const
つまり 「純粋な仮想」メソッドがここにリストされています。 columnCount()を実装する必要があるのは奇妙に思えます。 なぜなら 列が1つであることは明らかです。 次に、 インデックス()および親()は、単純な線形データ構造(リスト)の背景に対して、何らかの形で冗長に見えます。 これらはQTreeViewの階層ツリー型モデルを構築するためにさらに必要です。 したがって、私たち自身のために余分な作業を発明しないために、 QAbstractListModelからモデルクラスを継承することが決定されました 。これはこの場合にも適しています。 また、リストから最後の2つ(「純粋な仮想」)メソッドのみを実装する必要があります。
したがって、国籍のコンボボックスの選択。 モデルの次の実装が判明します。
// nationalitymodel.h // #pragma once #include <QAbstractListModel> class NationalityModel : public QAbstractListModel { Q_OBJECT typedef QPair<QVariant, QVariant> DataPair; QList< DataPair > m_content; public: explicit NationalityModel( QObject *parent = 0 ); virtual QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; virtual int rowCount( const QModelIndex & parent = QModelIndex() ) const; }; // nationalitymodel.cpp #include "nationalitymodel.h" NationalityModel::NationalityModel(QObject *parent) : QAbstractListModel(parent) { m_content << qMakePair( DataPair::first_type(), DataPair::second_type( "" ) ) << qMakePair( DataPair::first_type( "Russian" ), DataPair::second_type( "russian" ) ) << qMakePair( DataPair::first_type( "Belgian" ), DataPair::second_type( "belgian" ) ) << qMakePair( DataPair::first_type( "Norwegian" ), DataPair::second_type( "norwegian" ) ) << qMakePair( DataPair::first_type( "American" ), DataPair::second_type( "american" ) ) << qMakePair( DataPair::first_type( "German" ), DataPair::second_type( "german" ) ); } QVariant NationalityModel::data( const QModelIndex &index, int role ) const { const DataPair& data = m_content.at( index.row() ); QVariant value; switch ( role ) { case Qt::DisplayRole: { value = data.first; } break; case Qt::UserRole: { value = data.second; } break; default: break; } return value; } int NationalityModel::rowCount(const QModelIndex &/*parent*/) const { return m_content.count(); } // addressbookmainwindow.cpp. , ( AddressBookMainWindow::AddressBookMainWindow() ) ui->nationalityCombo->setModel( new NationalityModel( this ) );
コンボボックスアイテムのすべての値は、単にQList <DataPair> m_contentに書き込まれます。 。 そして、コンボボックスがNationalityModel :: data()関数に呼び出されたときに発行されます。 初心者が理解することが重要です。 自分のコードでこの関数を明示的に呼び出すのはプログラマーではありません。 コンボボックスは、必要なときにこの関数を参照します! あなたのタスクは、関数がリクエストに応じてこれらの関連データを返すことです。
NationalityModel :: data()は、2つのパラメーターで呼び出されます。 QAbstractItemModel :: data()単純型の必要に応じて:
- const QModelIndex&index 。 行番号、列、および親QModelIndexへのリンクを含むオブジェクト。 つまり QComboBoxは、データが要求されたアイテムの場所(位置)を報告します。 この場合、関連するのは行番号のみです。 残りのパラメーターは、 QTreeViewやQTableViewなどの他のモデルとの互換性のためにのみ内部およびインデックスにあります 。 したがって、この関数は、この行に対してのみ「読み取り可能」値と「サービス」値のペア(DataPair)を要求します。 可能な値のリスト(m_content)に保存されます。
- intロール 。 このパラメーターでは、 QComboBoxは必要なデータの種類(役割)を報告します。 この場合、「読み取り可能な」値はQt :: DisplayRoleであり、「サービス」の値はQt :: UserRoleです。
NationalityModel :: data()の 1回の呼び出しで、リスト内の1つの特定の行に対して1つのロールのデータが返されます。
Qt :: DisplayRole、Qt :: UserRoleが定義されている列挙型ItemDataRoleを使用すると 、 他にそのようなモデルを実装できる理由が明らかになります。 たとえば、いくつかのアイテムのフォントを変更します(Qt :: FontRole) 。 特別な方法で、メニュー項目のテキストを揃えます。 または、ツールチップテキストを設定します。 上記の列挙型を参照してください。 おそらく、あなたは長い間探していたものをそこに見つけるでしょう。
ソースコード例
職場でこのコードを学ぶことに興味があるかもしれません。 これらの目的のために、 小さなアドレス帳の実装が作成されました。
- プロジェクト「git clone https://github.com/stanislav888/AddressBook.git」をダウンロードします
- 現在のcd AddressBookディレクトリを変更する
- gitサブモジュールの初期化サブモジュールを初期化する
- サブモジュールコードをgitサブモジュール更新プロジェクトにアップロードします
- プロジェクトを開いてビルドする
- プログラムを実行する
- すべてが正常であれば、データベースファイルを選択/作成するためのウィンドウが表示されます。 どのようなプログラムを見ることができます。 テストデータを入力するためのボタン「テストデータを入力」があります
ビルドするには、少なくとも5.0のQtを備えたQtCreatorが必要です。 個人的に、Qt 5.5.0とgcc 5.3.1コンパイラを使用してプロジェクトを構築しました。 プロジェクトは収集され、Qt 4.8.1でも実行されます。 データベースをデバッグするには、Firefox SQLite Managerの拡張機能を使用できます。
メソッド番号2。 SQL DBの列挙からの高速モデル作成
もちろん、転送を整理する最も正しい方法。 これは、個別のテーブルの形式でデータベースに保存することです。 そして、フォームのデザイナーのコンボボックスにロードします。 そして、ある種の普遍的なソリューションを持つことが理想的です。 リストごとに個別のモデルクラスを記述する代わりに。
実装にはQSqlQueryModelが必要です 。 これは同様のモデルです。 彼女はQAbstractItemModelの子孫でもありますが、 QTableViewテーブルにQSqlQuery SQLクエリの結果を表示するために使用されます。 この場合、タスクはこのクラスを適応させることです。 彼が最初の例のようにデータを提供すること。
驚くでしょうが、コードは小さいことがわかりました。
// addressdialog.h /// #pragma once #include <QSqlQueryModel> class BaseComboModel : public QSqlQueryModel { Q_OBJECT QVariant dataFromParent(QModelIndex index, int column) const; public: explicit BaseComboModel( const QString &columns, const QString &queryTail, QObject *parent = 0 ); virtual QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex &parent) const; }; // basecombomodel.cpp #include "basecombomodel.h" #include <QSqlQuery> namespace { enum Columns // Depends with 'query.prepare( QString( "SELECT ... ' { Id, Data, }; } BaseComboModel::BaseComboModel( const QString& visualColumn, const QString& queryTail, QObject *parent ) : QSqlQueryModel( parent ) { QSqlQuery query; query.prepare( QString( "SELECT %1.id, %2 FROM %3" ).arg( queryTail.split( ' ' ).first() ).arg( visualColumn ).arg( queryTail ) ); // Ie query.prepare( "SELECT country.id, countryname || ' - ' || countrycode FROM country" ); query.exec(); QSqlQueryModel::setQuery( query ); } QVariant BaseComboModel::dataFromParent( QModelIndex index, int column ) const { return QSqlQueryModel::data( QSqlQueryModel::index( index.row() - 1 // "- 1" because make first row empty , column ) ); } int BaseComboModel::rowCount(const QModelIndex &parent) const { return QSqlQueryModel::rowCount( parent ) + 1; // Add info about first empty row } QVariant BaseComboModel::data(const QModelIndex & item, int role /* = Qt::DisplayRole */) const { QVariant result; if( item.row() == 0 ) // Make first row empty { switch( role ) { case Qt::UserRole: result = 0; break; case Qt::DisplayRole: result = "(please select)"; break; default: break; } } else { switch( role ) { case Qt::UserRole: result = dataFromParent( item, Id ); break; case Qt::DisplayRole: result = dataFromParent( item, Data ); break; default: break; } } return result; } // (addressdialog.ui) ui->countryCombo->setModel( new BaseComboModel( "countryname || ' - ' || countrycode", "country", this ) );
この実装では、 QSqlQueryModelがすべての作業を行います。 QSqlQueryModel :: data()のロジックをわずかにオーバーライドする必要があります。 まず、SQLクエリ「SELECT country.id、countryname || '-' || 国コードFROM国 。 」
もちろん、プロジェクトコードでは、これはもう少し複雑です。 ただし、そこでデバッグすると、まさにそのような行が形成されます。 クエリには2つの列が表示されます。 主キー(「id」)。 そして、スクリーンショットに表示される人間が読み取れる値。 SQLクエリのすべての結果はQSqlQueryModelのQt :: DisplayRoleにあるためです。 QSqlQueryModelを変更せずに、コンボボックスのモデルとして、単に「id」のリストを表示します。 また、人間が読み取れる値は表示されません。 なぜなら コンボボックスは、モデル(クエリ)の2番目の列を使用しません。 BaseComboModel :: data()の宣言と実装をコメントアウトすると、これが表示されます。
スクリーンショットのように国のリストを表示するには、 BaseComboModel :: data() :
- 最初に要求された列(「id」)のデータをQtとして返します::最初の列のUserRole
- 2番目の列のデータ(「国名|| '-' || countrycode」)をQtとして返します::最初の列のDisplayRole
- 文字列「(please select)」を最初に追加します。 QSqlQueryModelからデータをリクエストする際のオフセット番号のため。 つまり SQLクエリの結果に、モデル自体が別の行を追加します
これにより、 BaseComboModelを使用してQComboBoxのモデルをすばやく簡単に作成できます。 たとえば、1年の月(「月」)のSQLテーブルがあるとします。 「id」と「monthname」の2つの列はどこにありますか。 次のように、月選択コンボボックスに入力できます。
ui-> monthsCombo-> setModel(新しいBaseComboModel( "monthname"、 "months"、this));
選択した月のid値を取得しますui-> monthsCombo-> itemData(ui-> monthsCombo-> currentIndex()、Qt :: UserRole); 。 ユーザーに見える値を取得ui-> monthsCombo-> currentText(); 。 このコードは、他のすべての場合よりもはるかにコンパクトです。 この状況のほとんどの開発者は、データベースに個別のクエリ(QSqlQuery)を作成します。 次に、ループで、 QComboBox :: addItem()を使用して、受信したエントリをコンボボックスに追加します 。 これはもちろん機能しますが、最も美しいソリューションではありません。
練習する
ここですべてがどのように機能し、どのように機能するかを誰もが理解しているとは思わない これには、モデルの実装に非常に具体的な経験が必要だからです。 この場合、記事の時間は無駄になりません。 与えられたコードを実際に使用してみましょう。 その後、何が最終的にそれを理解します。
これを行うための2つのオプション:
- 私のアプリケーションに基づいた実験-上記のアドレス帳。 BaseComboModelのヘッダーと実装は、プロジェクトに既に存在します。 以下の例はそれに基づいています。
- SQLデータベースで動作する他のアプリケーションを使用します。 SQLiteである必要はありません。 任意のベースが行います! 上記のコードを任意のフォームの実装ファイルに貼り付けるだけです。
もちろん、 BaseComboModelのファイル、ヘッダー、実装を個別に作成するのが正しいでしょう。 今のところ、これを行うのが面倒だと思います。 もちろん、コンパイルエラーに少し対処する必要があります。 しかし、彼らは簡単になります。 コンボボックスのデータを取得するテーブル。 「id」列が存在する必要があります
BaseComboModelコンストラクターパラメーター(const QString&columns、const QString&queryTail、QObject * parent = 0) :
- const QString&columns 。 ユーザーが判読できるアイテム名の形成。 上記の例では、 「国名|| '-' || countrycode」は、ハイフンを介した2つの列の連結が適用されます。 連結演算子「||」 SQLite固有。 複数の列をコンマで区切って指定できます。 ただし、最初のもののみが表示されます。
- const QString&queryTail 。 リクエストの末尾。 "FROM"の後のSQLクエリの内容。 明らかに、この行では、データを取得するテーブルの名前を最初にする必要があります。 ただし、 WHERE句などを追加できます。
次に、実験するフォームにQComboBoxを追加する必要があります。 私の場合、それはaddressbookmainwindow.uiになります。 新しいウィジェット名ui-> comboBox
次に、このコンボボックスにさまざまな方法で入力します
ui-> comboBox-> setModel(新しいBaseComboModel( "countryname"、 "country"、this));
「country.id、countryname FROM countryを選択してください 。 」 国のリストだけ | |
ui-> comboBox-> setModel(新しいBaseComboModel( "countryname"、 "country WHERE countrycode IN( 'US'、 'RU'、 'CN')"、this));
"country.idを選択、国名FROM国WHERE国コードIN(" US "、" RU "、" CN ") 。 " コードによって選択されたいくつかの国。 | |
ui-> comboBox-> setModel(新しいBaseComboModel( "lastname"、 "persons"、this));
「persons.idを選択して、姓からFROM名」 。 データベースに記録されている姓のリスト。 何であれ、「テストデータを記入」ボタンをクリックします | |
ui-> comboBox-> setModel(新しいBaseComboModel( "lastname || '-' || email"、 "左のJOINアドレスをON a.id = person.addressid"にすると);
「persons.id、姓を選択|| '-' || 人からメールを送るa.id = persons.addressidとしてのアドレスとして参加します " 。 メールアドレス付きの姓のリスト。 「||」を忘れないでください SQLiteのみの文字列連結演算子。 他のベースについては、連結をやり直す必要があります | |
ui-> comboBox-> setModel(新しいBaseComboModel( "lastname || '-' || countryname"、 "persons INNER JOINアドレスAS a ON a.id = people.addressid INNER JOIN country AS c ON a.countryid = c。 id "、this));;
「persons.id、姓を選択|| '-' || a.id = people.addressidとしての内部結合アドレスからの国名a.countryid = c.idにおけるcとしての内部結合国。 関連する国の姓のリスト |
もちろん、 「JOIN」と「WHERE」を使用したこれらのトリックはすべて面白そうです。 しかし、ほとんどの場合、それらは必要ありません。 したがって、コンストラクターで2つのパラメーターを使用することが決定されました。 そこにSQLクエリ全体を送信する代わりに。 すべてのリストを1つのテーブルに保存する場合。 そして、これらの転送を追加のキーで分離します。 このキーの値を使用して、3番目のパラメーターを作成することをお勧めします。 毎回WHEREを使用する代わりに。
選択したレコードの「ID」を取得する方法を繰り返します
ui-> comboBox-> itemData(ui-> comboBox-> currentIndex()、Qt :: UserRole);
おわりに
うまくいけば、コードの複雑さにもかかわらず。 あなたは自分に役立つ何かを学びました。 例として、ここでAddressBookアプリケーションについて詳しく知りたい場合。 記事「SQLデータベースを使用したQtフォームからのデータ交換の自動化」を参照してください。