JavaFXのGroovyとJava

画像



JavaFXは良いです!



最初に、JavaFXについて少し説明します。 私たちは彼女と一緒に仕事が好きでした。



最新のAPI 「ビルダー」がなくても、すべてが非常にモダンに見えます。



トータルデータドリブン開発 。 それを崇拝します。 大量のデータに基づいたロジックは、ジャンク、ゲッター/セッターからコードをクリアします-「ダウン!」。 データ変更イベント、双方向の「バインド」を処理します。



FXML プロトタイピングに最適です。 設計者には明らかです。Oracleの優れた視覚ツール「JavaFX Scene Builder」があります。 それから、FXMLを通常のコードの形で書き直したかったことに注意してください。 FXMLの保守はコードよりも簡単です。コードとFXMLの2つのファイルを常に編集する必要があります。 さらに、コードを使用する場合、継承を使用する方が簡単です。



ノード コンポーネントの構造。 ツリーで実行できます。 ルックアップで検索できます()。 DOMのように。 jQueryを書くだけです。



CSS これは本当にものです。 1つの一般的なcssファイルを介してコンポーネントを「スロー」します。 IDシュニック、CSSクラス、セレクター、および疑似セレクター。



テキストエンジン 複雑なテキスト用の非常に優れたエンジン。



WebView Webkitエンジンに洗練されたコンポーネントを実装します。 前の記事でそれについて読んでください



あまり良くない



いいですね 何が悪いの? かつてJavaFXスクリプトは発明されただけではありませんでした。 ゲッターとセッターを介してバインド可能なデータにアクセスするためのフィールドを作成することは、一歩後退して昨日です。 Javaはここではあまり良くありません。 Java 8にはラムダ式がありますが、その外観は、Javaで何かを行う必要があるという質問に対する答えであり、より基本的なソリューションについて考える機会でもあります。



グルーヴィー!



Groovyを選択することで、これらの問題をすべて自分で解決しました。 簡潔で、良い意味で古いもの(成熟したもの)であり、IDEAでよく管理されています。 Groovyを使用すると、コードを10回確実にカットできました。 動作し、見た目も読み取りもJavaのように見えますが、コンパクトさという点では優れています。



JVMには多くの優れた美しい言語がまだありますが、Groovyが私たちに合っているのは偶然です。 また、かっこ、注釈が大好きで、ここで何かを壊したくありません。 さらに、私は個人的にGroovyを使用した7年の経験があり、チームに専門家がいる場合は、完全に未知のものをとるよりも使用する方が良いです。



ちなみに、Groovyは言語の人気で18位になっています( TIOBEによる)。



私たちの実践



次に、いくつかの例を見てみましょう。 プロジェクトからコピーします。コードは本物です。



コンポーネント構成



コードを使用してコンポーネントのインスタンスを作成し、構成するだけです。

Javaでは、値を割り当てるために、1行ずつ1ステップずつステップを踏む必要がありました。



Button button = new Button(); button.setFocusTraversable(false); button.setLayoutX(23); button.setPrefHeight(30); button.setPrefWidth(30); button.setText("ADD");
      
      





Groovyを書き換えた場合、同じように見えますか?



 Button button = new Button(focusTraversable: false, layoutY: 23, prefHeight: 30, prefWidth: 30, text: "Add")
      
      





Groovesは、知らない人に、set / getプレフィックスなしでアクセス方法(ゲッター、セッター)にアクセスできるようにします。 つまり、クラスにsetTextメソッドがある場合、値の単純な割り当て(text = "Add")によって呼び出されます。 さらに、Groovyクラスをコンパイルすると、ゲッターとセッターが自動的にパブリックフィールドに追加されます。 したがって、set / getメソッドを実際に必要としない場合に溝から呼び出すことは秘密ではありません。



また、コンストラクターパラメーターにペアを渡すことができます-name:value(実際、これは通常のHashMapであり、ここでの構文はGroovy Maps-[key1:value1、key2:value])。



さらに、IDEAがこれらすべてを通知し、データ型とアクセス制限を検証することが重要です。



コンポーネントを構成するこの方法は、すぐにコンポーネントの構造を構成することは不可能であることを示唆していますか?



