一、メタオブジェクトシステム
1、Qtのメタオブジェクトシステムが提供する機能には、**オブジェクト間通信のシグナルとスロットメカニズム**、**実行時型情報と動的プロパティシステム**などがあります。
2、メタオブジェクトシステムはC++の拡張であり、主にシグナルとスロットメカニズムを実現するために導入されました。これはQtの核心的な特徴です。
3、メタオブジェクトシステムの機能を使用するためには、以下の3つの条件を満たす必要があります:
- クラスはQObjectから継承されている必要がある
- クラス宣言にQ_OBJECTマクロを追加する必要がある。このマクロはメタオブジェクトコンパイラ(moc)によって処理される
- メタオブジェクトコンパイラ(moc)はQtプログラムを標準C++プログラムに変換し、その後標準C++コンパイラでコンパイルする
4、メタオブジェクトシステムの動作原理:
- メタオブジェクトシステムはC++の拡張であるため、従来のコンパイラではQtプログラムを直接コンパイルすることはできません。そのため、コンパイル前に拡張構文を取り除く必要があります。その役割をmocが果たします。その後、標準C++コンパイラでコンパイルされます。
- mocの全称はMeta-Object Compiler(メタオブジェクトコンパイラ)であり、これはC++ソースファイルを読み取り解析するツールです。Q_OBJECTマクロを含むクラス宣言を見つけた場合、Q_OBJECTマクロの実装コードを含む別のC++ソースファイル(通常moc_*.cppという名前)を生成します。この新しいファイルは、元のソースファイルに#includeで含めるか、クラスの実装にコンパイル時に連結されます。新しいファイルは古いファイルを置き換えず、両方を一緒にコンパイルします。
5、他の概念:
- メタオブジェクトコード:mocツールによって生成されたソースファイルのコードで、Q_OBJECTマクロの実装コードを含みます
- mocツールのパス:F:\app\Qt5.8.0MinGw\5.8\mingw53_32\bin
QObject* btn = new QPushButton;
qDebug()<
メタオブジェクトシステムとリフレクションメカニズム
リフレクションモード(リフレクションメカニズム):実行時に任意のクラスオブジェクトのすべての型情報、プロパティ、メンバー関数などの情報を取得できるメカニズムです。
- メタオブジェクトシステムの機能の一つとして、QObject派生クラスオブジェクトに実行時の型情報およびデータメンバーの現在値を提供します。つまり、実行段階で、QObject派生クラスオブジェクトの所属クラス名、親クラス名、メンバー関数、列挙型、データメンバーなどの情報を取得できます。これはまさにリフレクションメカニズムです。
- QtのメタオブジェクトシステムはQObjectから継承しなければならないため、リフレクションメカニズムの主な役割を見ると、Qtのメタオブジェクトシステムは主にQObjectクラスオブジェクトおよびその派生クラスオブジェクトの情報を提供します。つまり、QObjectから派生しないクラスオブジェクトでは、これらの情報を取得することができません。
二、プロパティシステム
1、プロパティはデータメンバーと似ていますが、Qtメタオブジェクトシステムの機能を使用できます。主な違いはアクセス方法にあります。例えば、プロパティ値は通常getter関数(関数名がgetで始まる)とsetter関数(関数名がsetで始まる)を使ってアクセスします。この他にもQtにはプロパティ値をアクセスする他の方法があります。
2、Qtではプロパティとデータメンバーは異なる概念であり、関連付けることもしなくても構いません。例えば、名前がaのプロパティとデータメンバーaが同じ名前であっても、関連付けられていない限り、データメンバーaとプロパティaは全く関係ありません。通常、プロパティには関連するデータメンバーがあり、命名規則としてはm_プレフィックスを使用します。例えば、プロパティ名がaの場合、関連するデータメンバー名は通常m_aになります。
3、プロパティ値は以下の方法でアクセスできます:
- QObject::propertyとQObject::setProperty関数を使ってアクセス
- プロパティに関連するアクセサ関数がある場合は、アクセサ関数を使ってアクセス
- プロパティはメタオブジェクトシステムのQMetaObjectクラスを通じてアクセスできます
- プロパティが特定のデータメンバーに関連付けられている場合、通常のデータメンバーの値をアクセスすることでプロパティ値を間接的にアクセスできます
注意:Qtのクラスではプロパティのみがあり、データメンバーはありません。したがって、プロパティ値を変更するには上記の3つの方法のみを使用できます。
inventory_btn_freeze_r->setProperty("inventory","on"); //プロパティと値を設定
inventory_btn_freeze_r->setStyle(QApplication::style());//スタイルシートでスタイルを適用
QWidget[temp='freeze']#CabinetCabTopWidget{
background-color: #43bcd8;
}
三、シグナルとスロット
メタオブジェクトシステムがサポートするオブジェクト間通信メカニズムです。これにより、Qtはslots、signals、emitというキーワードを導入しました。これらはC++のキーワードではなく、Qt固有のものです。これらのキーワードはQtのmocによって標準C++文に変換されます。
シグナルとスロットは観察者パターンの一種であり、クラス間の結合度を最大限に緩和します。
クラス内のスロット関数のメンバー関数は通常public slotsの下に書かれます。Qt5以降はpublic slotsを書かなくても構いません。
1. 相互関係
- シグナルは観察者、スロットは観察者です
- 一つのシグナルで複数のスロットに接続できます(1対多)
- 複数のシグナルで一つのスロットに接続できます(多対1)
- シグナルとシグナルを接続できます(転送)
- 接続はdisconnect関数で削除できます
注:ui接続方式とconnect接続は同時に有効になります
2.disconnect関数
static bool disconnect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member);
- 0はワイルドカードとして使用でき、それぞれ「任意のシグナル」、「任意の受信オブジェクト」または「受信オブジェクト内の任意のスロット」を表します
- senderは常に0ではありません
- disconnect(&ma, 0, 0, 0); オブジェクトmaに関連するすべてのシグナルからすべてのスロットへの接続を切断します
- disconnect(&ma , SIGNAL(s()), 0, 0); オブジェクトmaのシグナルsに関連するすべてのスロットへの接続を切断します
- disconnect(&ma, 0, &mb, 0); maのすべてのシグナルとmbのすべてのスロットの関連を切断します
3.Qt::ConnectionType
型:シグナルとスロットの接続方法を指定するもので、シグナルがスロットに即座に送られるか、後でキューに積まれて送られるかを決定します。接続方法はQt::ConnectionType列挙型で記述され、以下の値と意味を持ちます。
- 各スレッドには独自のイベントキューがあります。スレッドはイベントキューを通じてシグナルを受け取ります
- スレッドのイベントループ:各スレッドには独自のイベントループを持つことができます。初期化スレッド(GUIスレッド)はQCoreApplication::exec()を使用してイベントループを開始します(独立したダイアログインターフェースプログラムではQDialog::exec()を使用することもできます)。他のスレッドはQThread::exec()を使用してイベントループを開始できます。QCoreApplicationと同様に、QThreadはexit()関数とquit()スロットを提供します。スレッドでイベントループを使用することで、イベントループが必要なGUI以外のクラス(例えばQTimer、QTcpSocket、QProcessなど)を使用できるようになります。また、任意のスレッドのシグナルを指定スレッドのスロットに接続することもできます
- Qt::AutoConnection:デフォルト値
- この値を使用すると、接続タイプはシグナル送信時に決定されます。受信者と送信者が同じスレッドにある場合はQt::DirectConnectionを使用し、そうでない場合はQt::QueuedConnectionを使用します
- Qt::DirectConnection(即時呼び出し):シグナルを送信するスレッドでスロット関数を直接呼び出します。これはスロット関数のリアルタイム呼び出しと同等です!
- Qt::QueuedConnection(非同期呼び出し):シグナルは対象スレッドのイベントキューに送られ、イベントループで処理待ちになります。現在のスレッドは次の行を実行します!
- Qt::BlockingQueuedConnection(同期呼び出し):シグナルは対象スレッドのイベントキューに送られ、対象スレッドで処理されます。現在のスレッドはスロット関数の戻り値を待ってから次の行を実行します!
- Qt::UniqueConnection(一意接続):AutoConnectionと同じ機能で、自動的に接続タイプを決定します。同じシグナルと同じスロット関数間には**1つの接続**しかありません(デフォルトでは同じシグナルは同じスロット関数に複数回接続でき、複数回接続は同じスロット関数の複数回呼び出しを意味します)
connect(ui->startBtn,&QPushButton::clicked,this,&MainWindow::on_startBtn_clicked,Qt::UniqueConnection);
connect(ui->pushButton_3,&QPushButton::clicked,this,&MainWindow::on_startBtn_clicked,Qt::ConnectionType(Qt::UniqueConnection|Qt::AutoConnection));
スロット関数内で、QObject::sender()を使用してシグナル送信者のポインタを取得できます。
QSpinbox* spinbox = qobject<QSpinBox*>(sender());
スレッドセーフ:複数のスレッドが共有リソースにアクセスする際、プログラムが正常に動作しエラーが出ないことです。
Qtのシグナルとスロット自体はスレッドセーフですが、受信者と送信者が異なるスレッドでQt::DirectConnectionを使用すると、スレッドセーフ問題が発生する可能性があります。
四、オブジェクトツリーとライフサイクル
1、オブジェクトツリーを使用する理由:GUIプログラムは通常親子関係を持っています。例えば、ダイアログにボタンやリストなどの部品が含まれている場合、それらの部品はクラスのオブジェクトです(クラスではなく)。明らかにこれらのオブジェクト間には親子関係があります。したがって、GUIプログラムは通常親オブジェクトが一連の子オブジェクトリストを維持することで、部品の管理が容易になります。例えば、Tabキーを押すと親オブジェクトが子オブジェクトリストに基づいて各子オブジェクトにフォーカスを順番に渡します。ダイアログを閉じるとき、親オブジェクトは子オブジェクトリストに基づいて各子オブジェクトを見つけ、それらを削除します。Qtではオブジェクト管理にツリー構造、つまりオブジェクトツリーを使用します。
2、子オブジェクトと親オブジェクト:このセクションの親/子オブジェクトはオブジェクトで構成されたツリー構造におけるものです。親ノードオブジェクトは親オブジェクトと呼ばれ、子ノードオブジェクトは子オブジェクトと呼ばれます。注意:子オブジェクトはクラス内のオブジェクトメンバを指すものではありません。
QObjectはparent()、children()、findChildren()などの関数を提供します。
一、コンポジットパターンとオブジェクトツリー
- コンポジットパターンとは、クラスのオブジェクトをツリー構造に組織化する手法です。このツリー構造はオブジェクトツリーと呼ばれます。Qtはオブジェクトツリーを使用してQObjectおよびその派生クラスのオブジェクトを管理します。注意:これはクラスのオブジェクトを指します。
- コンポジットパターンの主な目的は、ルートノードオブジェクトから子ノードの仮想関数を間接的に呼び出して、子ノードオブジェクトを操作することです。
- コンポジットパターンの基本思想は、親クラス型のポインタを使用して子クラスオブジェクトを指し、このポインタを配列(コンテナを使用するのが便利)に格納することです。そして、クラスオブジェクトを作成するたびに、そのオブジェクトへのポインタをこのコンテナに追加します。
コンポジットパターンはツリー構造を使用し、そのコア思想は複数のオブジェクトをツリー構造に組み合わせることで、「全体-部分」の階層関係を表現することです。
- ルートノードと枝ノードは本質的に同じデータ型であり、コンテナとして使用できます。一方、葉ノードと枝ノードは意味的に異なる型です。しかし、コンポジットパターンでは枝ノードと葉ノードを同じ型(統一インターフェースで定義)として扱い、同じ振る舞いを持たせます。
- コンポジットパターンを使うことで、ユーザーはツリー構造の個々のオブジェクトとコンポジットオブジェクトを統一された方法で処理でき、クライアントの操作を簡略化できます。
- コンポジットパターンは高い拡張性を持ち、コンポジットオブジェクトを変更する際に内部の階層関係を調整するだけでよく、クライアント側は変更不要です。
- クライアントはコンポジットの詳細を気にせず、ノードと葉を追加して複雑なツリー構造を作成できます。
- 処理するオブジェクトがツリー構造である場合はコンポジットパターンを検討してください。
- ノードと葉ノードの差異が大きい場合はコンポジットパターンは推奨されません。
A ma1; B mb1;
mb1.setParent(&ma1); //スタック上でma1をmb1の親オブジェクトとして指定。この場合、親オブジェクトは子オブジェクトより前に作成されています。
オブジェクトツリーの組織ルール:
- 各QObjectオブジェクトは1つの親QObjectオブジェクトしか持てませんが、任意数の子QObjectオブジェクトを持つことができます。例えば、A ma; B mb; C mc; ma.setParent(&mb); //オブジェクトmaをmbの子オブジェクトリストに追加します。ma.setParent(&mc); //この文はmaをmbの子オブジェクトリストから削除し、mcの子オブジェクトリストに追加します。
- QObjectオブジェクトは各子オブジェクトのアドレスポインタをQObjectListに格納します。QObjectListはQListの別名であり、QListはQtのリストコンテナです。
Qtでは、親オブジェクトが削除されると、自動的にすべての子オブジェクト(QWidgetオブジェクトやその他のオブジェクト)を削除します。親オブジェクトを削除する際、Qtはまず子オブジェクトリストを走査し、再帰的に各子オブジェクトを削除してから親オブジェクトを削除します。したがって、子オブジェクトが先に削除され、その後に親オブジェクトが削除されます。
ヒント:
コールバック関数:関数ポインタを使用して必要な関数を呼び出すことで、任意の名前の関数を呼び出すことができます(関数型がポインタと一致すれば可能です)。
プロセスは独立したアドレス空間を持ち、スレッドは同じアドレス空間を共有します。
複数のスレッドはスタック領域を共有し、ヒープ領域、コード領域、グローバル領域を共有します。
1. Qtイベントの理解
GUIプログラムはイベント駆動です。
イベント:QtはC++ベースのクロスプラットフォームインターフェースアプリケーションフレームワークであり、主にウィンドウを持つアプリケーションを開発するために使用されます。ウィンドウサイズの変更やマウスクリック、キーボード入力など、さまざまなイベントが発生し、それぞれに対応するイベントがトリガーされます。Qtのウィンドウはネストされており、複数のサブウィンドウが存在します。イベントは対応するウィンドウに送られます。
イベントディスパッチ(Qtのアプリケーションオブジェクトはイベントを対応するウィンドウに送信します)
各Qtプログラムに対応する唯一のQApplicationオブジェクトがあり、オブジェクトが作成されるとexec関数を呼び出して**メインイベントループ**を開始し、アプリケーションのイベントを検出します。
イベントが発生すると、アプリケーションオブジェクトはnotify()関数を使用してイベントを指定されたウィンドウに送信します。
- イベントフィルター(イベントをフィルター処理できる)
イベントディスパッチ中にウィンドウはevenFilter()関数を使用してイベントをフィルター処理できます。デフォルトではすべてのイベントをフィルター処理しません。
- イベントディスパッチ
イベントが指定されたウィンドウに送られた後、イベントはevent()関数で細分化され、イベントディスパッチャーが処理します。
- イベント処理
ウィンドウが細分化されたイベントを受け取ると、対応するイベント処理関数を実行します。
2. Qtシグナルとスロットの下位メカニズム
define signal = public
define slot = ""
- このクラスはQObjectから継承されている必要があります。
- クラス宣言にQ_OBJECTマクロを追加する必要があります。メタオブジェクトコンパイラ(moc)オブジェクト
クラスオブジェクトはmocコンパイラによって対応するmocファイルを生成し、objectマクロは静的メタオブジェクトstaticMetaObjectと取得方法を提供し、シグナルとスロットの一部のロジックを補完し、完全なクラスオブジェクトを構成し、現在のクラス情報と情報インデックステーブル、コールバック関数を含みます。
手順:シグナルとスロットの絶対インデックスを計算し、シグナルとスロットの接続をシグナルのプライベートメタオブジェクトのシグナルスロット格納コンテナQObjectConnectListVectorの指定位置に配置します。
これはVectorコンテナであり、基本単位はConnectionList型のデータです。ConnectionListの数はこのQObjectオブジェクトのシグナル数と同じです。各ConnectionListは1つのシグナルに対応し、そのシグナルに接続されたすべての接続を記録します。前述のConnectionListの定義には2つの重要なメンバーfirstとlastがあります。これらはConnection型のポインタであり、シグナルに接続された最初と最後の接続を指します。シグナルに接続されたすべての接続は単方向リンクリストとして整理されます。Connection構造体のnextConnectionListメンバーは、このリスト内の次の接続を指すために使用されます。
シグナルとスロットは疎結合であり、シグナル送信者は受信者情報を知る必要がありません。コールバック関数は密結合であり、直接的に目標オブジェクトの特定の関数を呼び出します。
シグナルとスロットメカニズムはコールバック関数よりも速度が遅いです。
コールバック関数は**同期実行**され、スロット関数は**非同期実行**される可能性があります。直接接続は同期実行です。
オブジェクトがシグナルを発信すると、メタオブジェクトシステムはシグナル名に基づいて該当するインデックスを見つけ、インデックスに基づいてシグナルに接続されたスロット関数を見つけます。
観察者パターンでは、Qtオブジェクトがシグナルを発信する際、どのオブジェクトのどのスロット関数がそれを受信するかを知る必要はありません。適切なタイミングで適切なシグナルを送信するだけでよく、受信者が受け取ったかどうか、あるいはどのオブジェクトのどのスロットがシグナルを受け取ったかを気にする必要はありません。
シグナルスロットメカニズムはコールバック関数と比較して速度が遅いです。走査を行い、シグナルを介してスロット関数を呼び出すのは、非仮想関数を直接呼び出すよりも10倍遅くなります。理由は以下の通り:-
- シグナルを受信するオブジェクトの位置を特定する必要がある
- すべての関連(シグナルが複数のスロットに関連する場合)を安全に走査する必要がある
- 渡されたパラメータのグループ化/アングループ化
- マルチスレッドの場合、シグナルはキューに積まれる可能性がある
シグナルスロットメカニズムがない時代、C++オブジェクト間の相互作用は一般的にコールバック関数を使用して実現されていました。あるオブジェクトを使用する際、別のオブジェクトの関数へのポインタを使用し、その関数はコールバック関数と呼ばれます。コールバック関数には欠点があり、複数のオブジェクトと通信するオブジェクトがある場合、複数のオブジェクトのコールバック関数を格納するコンテナが必要になります。このコンテナの保守はコード作成効率を下げ、拡張性を低下させます。
Qtのシグナルスロットは本質的にコールバック関数です。
3.deleteLaterとdeleteの違い
deleteはC++とQtの両方に共通するキーワード識別子であり、**即時破棄**であり、deleteは即座に対象オブジェクトを破棄します。
一方、Qtの**deleteLater**の仕組みは:QObject::deleteLater()は**オブジェクトを即座に破棄せず、メインメッセージループにイベントを送信します。次回のメインメッセージループでこのイベントを受け取るとオブジェクトが破棄されます。** この利点は、遅延削除の間にいくつかの操作を完了できることです。欠点はメモリ解放が遅れることです。
deleteLaterでthisをQDeferredDeleteEventメッセージのrecieverとして設定すると、次のイベントループでメッセージを配信する際、recieverのイベント処理関数eventが呼び出され、DeferredDelete型メッセージが処理され、処理動作はdelete thisとなります。
削除操作が完了した後、Qtは自動的にポインタをnullptrに設定し、削除されたオブジェクトへのアクセスを防ぎ、野暮ったいポインタを防ぎます。
3. オブジェクトツリー構造
QObjectオブジェクトを作成する際、親オブジェクトを指定できます。作成されたこのQObjectオブジェクトは自動的にその親オブジェクトのChildren()リストに追加されます。親オブジェクトが破棄されると、このリストのすべてのオブジェクトが破棄されます。
親オブジェクトが破棄されるとき、この子オブジェクトリストのすべてのオブジェクトが破棄され、子オブジェクトが破棄される際、自動的に親オブジェクトの子オブジェクトリストから削除されます。
Qtオブジェクトが破棄されるとき、親オブジェクトの子オブジェクトリストから自分自身を削除し、子オブジェクトリストのすべてのオブジェクトを破棄します。Qtオブジェクトは破棄時に親オブジェクトとの親子関係を解除し、すべての子オブジェクトを破棄します。
C++では、構築順序の逆順で破棄順序が規定されています。
C++では、継承関係がない場合、構築順序に従って、最初に作成されたものが最初に構築され、最初に構築されたものが最後に破棄されます。
- 親オブジェクトを最初に作成し、その後に子オブジェクトを作成し、子オブジェクト作成時に親オブジェクトを指定します
- できるだけヒープ上で子オブジェクトを作成します
5. シグナルを宣言しただけで定義していないのになぜ使えるのか?
Qtはメタオブジェクトシステムを使用してシグナルとスロットの接続を実装しており、コンパイル時に追加コードを生成してシグナルとスロットの接続をサポートします。
6. イベントループ
イベント発生 -> イベントキューイング -> イベントフィルター(installEventFilter) -> イベントディスパッチ(Qtはイベントディスパッチメカニズムを使用してイベントを適切なオブジェクトに渡します。例えば、QWidgetはマウスやキーボード関連のイベントを受信して処理します。) -> イベント処理(ターゲットオブジェクト(例えばQWidget)がイベントを処理します。)
Qtプログラムには常にQApplicationオブジェクトがあります。
イベントループはexec関数から開始され、プログラムはexecの中で無限ループし、イベントループはイベントを受け取り処理します。イベントが多い場合はキューに格納され、イベントループキューと呼ばれます。QEventLoop::quit()はイベントループを終了させます。
イベントループは通常**exec**()関数で開始されます。QApplication::exec()、QMessageBox::exec()はイベントループです。前者はメインイベントループと呼ばれます。イベントループは無限ループであり、exec()の後に続くコードは実行されません。exec()から抜け出すとイベントループが終了します。また、イベントループは「イベント」ループと呼ばれる理由は、イベントを受け取り処理できるからです。イベントが多すぎてすぐに処理できない場合、待処理イベントは「キュー」に格納され、イベントループキューと呼ばれます。イベントループが1つのイベントを処理した後、「イベントループキュー」から次のイベントを取り出して処理します。キューが空になると、真っ暗な永真ループに似ていますが、永真ループとは異なり、イベントループはCPUリソースを大量に消費しません。イベントループの本質は、キューを使用してスレッドタイムスライスを再分配することです。
イベントキューは待処理イベントを格納するデータ構造であり、通常はキューです。Qtではイベントキューは各種イベントを格納し、ユーザー入力イベント(マウスクリック、キーボード入力)、タイマーイベント、システムイベントなどが含まれます。
イベントキューの主な役割は、イベントを発生させたソース(ユーザー入力デバイス、タイマー、システムシグナルなど)からイベント受信者(ウィンドウコンポーネント、カスタムオブジェクト、スレッドなど)にイベントを渡し、適切なときにイベントをイベント受信者に送信して処理することです。イベントが発生すると、Qtはそれをイベントオブジェクトにカプセル化し、イベントキューに追加します。
Qtでは、**サブイベントループはメインスレッド以外のスレッドで作成・使用され、メインスレッドのイベントループがブロックされないようにします**。サブイベントループを使用することで、マルチスレッドプログラミングにおける非同期イベント処理とイベント待機を実現し、インターフェースの応答性を維持し、必要に応じてイベントループの実行を手動で制御できます。
7. Qtマルチスレッドの実装方法
- スレッドサブクラスを作成し、QThreadを継承し、run関数をオーバーライドします。メインスレッドでサブスレッドオブジェクトを作成し、サブスレッドを開始し、startメソッドを呼び出します
- ワーククラスをObjectクラスから派生させ、メインスレッドでサブスレッドを作成し、ワーククラスオブジェクトをメインスレッドにmoveToThreadします。サブスレッドstartメソッドを呼び出し、ワーククラス関数を呼び出します
8. Qtプラグインの原理
プラグインの役割:主プログラム機能を拡張し、動的にロードし、インターフェースプログラミングを使用し、モジュールとして理解できます。
静的ライブラリプラグインと動的ライブラリプラグインに分けられます。コンパイル時に静的ライブラリプラグインをロードし、実行時に動的ライブラリプラグインをロードします。
プラグインマネージャーは自分で実装する必要があります。
Q_INTERFACESはインターフェースIDを判断し、上位変換を行います。インターフェース型のポインタを返します。
インターフェースを上位変換する際、インターフェースIDを確認してプラグインの有効性を確保します。
親クラスポインタが子クラスオブジェクトを指し、子クラスオブジェクトに格納された親クラスIDを確認して、子クラスオブジェクトの有効性を判断します。
Q_INTERFACES(LooseStorageInterface)
Q_PLUGIN_METADATA(IID "LooseStorage_Management_plugin")
子クラスでインターフェースの有効性を確認します。
plugin_metadataはプラグインのデータとインスタンスを提供し、メタオブジェクト機能を取得します。
現在の抽象インターフェースを宣言し、抽象インターフェースのIDを設定し、取得機能を提供し、宣言されたIDと照合してプラグインの有効性を確認します。
親クラス抽象インターフェース
class LooseStorageInterface
{
public:
LooseStorageInterface();
virtual ~LooseStorageInterface(){}
virtual QWidget *GetLooseStoragePanel(QWidget *parent = nullptr) = 0;
virtual QWidget *SetLooseStoragePanel(QSqlDatabase m_db,QWidget *parent = nullptr) = 0;
virtual QString get_name() const =0;
signals:
public slots:
};
Q_DECLARE_INTERFACE(LooseStorageInterface,"LooseStorageInterface.plugins.pluginInterface")
Qtのメタオブジェクトシステムを通じて、プラグインは宣言時に提供されたデータを現在の静的メタオブジェクトに挿入し、静的メタオブジェクトの関連操作を通じてプラグインの管理を実現します。
動的ライブラリのロードとアンロードにはC++のネイティブloadlibraryとfreelibraryを使用します。
メインフレームワークは一貫した抽象インターフェースを提供し、Qtプラグインは抽象インターフェースを継承し、自身の業務要件を実装します。メインフレームワークは一貫したインターフェースオブジェクトを呼び出して具体的なプラグインの呼び出しを実現します。プラグインはメインフレームワークの業務ロジックと業務要件を分担し、メインフレームワークは業務の骨格とコアスケジューリングを実装します。Qtは基本的なスケジューリング機能のみを提供し、具体的なスケジューリングロジックは業務に応じて実装する必要があります。
異なるモジュール間の通信:
プラグインはまずメッセージをプラグインマネージャーに送信し、プラグインマネージャーのメッセージスケジューラーが他のプラグインにメッセージを送信します。
メインプログラムはプラグインマネージャーを作成し、すべてのプラグインをロードするメソッドを呼び出し、プラグインマネージャーがすべてのプラグインをロードし初期化します。プラグインは宣言時にデータとその他の情報を静的メタデータにロードします。
プラグインの作成方法
- インターフェースクラスを定義し、Q_DECLARE_INTERFACEを使用して現在を抽象インターフェースとインターフェースIIDを宣言します
- プラグインクラスを定義し、QObjectとプラグインが提供するインターフェースを継承し、Q_INTERFACESインターフェースIDを判断してインターフェースの有効性を確認し、Q_PLUGIN_METADATA(IID "Login_Interface_plugin")を使用してプラグインのデータとインスタンスを提供します
- インターフェースファイルをメインフレームワークディレクトリにコピーし、右クリックしてライブラリファイルを追加し、QPluginLoderを使用してすべてのプラグインをロードし初期化します
- QObject_cast()を使用してプラグインが指定されたインターフェースを実装しているかどうかをテストします
// qmakeの条件判断文で、現在がdebugモードかどうかを確認します
// debugモードでもreleaseモードでも、最終的なターゲットディレクトリはbin/pluginsディレクトリになります。
// これらのターゲットファイルには実行ファイル、静的ライブラリファイル、ダイナミックリンクライブラリファイルが含まれます。
CONFIG(debug,debug|release) {
DESTDIR = $$PWD/../bin/plugins
}else {
DESTDIR = $$PWD/../bin/plugins
}
// プロジェクトはQtプラグインメカニズムで使用可能なプラグインライブラリを生成します
CONFIG += plugin
9. QtのMVCアーキテクチャの理解
1. ユーザーがViewとインタラクションし、セルの編集、ドラッグ&ドロップ操作などを実行してユーザーアクションイベントを発生させます。
2. ViewはユーザーのアクションイベントをDelegateに渡します。Delegateはデータを修正、検証、または特定の操作を行う場合があります。
3. Delegateは更新されたデータをModelに渡して保存・更新します。
4. ModelはViewにデータの変更を通知し、Viewは更新されたデータを再取得して表示を更新します。
Qtは本質的にMV構造を実装しており、Controlはなく、中間にDelegateプロキシを加え、データの保存方法とビューの表示方法を解耦しています。
1. QTableWidgetはQTableViewのサブクラスであり、主な違いは**QTableViewはカスタムデータモデルを使用してコンテンツを表示できます**(つまり、**setModel**を使用してデータソースをバインドする必要があります)。
一方、**QTableWidgetは標準データモデルのみを使用でき**、そのセルデータは**QTableWidgetItem**オブジェクトで実装されます(つまり、データソースは必要なく、セルごとに情報を手動で入力できます)。
MyComboDelegate *ComDelegate = new MyComboDelegate;
ui->tableWidget->setItemDelegateForColumn(4,ComDelegate); //テーブルの第4列をカスタムプロキシに設定
- モデル(Model)は一時オブジェクトを格納し、**メモリ内で一時的に格納します**。データはクラス、ファイル、データベースに格納されます
- ビュー(View)はモデルのユーザーインターフェースで、データを表示します。ビューはモデルが提供するインターフェースを通じてデータを取得できます。モデルは通常rowCount()、columnCount()、data()などのメソッドを提供し、ビューが呼び出して対応するデータを取得します。
- コントローラ(Controller)はユーザーインターフェースがユーザー入力に対してどのように反応するかを定義します。
- デリゲート(Delegate)**指定されたインデックスのデータにカスタム表示方法と編集方法を提供します**。デリゲートは特定のメソッドをオーバーライドまたは実装することでこれらの機能を実現できます。
なぜデリゲートを使用するのか?
デリゲートの役割は:**コンポーネントが編集状態に入り、特定の項目を編集する際、データ編集用の一時的なコンパイラを提供します。デフォルトではlineeditです**。
- もしQTableViewを使用してテーブルコントロールでデータを表示するが、突然インタラクティブなコンポーネント(例:ボタン、プログレスバーなど)を追加したい場合
QTableWidgetのsetCellWidgetメソッドを使用してコンポーネントを追加できますが、これはモデル-ビュー枠組みをサポートせず、カスタムデータモデルを使用できません。
- しかし、このテーブルにコンポーネントを挿入する方法では、そのコンポーネントは常に表示されるため、効果が良くありません。
デリゲートの実装方法
一般的にデリゲートはQStyledItemDelegateとQItemDelegateという2つのクラスを継承します。
- デリゲートはモデルとビューへのアクセスインターフェースを提供します。複雑な表示機能(例:テーブルにコンボボックス、ボタン、プログレスバーを表示)を実現するには、モデルとビューだけでは不十分です。デリゲートを使用して実現する必要があります。デリゲートはエディタ(エディタはコンボボックス、ボタン、プログレスバーなど)を作成し、デリゲートが提供する仮想関数を実装してこれらの機能を実現します。
CreateEditor():モデルデータを編集するコンポーネントを作成します(例:QSpinBoxコンポーネント)
SetEditorData():モデルからエディタにデータを設定します
SetModelData():エディタからモデルにデータを保存します
UpdateEditorGeometry():エディタの位置とサイズを更新します
paint()関数:デリゲートアイテムの外観を描画します。
モデルの役割
モデルまたはデータモデル(Model)は実際のデータと通信し、ビューにデータインターフェースを提供します。
- rowCount(const QModelIndex &parent) const:
この関数は指定された親ノードの行数(つまり、サブアイテム数)を返します。 - columnCount(const QModelIndex &parent) const:
この関数は各行の列数(つまり、各データアイテムのフィールド数)を返します。 - data(const QModelIndex &index, int role) const:
この関数は指定されたインデックス位置のデータを返します。 - flags(const QModelIndex &index) const:
この関数は指定されたインデックス位置のプロジェクトフラグを返します。これは編集可能か、選択可能かなどを示します。 - headerData(int section, Qt::Orientation orientation, int role) const:
この関数はヘッダー(Header)のデータを返します。行ヘッダーと列ヘッダーが含まれます。
すべてのアイテムモデルは**QAbstractItemModel**クラスに基づいています。このクラスはビューとデリゲートがデータにアクセスするために使用するインターフェースを定義します。
データ自体は必ずしもモデルに格納されているわけではありません**。別のクラス、ファイル、データベース、または他のアプリケーションコンポーネントによって提供されるデータ構造またはストレージに格納されることがあります。これが**データの分離**です。モデルはデータを表示するためのレイヤーです。
モデルの目的は、インターフェースとの関係を結ぶ中間層のようなものです。1つのモデルは複数のビューで使用できます。
カスタムモデルの例、パフォーマンスとメモリの最適化
一般的にニーズを満たしますが、場合によってはカスタム機能が必要であったり、大量のデータでパフォーマンスとオーバーヘッドに注意が必要な場合、既存のモデルが無駄な機能が多く効率が悪いと感じられる場合、カスタムモデルが適しています。
Qtには以下の一般的なモデルクラスがあります:
- QAbstractItemModel:すべてのモデルクラスの基底クラスであり、モデルデータへのアクセスの標準インターフェースを定義します。
- QStandardItemModel:QAbstractItemModelのサブクラスであり、項目ベースの編集可能なモデルを提供します。各項目にはテキスト、アイコン、データ項目を含めることができます。
- QStringListModel:QAbstractListModelのサブクラスであり、文字列リストをモデルデータとして保存するために使用されます。
- QSqlTableModel:QAbstractTableModelのサブクラスであり、データベースの単一テーブルとのインタラクションに使用されます。
- QFileSystemModel:QAbstractItemModelのサブクラスであり、ローカルファイルシステムのディレクトリとファイルのモデルを表示するために使用されます。
- QSortFilterProxyModel:QAbstractProxyModelのサブクラスであり、ソースモデルの並べ替えとフィルタリング機能を提供します。
- QAbstractListModel:QAbstractItemModelのサブクラスであり、シンプルな拡張可能なリストデータを保存するために使用されます。
- QAbstractTableModel:QAbstractItemModelのサブクラスであり、テーブルデータを保存するために使用されます。
- QStandardItem:QAbstractItemModelの補助クラスであり、QStandardItemModelでデータ項目を格納するために使用されます。
QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModelはデータベースデータを構造化・カプセル化し、データへのアクセスインターフェースを提供します。
- QSqlTableModelはQSqlQueryModelクラスを継承していますが、柔軟性はQSqlQueryModelよりもはるかに高いです。
- QSqlTableModelはデータを操作でき、QSqlTableModelオブジェクトのデータを操作すると、対応するデータテーブルが変更されます。
QTableViewはカスタムデータモデルを使用でき、setModelメソッドを使用して呼び出します。
カスタムsqlmodelはQSqlQueryModelを継承し、データを設定し、モデルインデックスQModelIndex getSelectIndex() constを返します。
QString querySQL=QString(R"(select NULL,"MC"||' '||"GGBM" from "REPAIR"."WXBZ_JCXX_GJXX" where ("SCBS" <> 1 or "SCBS" is null or "SCBS"='') )");
qDebug() << querySQL;
materialNameTable = new MaterialNameTable(db,querySQL,2,ColumnFunctions,10000,28,colWidthList,this);
materialNameTable->setGeometry(44,174,712,136);
materialNameTable->show();
int delegateColIndex = 0;
materialNameTable->SetColumnDelegate(delegateColIndex,0,1);
if(delegateType == 1){//チェックボックスプロキシ
MaterialCheckBoxDelegate *columnDelegate = new MaterialCheckBoxDelegate(queryModel,columnIndex,dValueIndex,startX,this);
connect(columnDelegate,SIGNAL(ClickEvent(int,QString,QSqlRecord)),this,SLOT(Slot_ClickEvent(int,QString,QSqlRecord)));
connect(this,SIGNAL(mouseMoveSig(QModelIndex&,QPoint)),columnDelegate,SLOT(MouseMoveEvent(QModelIndex&,QPoint)));
connect(this,SIGNAL(mouseReleaseSig(QModelIndex&,QPoint)),columnDelegate,SLOT(MouseRelaseEvent(QModelIndex&,QPoint)));
connect(this,SIGNAL(mouseClickSig(QModelIndex&,QPoint)),columnDelegate,SLOT(MouseClickEvent(QModelIndex&,QPoint)));
queryTable->setItemDelegateForColumn(columnIndex,columnDelegate);
//queryTable->setColumnHidden(columnIndex,true);
deledagateList.insert(columnIndex,columnDelegate);
}
MaterialNameSqlQueryModel *queryModel;
QSqlQueryModelのvoid setQuery() // QSqlQueryオブジェクトを設定し、データを取得します
QSqlRecord record = queryModel->record(rowIndex); recordメソッドを使用してモデルの特定行のデータを取得します
変更後のデータをSQLでデータベースに更新し、データモデルがQSqlQueryModelのsetQueryメソッドを再呼び出しして、テーブルビューに自動的に更新します。
10. Qtスレッドセーフ
スレッドセーフとは、マルチスレッド環境において、複数のスレッドが共有リソースに同時にアクセスする際に、正しく動作したりデータが破損したりしないことを意味します。
11. イベントとシグナルの違い
- 発生方法:
- イベント:イベントの発生は通常システムまたはフレームワークによって自動的に生成されます。例えば、ボタンをクリックしたりウィンドウを操作したりすると、マウスクリックイベントが生成され、システムが自動的にそのイベントをターゲットオブジェクトに配信します。
- シグナル:シグナルの発生は開発者がコード内で明示的に呼び出します。例えば、ボタンがクリックされたときにシグナルを発信し、他のコンポーネントに通知できます。
- 接続と処理:
- イベント:イベント処理はイベント処理関数のオーバーライドによって実現されます。イベントがオブジェクトに到達すると、そのオブジェクトの該当イベント処理関数(例えばmousePressEvent())が自動的に呼び出されます。イベントディスパッチはQtの内部メカニズムであり、イベントループによってイベントが自動的に管理されます。
- シグナル:シグナルはconnect()関数を使用してスロットと接続され、シグナルが発信されたときに関連するスロット関数が呼び出されるように保証されます。シグナルとスロットの間はQtのメタオブジェクトシステムによって実現され、開発者は明示的にシグナルとスロットを接続します。emitキーワードを使用してシグナルを発信します。