AndroidでのQtシグナル/スロットの実装

まえがき


最近、かなり大きなプロジェクトをQt(C ++)からAndroid(Java)に移植しました。作業の過程で、しばしば動的オブジェクトバインディングを使用する必要がありました。 問題は、JavaのQtの通常のシグナルやスロットとは異なり、バインディングがリスナーを介して実装され、このメソッドが同等であり、シグナルを使用する場合と同じ利便性があると自分自身に納得させようとしなかったことですスロットに到達できませんでした。
たとえば、スライダー(QtのQSliderまたはAndroidのSeekBar)を何らかのアクションに関連付け、少なくとも最初のスライダーの後に素直に移動する別のスライダーをアタッチする必要があります。 Qtでは、同様の操作は次のとおりです。


例1

//   QSlider *primary = new QSlider(this); QSlider *secondary = new QSlider(this); //   ,  // ... //      connect(primary, SIGNAL(valueChanged(int)), secondary, SLOT(setValue(int))); 


その結果、 プライマリ スライダーの valueChanged()信号とセカンダリ スライダーのsetValue()スロットの接続を取得します。 Androidでも同じこと:

例2

 //   SeekBar firstBar = (SeekBar)findViewById(R.id.firstSeekBar); SeekBar secondBar = (SeekBar)findViewById(R.id.secondSeekBar); //  // ... //      firstBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override // ........ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { secondBar.setProgress(progress); } }); 


そして結果として、同じこと、つまりfirstBarsecondBarの動きを関連付けます。
ここで何が起こるか見てみましょう。 firstBarの腸のどこかにOnSeekBarChangeListener型の変数があり、移動が発生するnullにチェックされ、突然ゼロ以外になった場合、 onProgressChanged()メソッドが適切なパラメーターで呼び出され、その結果secondBar.setProgress(progress)が呼び出されます2番目のスライダーの値を設定します。
多少面倒ですが、すべてが非常に明確で理解しやすいものです。 この場合のQtはより簡潔ですが、 MOC(Meta Object Compiler)を使用してプロジェクトをビルドするプロセスでコードを生成することで動的リンクを実装することはC ++の範囲を超えています。 簡潔さの代価を支払う必要があります。これは、デバッグの過程で事前に生成されたコードに気づいたときに明らかになります。 しかし、幸いなことに、最も単純なルールに従えば、これは非常にまれです。
しかし、Androidに戻ります。 Android APIのすべてのクラスには、利便性を確保するのに十分なライセンサーのセットがありますが、信号とスロットで動作するコードの大きな配列がある場合はどうでしょうか? グーグルでは、Javaでのシグナルとスロットの実装をいくつか見つけましたが、最も価値があるのは、当然のことながら、QtのJavaでの不当に忘れられた実装であるQt Jambiライブラリです。 しかし、優れた実装はいくつかの理由で私には向いていませんでした。その最も重要な理由は、構文が元の構文と一致しないため、C ++とJavaのライブラリの同じテクノロジーが非常に異なって実装されていることです。
その結果、Java用のAndroid用の信号とスロットを独自に実装するというアイデアが生まれました。

挑戦する


Android APIを使用して、可能な限りQt C ++構文に近いJavaでシグナルおよびスロットメカニズムを実装します。

実装


どうした

何度か試行した結果、Java コネクタクラスは600行未満で記述されました。これには、いくつかの静的メソッドと、信号とスロットの静的マップ(Map)があります。 すべての機能は、4つの静的メソッドに囲まれています。

  1. ブール接続(オブジェクト送信側、ストリング信号、オブジェクト受信側、ストリングスロット、ConnectionTypeタイプ)
  2. void disconnect(オブジェクト送信者、文字列信号、オブジェクト受信者、文字列スロット)
  3. void emit(オブジェクト送信者、文字列signalName、オブジェクト... params)
  4. オブジェクト送信者()


ランナーの例では、接続は次のようになります。

例3

 //     Android API    private static class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { private Object mSender = null; public SeekBarChangeListener(Object sender) { mSender = sender; } @Override // ........ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { Connector.emit(mSender, "progressChanged", progress); } } //   SeekBar firstBar = (SeekBar)findViewById(R.id.firstSeekBar); SeekBar secondBar = (SeekBar)findViewById(R.id.secondSeekBar); //  // ... //  firsBar   firstBar.setOnSeekBarChangeListener(new SeekBarChangeListener(firstBar)); //      Connector.connect(firstBar, "SIGNAL(progressChanged(int))" , secondBar, "SLOT(setProgress(int))"); 

一見、 Connectorを使用した実装はlysenerを使用するよりも煩雑に思えるかもしれませんが、 SeekBarクラスは、他のすべての標準Android APIクラスと同様に、lysenerの使用によってシャープ化されることを忘れないでください。したがって、ラッパーを使用する必要があります。 クラスを開発するときにコネクタを使用するか、QtプロジェクトをAndroidに移植することにより、さらに多くのメリットを得ることができます。
  1. ライゼナー用のインターフェースを作成する必要はありません
  2. 変数を作成する必要はありません
  3. ライザーのセッターメソッドは不要


より複雑な例、実装の詳細については、次の記事をご覧ください。
(続く)

Source: https://habr.com/ru/post/J157363/


All Articles