ネットワーク上のQtメタシステム。 パート1-プロパティ



私は、うらやましい規則性でQtを使用してクライアントサーバーアプリケーションを作成するタスクを持っています。 そして、私は考えました-このプロセスを単純化してみませんか? 実際、おなじみの信号とスロットを使用できる場合、毎回新しいプロトコルを発明するのはなぜですか? D-BusやQRemoteSignalなど、似たようなものが既に存在しますが、私にはあまり便利ではなく、いくつかの機能もありません。



同意して、このような何かを書くことは非常に便利です:

コンピューター1:

//      xValue net.addProprety("value", "xValue", object); //   net.addSignal("started", object, SIGNAL(started(int, QString))); //   net.addFunction("start", object, "method_name");
      
      







コンピューター2:

 //     net.setProperty("value", 123); //    -  ,  QLineEdit net.bindProperty("value", lineEdit, "text"); //     net.connect(SIGNAL(started(int, QString)), object, SLOT(onStarted(int, QString))); //   ( ) bool ok; QVariant ret = net.call("start", QVariantList() << "str1" << 1, &ok); //   (      ) net.call("start", QVariantList() << "str1" << 1, object, SLOT(startCalled(bool, QVariant)));
      
      





netは任意の抽象的なネットワークアクセスインターフェイスです



また、オブジェクトのプロパティまたはシグナルのリスト全体をすぐに利用できるようにするメソッドを作成するのも簡単です。 そして、それだけではありません! これらすべてにQt Quickを簡単に添付できます。 一般に、プロパティの変更について学習し、信号をキャッチし、実行時に任意のタイプのスロットを実行する方法を理解したら、多くのことができます。

最も単純なプロパティから始めましょう。



1.プロパティ



最初に、プロパティを動的に変更する方法、さらに重要なこととして、フォームの変更に関するシグナルを受信する方法について説明します。<プロパティ名、新しい値>



問題があるのはプロパティの名前です。すべてのプロパティの変更に関する信号を1つのスロットに接続すると、変更されたプロパティの名前を見つけることができなくなります。 送信者()関数を使用してこの変更を投稿したユーザーを見つけることしかできませんが、この方法ではプロパティの名前を見つけることができません。 次に、プロパティの名前を格納するクラスのオブジェクトを各プロパティに対して作成し、その変更に関するシグナルを受信し、名前を付けて新しいシグナルを生成することがすぐに思い浮かびます。



前額法


 Class Property { public: Proprety(const QString &name) : m_name(name) {} public slots: void propertyChanged(const QVariant &newValue) { emit mapped(m_name, newValue); } signals: void mapped(const QString &propertyName, const QVariant &newValue); }
      
      





オブジェクトオブジェクトのプロパティp1、p2の変更について知りたい場合は、次のコードを記述できます。

 PropertyMapper *m1 = new PropertyMapper("p1"); connect(object, SIGNAL(p1Changed(QVariant)), m1, SLOT(propertyChanged(QVariant)); PropertyMapper *m2 = new PropertyMapper("p2"); connect(object, SIGNAL(p2Changed(QVariant)), m2, SLOT(propertyChanged(QVariant));
      
      





次に、単にPropertyMapper ::マップされた信号に接続し、プロパティの名前とその新しい値を持つ信号を取得します。 しかし、ここでは明らかな問題をすぐに見ることができます:メモリとプロセッサリソースの浪費、さらに重要なことには、各タイプ、変換などに追加のスロットを作成せずに他のタイプのプロパティを操作できないことです。 一般に、これらすべての問題を一度に解決するはるかに洗練された方法があります。



高度な方法


最初に、スロットがどのように呼び出され、QObject :: connect()関数がどのように機能するかを見てみましょう。

スロットコール

クラス宣言に追加されたQ_OBJECTマクロは、(特に)qt_metacall()メソッドを追加します。 スロットが呼び出され、プロパティが設定されます。 さらに、スロットの存在のすべてのチェック、引数の削減がスロットに実装されています。 標準実装は次のようになります。

 int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QObject::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break; } _id -= 2; } return _id; }
      
      







QObject ::接続


この関数によって実行されるアクションを簡単に見てください。

1)信号およびスロットの名前を正規化された形式に変換します。 余分なスペースの削除、およびその他の変換(詳細はQMetaObject :: normalizedSignature())

2)型チェック

3)object-> metaObject()-> indexOfSlot(indexOfSignal)()を使用して、名前によるスロットとシグナルのインデックスの計算

4)そして最も興味深いのは、QMetaObject :: connect()を使用したインデックスによる信号とスロットの接続です。

多くの人がすでに何をする必要があるかを推測していると思います-qt_metacall実装を記述し、プロパティ変更のシグナルに手動で接続します。 続行:



