おじさんになると、
よっぽど興味を持ったこと以外はすぐに忘れてしまうようだ。
忘れるというよりも、覚えてるけどうまく思い出せない、というべきか。
今週ランチで食べたものを思い出してみようとしても、
昨日のメニューすらなかなか出てこないことがある。
頭で覚えられないならしかたない、頭の外でデータ化しよう、
ということで今回は食べたものを登録できるツールを作ってみる。
まずはシンプルな機能で
まずは食事内容を入力したらデータベースに登録してくれる
シンプルな機能として実装してみよう。
登録内容は
- 年月日
- 食事区分 (朝食/昼食/夕食)
- 主食
- 主菜(メインディッシュ)
- 副菜(サブディッシュ。複数)
くらいで始める。
いつもはコマンドを流すだけのスクリプトを作ることが多いけど、
今回は入力しやすいようにGUIアプリに挑戦する。
こんな入力画面を作るイメージ:
Clojure + JavaFX + PostgreSQLで実装する
スマホアプリではなく、デスクトップアプリ。
環境は、
- Java 8
- Clojure 1.8
- PostgreSQL 9.5
Clojureベースで作りたいので、JavaのGUIライブラリである
JavaFXと組み合わせる。
JavaFXはJava8から追加されたSwingに代わる標準GUIライブラリらしく、
初めて使ったけど、思ったより作りやすかった。
JavaDoc API
HTML風のFXMLと呼ばれるレイアウト定義ファイルと独自のCSSを
組み合わせてレイアウトを外部化できる。
Scene Builderという無料ツールも提供されており、これを使うと
GUIでレイアウトを組み立てていくことができる。楽。
JavaFX Scene Builder Information
Scene BuilderでFXMLファイルを作成する
Scene Builderで上の入力画面イメージを作って保存したFXMLファイルがこちら。
app.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import java.lang.*?> <?import javafx.scene.layout.*?> <VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> <children> <GridPane prefHeight="253.0" prefWidth="574.0"> <columnConstraints> <ColumnConstraints hgrow="SOMETIMES" maxWidth="282.0" minWidth="10.0" prefWidth="187.0" /> <ColumnConstraints hgrow="SOMETIMES" maxWidth="391.0" minWidth="10.0" prefWidth="391.0" /> </columnConstraints> <rowConstraints> <RowConstraints maxHeight="45.0" minHeight="10.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="84.0" minHeight="10.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="109.0" minHeight="10.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="154.0" minHeight="10.0" prefHeight="74.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="104.0" minHeight="0.0" vgrow="SOMETIMES" /> </rowConstraints> <children> <Label text="日付" /> <Label text="食事区分" GridPane.rowIndex="1" /> <Label text="主食" GridPane.rowIndex="2" /> <Label text="メインディッシュ" GridPane.rowIndex="3" /> <Label text="サイドディッシュ" GridPane.rowIndex="4" /> <DatePicker fx:id="daySelect" GridPane.columnIndex="1" /> <TextField fx:id="stapleTxt" GridPane.columnIndex="1" GridPane.rowIndex="2" /> <TextField fx:id="mainDishTxt" GridPane.columnIndex="1" GridPane.rowIndex="3" /> <ComboBox fx:id="mealDivSelect" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="1" /> <Button fx:id="saveBtn" mnemonicParsing="false" text="登録" GridPane.columnIndex="1" GridPane.rowIndex="5" /> <TextArea fx:id="sideDishesTxt" prefHeight="49.0" prefWidth="391.0" GridPane.columnIndex="1" GridPane.rowIndex="4" /> </children> </GridPane> </children> <padding> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </padding> </VBox>
ここでのポイントは、
それぞれの入力項目と登録ボタンにつけた「fx:id」属性。
これをClojure側で参照してイベント処理したり入力値を取得したりする。
FXML側でイベント定義もできるのだけど、
今回はClojure側で処理しやすいようにレイアウト定義だけを行い、
イベントの設定はClojure側で行った。
データ登録用テーブルを作成する
テーブルは下記のように定義する。
サイドディッシュは複数登録できるように
PostgreSQLのJSONB型カラムにJSON配列として登録する。
create table meshilogs ( id serial not null, day date not null default current_date, meal_div int not null, staple varchar(255) not null, main_dish varchar(255) not null, side_dishes jsonb default '[]'::jsonb, created timestamp not null default current_timestamp, primary key(id) );
Leiningenプロジェクトを作成する
ここからメインのClojureコーディング。
食事履歴なので「meshilog」という名前で作成する。
project.clj
(defproject meshilog "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.8.0"] [cheshire "5.6.3"] [korma "0.4.2"] [org.clojure/java.jdbc "0.4.2"] [org.postgresql/postgresql "9.4-1203-jdbc42"]] :resource-paths ["resources"] ; 追加1 :aot :all ; 追加2 :main ^:skip-aot meshilog.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
JavaFXのライブラリはJava8標準ライブラリなのでここでは特別な指定は不要。
コメントの「追加1」の行を追加しておくと、
resourcesフォルダに置いたfxmlファイルを相対的パスで取得できる。
「追加2」の行はmain関数からApplication/launchした時に
指定したcljファイルのstart関数を呼び出すために必要。
続いて、メインのcljファイル。
meshilog/core.clj
(ns meshilog.core (:gen-class :extends javafx.application.Application) (:import (java.sql Date) (javafx.application Application) (javafx.event ActionEvent EventHandler) (javafx.scene Scene) (javafx.scene.control Button) (javafx.scene.layout StackPane) (javafx.stage Stage) (javafx.fxml FXMLLoader) (javafx.collections FXCollections)) (:require [clojure.java.io :as io]) (:require [cheshire.core :as cheshire]) (:require [korma.db]) (:require [korma.core :as kc])) (korma.db/defdb db {:user "<DBユーザー名>" :password "<DBパスワード>" :subname "//localhost:5432/<DB名>" :port "5432" :subprotocol "postgresql"}) (kc/defentity meshilogs) (defn -start [this ^Stage stage] (let [root (-> "app.fxml" (io/resource) (FXMLLoader/load)) day-select (.lookup root "#daySelect") meal-div-select (.lookup root "#mealDivSelect") staple-txt (.lookup root "#stapleTxt") main-dish-txt (.lookup root "#mainDishTxt") side-dishes-txt (.lookup root "#sideDishesTxt") save-btn (.lookup root "#saveBtn") meal-divs {:朝食 1 :昼食 2 :夕食 3}] (doto meal-div-select (.setItems (FXCollections/observableArrayList (map name (keys meal-divs))))) (.setOnAction save-btn (proxy [EventHandler] [] (handle [_] (kc/insert meshilogs (kc/values {:day (Date/valueOf (.getValue day-select)) :meal_div (get meal-divs (keyword (.getValue meal-div-select))) :staple (.getText staple-txt) :main_dish (.getText main-dish-txt) :side_dishes (kc/raw (str "'" (cheshire/generate-string (clojure.string/split (.getText side-dishes-txt) #"\n")) "'::jsonb"))})) (println "log saved")))) (doto stage (.setTitle "Meshilog") (.setScene (Scene. root 500 350)) .show))) (defn -main [& args] (Application/launch meshilog.core args))
基本はJavaでJavaFXプログラムを作成するのと同じ流れとなる。
main関数でApplication/launchメソッドを実行すると
JavaFXが立ち上がってstart関数が呼び出される。
start関数の中では、
- fxmlファイルをロード
- fx:idを指定してfxml内の入力項目、ボタンオブジェクトを取得
- コンボボックス(meal-div-select)に選択肢を設定
- ボタン(save-btn)にイベントハンドラをセット
- ステージウィンドウを表示
という手順で処理を行っている。
今回はボタンのイベントハンドラでそのまま入力値を
データベースにinsertしてる。
ここまで書いて
lein run
すると、ウィンドウが立ち上がって食事履歴登録ができる。
lein uberjar
で実行可能jar化することで、
DBにアクセス可能なマシンなら
どこからでも実行できるアプリになる。
おわり
これで基本的な構成ができたので、
ここから食事履歴検索できるようにしたり、
メニューをマスタ化してカロリー計算したり色々応用できそう。
これからも年をとってうまくできなくなってきたことを
プログラムで補っていきたい。

- 作者: 大村忠史
- 出版社/メーカー: カットシステム
- 発売日: 2012/09
- メディア: 単行本
- クリック: 2回
- この商品を含むブログ (6件) を見る