日曜プログラミング

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

JavaFX 遊び TableView 編(5) - DB データを読み込んで表示してみる

前回で終わりと言っていたもののもう1回だけ。
個人的にやりたかった所の一つにようやく来た。

これは既に公式チュートリアルにはもうないネタではあるものの、やる事は固定データか DB から持ってきたかの違いでしかないのでそんなに変わらないはず。Clojure の DB ライブラリは今回は clojure.java.jdbc を使ってみる。

今回も基本ソース載せるだけ。

project.clj

依存関係として java.jdbc と特定の RDBMSjdbc ドライバを使うように追加。

(defproject fx-table-db "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"]
                 [org.clojure/java.jdbc "0.3.0-alpha5"]
                 [local.oracle/ojdbc14 "10.2.0.3.0"]]
  :aot [fx-table-db.core]
  :main fx-table-db.core)

しれっと local.oracle/ojdbc14 とか入ってるけど、これはローカル Maven リポジトリに追加済みのものを参照してるだけなので、Oracle DB 使うと言う人は以下を参考にしてもらえれば。
Maven リポジトリに登録されてない外部ライブラリを使う - 日曜プログラミング

core.clj

とりあえず表示だけさせてみると言う事でセル編集とデータ追加の機能は取っ払った。

DB 接続情報は使ってる RDBMS 毎に微妙に異なるので注意。また、サンプルの接続情報やクエリ投げてる所は架空のものに変えてるので使ってる DB に応じて適当に変えるといいかと。

(ns fx-table-db.core
  (:import javafx.application.Application
           [javafx.collections FXCollections ObservableList]
           [javafx.event EventHandler]
           javafx.geometry.Insets
           [javafx.scene Group Scene]
           [javafx.scene.control Label TableColumn TableView]
           [javafx.scene.control.cell MapValueFactory TextFieldTableCell]
           [javafx.scene.layout VBox]
           javafx.scene.text.Font
           javafx.stage.Stage
           [javafx.util Callback StringConverter])
  (:require [clojure.java.jdbc :as j]
            [clojure.java.jdbc.sql :as s])
  (:gen-class
   :extends javafx.application.Application))

(def ^:private db {:classname   "oracle.jdbc.driver.OracleDriver"
                   :subprotocol "oracle"
                   :subname "thin:@//localhost:1521/TEST"
                   :user "user"
                   :password "password"
                   :delimiter ""})

(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 (j/query db (s/select * :foo_master)))
        fields (->> data first keys)
        cell-factory (make-cell-factory)
        columns (map (fn [field width]
                       (doto (TableColumn. (name field))
                         (.setMinWidth width)
                         (.setCellValueFactory (MapValueFactory. field))
                         (.setCellFactory cell-factory)))
                     fields
                     (repeat (count fields) 100))
        scene (Scene. (Group.))
        label (Label. "DB Data")
        vbox (VBox.)]

    (doto stage
      (.setTitle "Table View Sample")
      (.setWidth 450)
      (.setHeight 500))

    (.setFont label (Font. "Arial" 20))

    (doto table
      (.setEditable true)
      (.setItems data))
    (.. table getColumns (setAll (to-array columns)))

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

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

    (doto stage
      (.setTitle "Table View Sample")
      (.setWidth 450)
      (.setHeight 500)
      (.setScene scene)
      (.show))))

(defn -main [& args]
  (Application/launch fx_table_db.core args))

今回まあ見るべき部分があるとすれば let のこの部分くらいかな。

        data (FXCollections/observableArrayList (j/query db (s/select * :foo_master)))
        fields (->> data first keys)
        cell-factory (make-cell-factory)
        columns (map (fn [field width]
                       (doto (TableColumn. (name field))
                         (.setMinWidth width)
                         (.setCellValueFactory (MapValueFactory. field))
                         (.setCellFactory cell-factory)))
                     fields
                     (repeat (count fields) 100))
  • data でクエリ結果を ObservableList の型でまるっと保持。今回は変更はしないので Clojure の PersistentMap のまま持たせる。
  • fields は Map で取れてるクエリ結果と紐付ける為のフィールド名だけ保持。*1
  • columns でフィールド数分の TableColumn の生成とヒモ付をまとめて実施。Clojure の keyword をそのままヒモ付のキーとして渡してるが問題なく表示できる。

何というか、ここだけ見ると随分と Clojure 率が上がってる感じがするw

*1:JDBC だとメタ情報で取れそうな気がするけどやり方分からん