できます!



 menus.addAll( new Menu(text: "File", newItems: [ new MenuItem( text: "New Window", onAction: { t -> ApplicationUtil.startAnotherColtInstance() } as EventHandler<ActionEvent>, accelerator: new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN) ), new Menu(text: "New Project", newItems: [ newAs = new MenuItem( text: "New AS Project", id: "new-as", onAction: { t -> ProjectDialogs.newAsProjectDialog(scene, false) } as EventHandler<ActionEvent> ), newJs = new MenuItem( text: "New JS Project", id: "new-js", onAction: { t -> ProjectDialogs.newJsProjectDialog(scene, false) } as EventHandler<ActionEvent> ) ]), new SeparatorMenuItem(), new MenuItem( text: "Open Project", onAction: { t -> ProjectDialogs.openProjectDialog(scene, false) } as EventHandler<ActionEvent>, accelerator: new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN) ), recentProjectsSubMenu = new Menu(text: "Open Recent", newItems: [ clearRecentProjects = new MenuItem( text: "Clear List", onAction: { t -> RecentProjects.clear() } as EventHandler<ActionEvent> ), ]), new SeparatorMenuItem(), save = new MenuItem( text: "Save Project", id: "save", onAction: { t -> ProjectDialogs.saveProjectDialog() } as EventHandler<ActionEvent>, accelerator: new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN), disable: true ), saveAs = new MenuItem( text: "Save As...", onAction: { t -> ProjectDialogs.saveAsProjectDialog(scene) } as EventHandler<ActionEvent>, accelerator: new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN), ), new MenuItem( text: "Close Project", onAction: { t -> ProjectDialogs.closeProjectDialog() } as EventHandler<ActionEvent>, accelerator: new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), ), new SeparatorMenuItem(), new MenuItem( text: "Exit", onAction: { t -> ApplicationUtil.exitColt() } as EventHandler<ActionEvent> ), ]), new Menu(text: "Help", newItems: [ new MenuItem( text: "Open Demo Projects Directory", onAction: { t -> ProjectDialogs.openDemoProjectDialog(scene) } as EventHandler<ActionEvent> ), new MenuItem( text: "Open Welcome Screen", onAction: { t -> ProjectDialogs.openWelcomeScreen(scene) } as EventHandler<ActionEvent> ), ]) )
      
      





このようなコードは、FXMLほど読みやすくありません。 さらに、ここでは、FXMLで実行できなかったすべてのイベントハンドラーを説明できます。 そして、そのようなコードの保守は簡単です。



動的なプロパティとメソッド



気配りのある読者は、メニュー項目「newItems」は何のためにあるのでしょうか? はい、Menuクラスにはそのようなメソッドはありません。 項目フィールドのみを読み取ることができるが、割り当てることができないため、このようなメソッドを追加しました。「setItems()」メソッドはありませんが、「getItems()」のみがあり、新しい値を割り当てることはできません。メニューを構造として構成し、動的フィールドを追加しました。



このようなフィールドの追加は非常に簡単ですが、Javaエンティティは、動的メソッドなどのセディションに長い間抵抗していました。 私たちは、ダイナミクスを使用する必要性の事実と和解するまで、多くの自転車を発明しました。 そして、すべてがシンプルで大胆不敵であることが判明しました。



動的フィールドを追加して、別のクラスGroovyDynamicMethodsに移動しました。 彼のコードは次のとおりです。



 class GroovyDynamicMethods { private static inited = false static void init() { if(inited)return inited = true addSetter(javafx.scene.Node, "newStyleClass", { String it -> styleClass.add(it) }) addSetter(Parent, "newChildren", {List<MenuItem> it -> children.addAll(it) }) addSetter(Menu, "newItems", {List<MenuItem> it -> items.addAll(it) }) } private static void addSetter(Class clazz, String methodName, Closure methodBody) { addMethod(clazz, "set" + methodName.capitalize(), methodBody) } private static void addMethod(Class clazz, String methodName, Closure methodBody) { ExpandoMetaClass exp = new ExpandoMetaClass(clazz, false) exp."$methodName" = methodBody exp.initialize() clazz.metaClass = exp } }
      
      





