日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

JavaFX 遊び TableView 編(3)

引き続き以下公式チュートリアルの TableView で残り試してない部分を試す。
Using JavaFX UI Controls: Table View | JavaFX 2 Tutorials and Documentation
Example 12-8 に相当。

今回は追加データ入力用のテキストフィールドを用意して追加ボタンを押すと追加されるコードを試す。ついでに前回で Clojure の Map でいける事も分かったので、元の Java の例でやってる Person クラスで 1 行データ云々の方法は取らず Map でやるようにちょいアレンジ。

結論としては問題なくできた。
今回も特に詳しく説明はせずソースだけ載せます。

project.clj

(defproject fx-table-add "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [local.oracle/javafxrt "2.2.25"]]
  :aot [fx-table-add.core]
  :main fx-table-add.core)

core.clj

(ns fx-table-add.core
  (:import [javafx.application Application]
           [javafx.collections FXCollections ObservableList]
           [javafx.event ActionEvent EventHandler]
           [javafx.geometry Insets]
           [javafx.scene Group Scene]
           [javafx.scene.control Button Label TableColumn TableView TextField]
           [javafx.scene.control.cell MapValueFactory TextFieldTableCell]
           [javafx.scene.layout HBox VBox]
           [javafx.scene.text Font]
           [javafx.stage Stage]
           [javafx.util Callback StringConverter])
  (:gen-class
   :extends javafx.application.Application))

(def ^:private key-names ["fname" "lname" "email"])

(defn make-cell-factory []
  (proxy [Callback] []
    (call [p]
      (TextFieldTableCell.
       (proxy [StringConverter] []
         (toString [t] (.toString t))
         (fromString [string] string))))))

(defn -start [this ^Stage stage]
  (let [table (TableView.)
        data (FXCollections/observableArrayList
              (map #(zipmap key-names %)
                   [["Jacob", "Smith", "jacob.smith@example.com"]
                      ["Isabella", "Johnson", "isabella.johnson@example.com"]
                      ["Ethan", "Williams", "ethan.williams@example.com"]
                      ["Emma", "Jones", "emma.jones@example.com"]
                      ["Michael", "Brown", "michael.brown@example.com"]]))
        hb (HBox.)
        scene (Scene. (Group.))
        label (Label. "Address Book")
        firstNameCol (TableColumn. "First Name")
        lastNameCol (TableColumn. "Last Name")
        emailCol (TableColumn. "email")
        table-columns [firstNameCol lastNameCol emailCol]
        cell-factory (make-cell-factory)
        addFirstName (TextField.)
        addLastName (TextField.)
        addEmail (TextField.)
        addButton (Button. "Add")
        add-fields [addFirstName addLastName addEmail]
        button-action (proxy [EventHandler] []
                        (handle [e]
                          (.add data (->> (map #(.getText %) [addFirstName addLastName addEmail])
                                          (zipmap key-names)))
                          (map #(.clear %) [addFirstName addLastName addEmail])))
        vbox (VBox.)]

    (doto stage
      (.setTitle "Table View Sample")
      (.setWidth 450)
      (.setHeight 500))
    (.setFont label (Font. "Arial" 20))
    (.setEditable table true)

    (.setItems table data)
    ; ===== (1) ====
    (doto firstNameCol
      (.setMinWidth 100)
      (.setCellValueFactory (MapValueFactory. (some #{"fname"} key-names))))
    (doto lastNameCol
      (.setMinWidth 100)
      (.setCellValueFactory (MapValueFactory. (some #{"lname"} key-names))))
    (doto emailCol
      (.setMinWidth 200)
      (.setCellValueFactory (MapValueFactory. (some #{"email"} key-names))))
    ; ===== (1) ====
    (.. table getColumns (setAll (to-array table-columns)))

    (map #(.setCellFactory % cell-factory) table-columns)
    (map #(.setPromptText %1 %2) add-fields ["First Name" "Last Name" "Email"])
    (map #(.setMaxWidth %1 (.getPrefWidth %2)) add-fields table-columns)

    (.setOnAction addButton button-action)
    (.. hb getChildren (addAll (to-array [addFirstName addLastName addEmail addButton])))

    (.setSpacing hb 3)

    (doto vbox
      (.setSpacing 5)
      (.setPadding (Insets. 10 0 0 10)))

    (.. vbox getChildren (addAll (to-array [label table hb])))

    (as-> (.getRoot scene) obj (cast Group obj) (.getChildren obj) (.addAll obj (to-array [vbox])))

    (doto stage
      (.setScene scene)
      (.show))))

(defn -main [& args]
  (Application/launch fx_table_add.core args))
  • ちょっと map を使い出してみた
  • コメントで ==== (1) ==== と挟んでる部分は↓のように置き換えられるんじゃと思って試したがデータも入らなければ列幅も全く設定されないのかすごい狭くなった。良く分からんが doto マクロ入れてるのがまずい?
    (map (fn [c width k]
           (doto c
             (.setMinWidth width)
             (.setCellValueFactory (MapValueFactory. (some #{k} key-names)))))
         [firstNameCol lastNameCol emailCol]
         [100 100 200]
         key-names)

まとめ

  • import を横に伸ばして書けるのはちょっと良いかも
  • さすがに start メソッド長くなってきたか。元がそうで割とそのままなんでしょうがないっちゃしょうがないが。
  • 複数インスタンスで同じメソッド呼び出し繰り返す場合 map が有効かも
    • 型もまたがってくるような場合はプロトコルでまとめちゃえといった所か