PropertyMapper.h

 class PropertyMapper : public QObject //     Q_OBJECT { public: PropertyMapper(QObject *mapToObject, const char *mapToMethod, QObject *parent = 0); int addProperty(const QString &propertyName, const char *mappingPropertyName, QObject *mappingObject, bool isQuickProperty); void setMappedProperty(const QString &name, const QVariant &value); QVariant mappedProperty(const QString &name) const; int qt_metacall(QMetaObject::Call call, int id, void **arguments); private: QObject *m_mapTo; const char *m_toMethod; QHash<QString, int> m_propertyIndices; typedef struct { QString name; QVariant::Type type; const char *mappingName; QObject *mappingObject; bool isQuickProperty; // need to call mappingObject->property to get value QVariant lastValue; } property_t; QList<property_t> m_properties; };
      
      





すべてのフィールドが必要な理由を説明しませんが、今ではすべてが明確になります。 キーポイントを細かく検討します(全体は記事の最後でダウンロードできます)。



propertyNameという名前のプロパティを追加すると、すべてのアクションはmappingObjectオブジェクトのmappingPropertyNameプロパティで発生します。 Qt Quickプロパティでこのフォーカスを作成したい場合、isQuickPropertyをtrueに設定する必要があります(これがどのように行われるかは後で明らかになります)。



最初に、同じ名前のプロパティがあるかどうかを確認します。 (m_propertyIndi​​cesにはproperty_name-property_indexのペアが含まれます):

 int PropertyMapper::addProperty(const QString &propertyName, const char *mappingPropertyName, QObject *mappingObject, bool isQuickProperty) { if (m_propertyIndices.contains(propertyName)) { qWarning() << "can't create" << propertyName << "property, already exist!"; return -1; }
      
      





プロパティインデックスを取得してから、QMetaPropertyインデックスを取得します。

  int propertyIdx = mappingObject->metaObject()->indexOfProperty(mappingPropertyName); QMetaProperty metaProperty = mappingObject->metaObject()->property(propertyIdx);
      
      





追加されたプロパティに関する情報を保存します。

  int id = m_properties.size(); m_propertyIndices[propertyName] = id; m_properties.push_back({propertyName, metaProperty.type(), mappingPropertyName, mappingObject, isQuickProperty, QVariant()});
      
      





最も興味深いのは、プロパティ変更のシグナルインデックスを取得して接続することです。タイプチェックは実行されません。 プロパティのタイプ(metaProperty.type())を保存し、取得したプロパティ値をキャストします。

  int signalId = metaProperty.notifySignalIndex(); if (signalId < 0) { qWarning() << "can't create" << propertyName << "(notify signal doesn't exist)"; return -1; } if (!QMetaObject::connect(mappingObject, signalId, this, id + metaObject()->methodCount())) { qWarning() << "can't connect to notify signal:" << mappingPropertyName; return -1; } return id; }
      
      







そして最も重要なのは、qt_metacall():

 int PropertyMapper::qt_metacall(QMetaObject::Call call, int id, void **arguments) { // ,   ,  ,    id = QObject::qt_metacall(call, id, arguments); if (id < 0 || call != QMetaObject::InvokeMetaMethod) return id; Q_ASSERT(id < m_properties.size());
      
      





以前に保存されたプロパティ情報を取得します。

  property_t &p = m_properties[id];
      
      





クイックプロパティでフォーカス: quickプロパティを変更するためのシグナルはsmthChanged()という形式を持っています。 実際の値がなければ、手動で取得します。 そして、クラスオブジェクトを作成するときに指定されたメソッドを呼び出すだけです(信号を生成することはできません。Q_OBJECTマクロを追加しなかったので、もちろんそれなしで実行できますが、なぜ必要なくすべてを複雑にしますか...):

  QVariant value; if (p.isQuickProperty) { value = p.mappingObject->property(p.mappingName); } else { const void *data = arguments[1]; value = QVariant(p.type, data); } if (value != p.lastValue) { p.lastValue = value; QMetaObject::invokeMethod(m_mapTo, m_toMethod, Q_ARG(QString, p.name), Q_ARG(QVariant, value)); } return -1; }
      
      





また、プロパティの最後の値も保存します。これは、クライアントとサーバーが1つしかない場合にのみ行うことはできませんが、参加者が多い場合、外部からプロパティを変更すると雪崩効果が発生する可能性があります(つまり、このプロパティを同じものに何度も設定します)値)。



小さな使用例:

 Reciever reciever; PropertyMapper mapper(&reciever, "mapped"); Tester tester; mapper.addProperty("value_m", "value", &tester); mapper.addProperty("name_m", "name", &tester); tester.setName("Button1"); tester.setValue(123);
      
      





テスター-2つのプロパティのみが含まれ、次のメソッドを受信します。

 Q_INVOKABLE void mapped(const QString &propertyName, const QVariant &newValue) { qDebug() << propertyName << newValue; }
      
      





以下を開始します。

「Name_m」QVariant(QString、「Button1」)

「Value_m」QVariant(int、123)


今日はこれですべてです:)



クラス全体



All Articles