ご覧のとおり、3つのメソッドを追加するだけで、構造全体でコンポーネントの構成をサポートできます。



さらに、クラスにこれらの動的フィールドがあることを理解するようにIDEAに教えました。



画像



IDEAは、そのようなフィールドがJavaFX APIに存在するかのように存在することを認識しています。



バインド可能なプロパティの使用



データバインディングは素晴らしいことです。 私たちのチームはこのマントラを使用します-「バインディングを通して何かができるなら、バインディングを通してそれをしてください。」 「...後でやり直さないように。」



バンディングを使用すると、データモデルとコンポーネントを関連付けることができます。 UIコンポーネント自体には、モデルデータに関連付けられる、またはこれらのプロパティロジックの変更に基づいて、データ変更イベントにサブスクライブできるバインディングプロパティがあります。



簡単なチェックボックスの例:



 CheckBox checkBox = new CheckBox(); checkBox.selectedProperty().bindBidirectional(selectedProperty);
      
      





そして、チェックボックスをクリックするイベントに反応します:



 CheckBox checkBox = new CheckBox(); checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> value, Boolean before, Boolean after) { System.out.println("value = " + value); } });
      
      





使用は便利です。 そのようなプロパティを記述することはあまり便利ではありません。



Javaはそのようなシナリオを提供します(IDEAコードは自動的に生成されます)。



 private StringProperty name = new SimpleStringProperty(); //   //     (     ) public StringProperty nameProperty() { return name; } //    public String getName() { return name.get(); } //       public void setName(String name) { this.name.set(name); }
      
      





すべてが問題なく、IDEがそのようなコードを生成します。 まあ、それは愚かではありませんか? なぜこれをすべて見る必要があるのですか? これらすべてのゴミの背後に、私たちの論理は見えません。



解決策! このコードを生成するAST変換を使用します。 コンパイル時。



プロパティ(Javaで10行で記述)は、1行でGroovyに変わり、次のようになります。



 @FXBindable String name;
      
      





@FXBindableは、 GroovyFXを使用することも、 私たちのものを使用することもできます。

この注釈を分岐し、 githubで取得できます。



さらに、同じプロジェクトでは、拡張子.gdslのファイルがあり、IDEAにこの注釈を使用するように指示します-autocliteなど



このような変換では、setName、getName、getNamePropertyメソッドも作成されます。 さらに、name()メソッドも追加され、さらに少ない文字を書くことでフィールドにアクセスできるようになります。 味わうが、ほとんどの場合、この特定の方法を使用します。



 this.nameInput.textProperty().bindBidirectional(this.name()) // this.name() -     name
      
      





匿名クラスでダウン



メニューの例では、匿名クラスを介してイベントをサブスクライブします。 メニュー構造の例は、イベントハンドラーが「シェル」であることを示しています。



 onAction: { t -> ProjectDialogs.newAsProjectDialog(scene, false) } as EventHandler<ActionEvent>
      
      





「as EventHandler」のすべての魔法は、トレジャーの本体がEventHandlerクラスのhandleメソッドの本体に移動することです。 このような短いレコードを使用してイベントを処理すると、コードが簡潔になります。 ちなみに、スマートIDEAは、クイック変更の「Quick to instant instantiation」クイックフィックスを提供します。 クラスハンドラーでいくつかのメソッドをオーバーロードする必要がある場合は、Map([handler1:{}、handler2:{}])を介して別の書き込みを使用することもできます。



XMLの仕事



このプロジェクトでは、データモデルをXMLでシリアル化し、ディスクから取得する必要がありました。 最初は習慣的にXStreamを使用したかったのですが、より管理しやすい構造が必要でした-JavaFXのBindableプロパティは大きく、コンバーターは書くのが面倒です。 JAXBを見て、あまりにも悪い。 また、Groovy XMLシリアル化を使用します。



XmlSlurperビルトインGroovy SDKが登場しました。



各Beanモデルは、buildXmlとbuildModelの2つのメソッド-シリアライゼーションとデシリアライゼーションを実装します



 Closure buildXml(Project project) { return { 'launcher'(launcherType) 'browser-path'(browserPath) 'nodejs-path'(nodejsPath) 'console-value'(console) } } @Override void buildModel(Object node) { launcherType = node.'launcher' browserPath = node.'browser-path' nodejsPath = node.'nodejs-path' console = node.'console-value' }
      
      





buildXmlメソッドは、構造をブックマークとして返します。 ここでの魔法は、存在しないメソッドとプロパティを呼び出して割り当てることです。 存在しないメソッドが呼び出されると、プロパティが子ノードの形式で作成され、値が存在しないフィールドに割り当てられると、XML属性が作成され、存在しないメソッドが呼び出され、パスがパラメーターとして渡されると、埋め込みXMLノード構造が作成されます。



buildModelメソッドはノード引数を取り、動的リクエストを介してノードを解析します。



ファイルを操作する



私たちのプログラムは、ファイルシステムで大いに機能します。 Groovyを使用して、IOコードを大幅に削減することができました。 ナノ秒ごとに保存する必要はありませんでしたし、ビジーなWebサーバーもありませんでした。Groovyが多くの作業を行ってくれたことは私たちにとっては問題ありませんでした。



Groovy SDKは、Fileを含むJavaクラスの多くの便利な拡張機能を提供します。 たとえば、単に「テキスト」フィールドを介してファイルの内容を読み書きする機能、または「splitEachLine」を使用してファイルの行を操作する機能。



さらに、ファイルの検索とフィルタリングにも使用できるAntBuilderが気に入りました。



次の例では、ファイルをコピーします。



 def ant = new AntBuilder() ant.sequential { myDir = "test/to/" mkdir(dir:myDir) copy(todir:myDir) { fileset(dir:"text/from/") { include(name:"**/*.*") } } }
      
      





fileScanerを使用して、パターンでファイルを検索できます。



 def ant = new AntBuilder() def scanner = ant.fileScanner { fileset(dir: file) { include(name: "**/*.jpg") } } scanner.each{ printlt(it) }
      
      





そしてもちろん、AntBuilderはすべての拡張機能を備えた本格的なANTです。 まだ勉強と勉強があります。 GradleもAntBuilderを使用しており、そこに「ねじる」ことができるという事実は印象的です。



GPathを使用してノードを操作する



JavaFXのコンポーネントの構造以来、ノードの要求をコレクションとして使用しました。 このアプローチでは、多数のループを取り除き、コードを大幅に削減しました。



たとえば、Javaでのスクロールを削除するには:



 webView.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>() { @Override void onChanged(ListChangeListener.Change<? extends Node> change) { Set<jNode> scrolls = webView.lookupAll(".scroll-bar"); for (Node scroll : scrolls) { scroll.setVisible(false); } } });
      
      





Groovyでも同じこと:



 webView.childrenUnmodifiable.addListener({ change -> webView.lookupAll(".scroll-bar")*.visible = false } as ListChangeListener)
      
      





ファイティングNPE



演算子「?。」-私たちの意見では、1つだけで、JavaからGroovyへの切り替えについて考えることができます。



 model?.projectSettings?.projectPaths?.livePaths?.each{ println(it) }
      
      





これをJavaに変換し、少なくとも20行のコードを取得します。



おわりに



おそらく私たちが覚えていることのすべてです。 もちろん、プロジェクトでは他のGroovyの「グッズ」を使用しましたが、すべてをリストすると、記事の範囲を超えてしまい、Groovyには多くの教科書があります。



しかし、Groovyから私たちに合わないものについて話をしたいと思います。 まず、不必要なダイナミクスを回避しました。 私たちのチームでは、変数またはフィールドを作成するときにタイプを指定する必要があることに同意しました(ストッキングのパラメーターを除く-ここではそれらの喜びの半分が失われます)。 また、ミックスインとオーバーロード演算子を使用しませんでした。 コードジャグリングは悪い習慣だと考えています。コンパクトなだけでなく、制御されたサポートされたコードも必要です。 それがおそらくすべてです。 GroovyはJavaに非常によく似ており、このコンテキストで使用しました。コンパイル中にAST変換が実行され、コードを作成するときに、デザインに何か他のものが自動的に追加されると想定しています。 これは、自動生成機能を備えたJavaです。 そして、他に何も必要ありません。



Codeorchestra.comプロジェクトサイト



All Articles