Gitのコマンドを使ってみる(その1)

Gitクライアントが重くて仕方がないので、そろそろちゃんと?コマンドを使ってGitの操作をしたいと思う。
まずはローカルのみのリポジトリで練習。

リポジトリの作成(init)

リポジトリを作りたいフォルダでコマンドを実行する。

$ git init
Initialized empty Git repository in xxx

すると .git フォルダが出来てリポジトリの初期化が行われる。

インデックスに追加(add)

ファイルを追加したり修正したりした場合、それをインデックスに追加する。
何かファイル(今回は空のREADME.mdファイル)を作成し、以下のコマンドを実行する。

$ git add .

ブランチにコミット(commit)

インデックスにあるファイルをブランチにコミットする。

$ git commit -m "first commit."
[master (root-commit) 70821ef] first commit.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README.md

リポジトリの確認(status)

何をするにも確認がだいじ。

$ git status
On branch master
nothing to commit, working tree clean

ブランチの作成(branch)

masterブランチだけで運用するのもアレなので、git-flowを想定して「master」「release」「develop」を作る。
initでリポジトリを作成すると「master」が出来るので、残りの「release」と「develop」を作成する。

branchコマンドは現在のブランチを基に分岐するので、いまどのブランチにいるかを把握する必要がある。
どのブランチにいるかは、先のstatusコマンドで調べられる。
On branch masterと言っていたので、現在は「master」ブランチにいることが分かる。

$ git branch release
$ git branch develop

ローカルブランチの確認(branch -l)

ローカルにあるブランチの一覧を表示する。

$ git branch -l
  develop
* master
  release

現在「master」にいて、先ほど作った「develop」と「release」ブランチがあることが分かる。

リモートも含むブランチの確認(branch -a)

リモートも含むブランチの一覧を表示する。
今回作ったものではない、リモートブランチがある別のリポジトリで試すと、以下のようになる。

$ git branch -a
  develop
  feature/xxx
* master
  release
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/feature/xxx
  remotes/origin/master
  remotes/origin/release

「remotes」となっているもの以下がリモートブランチ。

ブランチの移動(checkout)

他のブランチに移動する。

$ git checkout develop
Switched to branch 'develop'

$ git status
On branch develop
nothing to commit, working tree clean

ブランチを作って同時に移動する場合は -b オプションを付けて実行する。

$ git checkout -b feature/some_develop
Switched to a new branch 'feature/some_develop'

$ git status
On branch feature/some_develop
nothing to commit, working tree clean

次回予告

リモートも絡めないと使い物にならないので、そのあたりを実施しようそうしよう。

Djangoを使ってみる (アプリケーションへのルーティング)

前回「Djangoを使ってみる (準備)」の続き。

プロジェクトにアプリケーションへのルーティングを定義する

プロジェクトのフォルダのurls.pyファイルに、アプリケーション「sample_app」へのルーティングを定義する。

下記の「/sample_project/sample_project/urls.py」に、

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

アプリケーション「sample_app」のURLパターンを定義する。

urlpatterns = [
    url(r'^sample_app/', include('sample_app.urls')),
    url(r'^admin/', admin.site.urls),
]

元々あった http://localhost:8000/admin/ に加えて、 http://localhost:8000/sample_app/ の定義を加えた。
そのsample_appのアプリケーション内のURLパターンは引数の sample_app.urls に定義する。

アプリケーションにURLパターンを定義する

プロジェクトの定義でアプリケーションまで誘導できたので、続いて「sample_app/」以下のURLに対してパターンを定義する。 今度はアプリケーション内の「/sample_project/sample_app/urls.py」を新規作成し、下記のパターンを定義する。

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

urlpatternsに正規表現で空を意味する「^$」を記述している。 それにより、「sample_app/」のリクエストに対してviewsで定義したindexをマッピングするようになる。 続いてそのviewsを行う。

アプリケーションにビューを定義する

urls.pyに記述したURLとビューのマッピングを、「/sample_project/sample_app/views.py」で行う。

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world.")

今回はプレーンテキストで「Hello, world.」を出力する。

ここまで記述出来たら http://localhost:8000/sample_app/ にアクセスすると、Hello, world.だけのページが表示される。
f:id:nave_kazu:20171031145612p:plain

Djangoを使ってみる (準備)

Djangoとは

DjangoPythonで実装されたWebアプリケーションフレームワーク

Djangoのインストール

Django本体と関連するライブラリをインストール。

pip install django
pip install djangorestframework
pip install django-filter

データベース接続する際のライブラリもインストール。 今回はMySQL

pip install mysqlclient

プロジェクトとアプリケーションの作成

適当なフォルダでdjango-adminコマンドを実行してプロジェクト「sample_project」を作成する。

django-admin startproject sample_project

作成するとプロジェクト名のフォルダが作られるので、その中でアプリケーション作成のコマンドを実行して、アプリケーション「sample_app」を作成する。

cd sample_project
python manage.py startapp sample_app

ここまで実行すると、以下のようなフォルダとファイルが生成される。

sample_project
  │  manage.py
  │
  ├─sample_app
  │  │  admin.py
  │  │  apps.py
  │  │  models.py
  │  │  tests.py
  │  │  views.py
  │  │  __init__.py
  │  │
  │  └─migrations
  │          __init__.py
  │
  └─sample_project
      │  settings.py
      │  urls.py
      │  wsgi.py
      │  __init__.py
      │
      └─__pycache__
              settings.cpython-36.pyc
              __init__.cpython-36.pyc

ここに設定を追記したりコードを追加したりして行く。

JJUG CCC 2017 Spring に参加してきた

半年ぶりのJJUG CCCに参加してきた。
JJUG CCC 2017 Spring

以下は参加したセッションの所感等々。

JHipsterで学ぶ!Springによるサーバサイド開発手法

「関西を代表するエンジニア」こと、こざけさんのセッションに参加。

関西でのカンファレンス資料を見させていただいてJHipsterに関心を持って一度インストールしたけどうまく動かず挫折したのだが、やっぱり魅力的なのでもう一度試してみようと思う。
ちょうど移籍した先のプロダクトがSpring+Angularで作っているので、マッチするのでは?
後付でJHispterを導入するのは茨の道かもしれないが、その知見を得るためにも試しにやってみたいな。

Vue.js + Spring Bootで楽しくフルスタック開発やってみた

うらがみさんのセッションに参加。

Vue.js + Spring Bootで楽しくフルスタック開発やってみた

Spring+Angularのプロダクトで初めてバインディングが出来るJSライブラリを使ったのだが、バインディングすごい!と感動した矢先に同じくバインディングが出来るVue.jsの話を聴くことが出来た。
AngularとかではなくなぜVueなのだろ?と思ったけど、Java側のビルドに乗せたいとかエコシステムが整ってるとか日本語ドキュメントがあるとか、そういうところがマッチするんだろうなと思った。

データ履歴管理のためのテンポラルデータモデルとReladomoの紹介

ゴールドマン・サックス、現FOLIO伊藤博志さんのセッションに参加。

Reladomoの特徴をわかりやすく説明いただいた。
試してみよう。

このあたりはTISのKawasimaさんも詳しく話てた。

文型さえおさえれば英語を読む力は上がる!

伊藤博志さんと同じFOLIOのよこなさんのセッションに参加。

技術者には必須と思っている英語のお話。
SV、SVC、SVO、SVOO、SVOC。
中学で習ったなぁ。
前に買った「ポレポレ英文読解プロセス50―代々木ゼミ方式」で復讐しよう。

Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めた

阪田浩一さんのセッションに参加。

ご自身が所属しているプロジェクトがSeasar2からSpringへ移行し、今度はマイクロサービスアーキテクチャを採用するに至った経緯や取り組みを話してくれた。

Spark + DeepLearning4J の特長と最新動向(仮)

田中裕一さんのセッションに参加。

DeepLearning・MachineLearningはこれからも成長する分野だと思うので聞きに来たが、JavaとDeepLearningは相性が悪いように思える。
田中さんも言っていたが、Javaだとどうしても冗長になりがちで、これを解決するのはやはりRかPythonじゃないかな。

ハックで生きる:オープンソースで会社を興すには

Jenkins作者の川口耕介のセッションに参加。

オープンソースで作ったもので会社を興す、つまり収益を得るのがどれほど難しいのかを、プロプライエタリの代表のOracleAppleFacebookと比較しながら説明してくれた。
お金儲けだけが目的ならソースの公開をしないプロプライエタリ製品の方が良いが、ソフトウェア業界を良くして行こう・もっと自分たちが作ったもので皆がハッピーになって欲しいという信念で川口さんは活動しているとか。
とはいえ厳しいよなぁとは感じた。

聞けなかったが聞きたかったセッション

聞きたいが体はひとつしかないので、聞けなかったセッションは下記の通り。
公開していただいているスライドだけでも見させていただこう。

ふつうのJavaコーディング

非機能要件とSpring Boot

Javaで実装して学ぶOAuth 2.0!

ナビタイムも導入!!ボットで始発・終電案内から観光ガイドまで 〜 Java でも日本語自然言語処理をカンタンに利用可能 Microsoft LUIS!!

JJUG_CCC_2017_Spring-with-NAVITIME - Docs.com

Javaエンジニアに知って欲しいRDBアンチパターン

Java x Arduinoで始めるIoT / フィジカルコンピューティング

新卒2年目が鍛えられたコードレビュー道場の軌跡

おまけ

懇親会でJava本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまでの著者のひとりの谷本心さんからこの本を購入。
サインももらいました。感謝!読まないと。 f:id:nave_kazu:20170522101240j:plain

AnacondaでPython(データ解析の真似事)

AnacondaでPython環境を作って、データ解析の真似事としてタイタニック号の乗客名簿を読み込んで、乗客の年齢のヒストグラムを表示してみる。

Anacondaのダウンロードとインストール

下記サイトから実行環境に応じたインストーラをダウンロードしインストールする。

Download Anaconda Now! | Continuum

いまPythonデファクトPython 3系だそうなので、自分はWindows版のPython 3系のインストーラをダウンロードしインストールした。

Jupyter Notebookの起動

Pythonを書いたり実行結果を表示したりするREPLにあたるのがJupyter Notebook。 Windows版のAnacondaに付属するJupyter Notebookをスタートメニューから起動すると、ログインユーザのドキュメントフォルダをカレントディレクトリとして起動する。
だが個人的にここにコードを置くのは好きではないので、自分はコードを置くフォルダをカレントディレクトリとして起動するように、コードを置くフォルダに下記のようなバッチファイル(boot-jupyter-notebook.bat)を作ってそこから起動するようにした。

%USERPROFILE%\Anaconda3\Scripts\jupyter-notebook.exe
pause

boot-jupyter-notebook.batを起動すると、コンソールに「The Jupyter Notebook is running at: http://localhost:8888/」と出力され、このアドレスを開いたブラウザが起動する。

f:id:nave_kazu:20170307171954p:plain

Jupyter Notebookの最初に表示するページはカレントディレクトリのファイル一覧なので、boot-jupyter-notebook.batが表示されている。

次は、このJupyter NotebookからPython環境を作ったり、パッケージの管理をしたり、Pythonを書いたり、実行結果を表示したりする。

実行環境を作成する

実行環境はCondaによって管理されている。
Jupyter Notebookの上に「Conda」というタブがあるのでそこをクリックして開くと、デフォルトで構築されている実行環境「root」がある。

f:id:nave_kazu:20170307172005p:plain

それをそのまま使っても良いが、せっかくなので新しい環境を作って、そこで色々試してみる。

右上の「+」をクリックして新しい実行環境を作成する。
今回は名前を「test-env-01」とする。
「Type」はPython 3系が欲しいので、「Python 3」のまま。

f:id:nave_kazu:20170307172011p:plain

「Create」をクリックする。

しばらくすると環境一覧に「test-env-01」が現れる。

f:id:nave_kazu:20170307172017p:plain

続いてその「test-env-01」をクリックして、下のパッケージ一覧をrootからtest-env-01に切り替える。

f:id:nave_kazu:20170307172025p:plain

左側の利用可能パッケージから、データ解析ライブラリの「pandas」を選択し、「→」をクリックしてインストールする。

f:id:nave_kazu:20170307172038p:plain

続いて作図ツールの「matplotlib」を選択し、「→」をクリックしてインストールする。

f:id:nave_kazu:20170307172049p:plain

これで今回使う環境「test-env-01」は完成。

続いて、実行環境での作業内容を記録するNotebookを作成する。
Notebookは作成した実行環境のREPLで、実行環境に対していくつでも作成できる。
Anacondaには複数の実行環境「Conda」がぶら下がり、「Conda」にはその実行環境で実行することを記した複数の「Notebook」がぶら下がる。
Notebookは保存が可能なので、作業途中を保存して翌日それを読み込んで再開、というようなことも出来る。

Notebookは「Files」タブの右側「New」から作成する。
プルダウンの中から先程作成した環境「test-env-01」を選択する。
プルダウンが更新されていないことがあるので、もしプルダウンに「test-env-01」がなければページの再読込を行う。

f:id:nave_kazu:20170307172055p:plain

そうすると新しいブラウザタブが開いて、test-env-01でのREPLが表示される。

f:id:nave_kazu:20170307172103p:plain

データ解析の真似事をする

kaggleからタイタニックの乗客名簿(CSV)をダウンロードする(ダウンロードにはユーザ登録が必要)。

Titanic: Machine Learning from Disaster | Kaggle

タイタニックの乗客名簿「train.csv」をダウンロードする。
ダウンロードしたtrain.csvをカレントディレクトリ(boot-jupyter-notebook.batを実行したディレクトリ)にコピーする。

まずは手始めにダウンロードしたCSVファイルを読み込んで画面に表示してみる。
Cellと呼ばれる「 In [ ] 」の部分に下記コードを貼り付ける。

import pandas as pd

data = pd.read_csv("train.csv")
data.head(3)

この簡単なコードの説明としては、まずはじめにインストールしたパッケージpandasを読み込み、そのpandasを使ってダウンロードしたtrain.csvを読み込み、先頭から3行だけを表示している。
実行はメニューから「Cell -> Run Cells」を選択するか、再生ボタンのような右向き三角のボタンをクリックする。
すると下記のような結果が得られる。

f:id:nave_kazu:20170307172110p:plain

CSVが読めたので、続いてデータ解析の真似事として、乗客の年齢のヒストグラムを表示してみようと思う。

Cellの内容を下記のように変更する。

import pandas as pd
import matplotlib.pyplot as plot

data = pd.read_csv("train.csv")
age = data.Age.fillna(-1)
plot.hist(age, range=(0, 100), bins=101)
plot.show()

今回はpandasに加えて作図ツールのmatplotlibを読み込む。
先程と同様にtrain.csvを読み込んだ後、年齢の列「Age」の欠損箇所に「-1」を埋めている。
train.csvを見てみるとわかるが、年齢の列は空欄が目立つ。空欄があるとヒストグラムを作成する際にエラーとなるため、目印として一旦「-1」を入れる。
ヒストグラム作成のためhistを呼び出し、データageと、データの範囲として0歳~100歳、表示するビンの数として101(つまり1歳ごとに1本)のヒストグラムを作成する。データの範囲を0歳~100歳としているので、欠損データに埋めた-1は無視される。
最後にヒストグラムを表示している。
すると下記のような結果が得られる。

f:id:nave_kazu:20170307175253p:plain

なるほど、乗客の分布としては、20~30代が多かったのね。
最年少は0歳で、最高齢は80歳。なるほどなるほど。

以上、ここまで。

AnacondaでPython(走り書き)

Anacondaとは?

Pythonを使って機械学習やらのプログラムを書く際に使う、Pythonのエコシステムを支えるプラットフォームがAnaconda。
Anacondaを使うとローカルにPythonの実行環境をいくつも構築することが出来る。

機械学習やらのプログラムを書くのに必要なライブラリをAnacondaに作成した実行環境にインストールするのだが、ライブラリの管理もAnaconda上で出来るので、実行環境Aはxxx用、実行環境Bはyyy用というように目的別に実行環境を分けるという使い方が出来る。

Continuum | Home

実行環境の管理は、Jupyter Notebookを使ってGUIで操作が可能。

Jupyter Notebookとは?

Anacondaでの実行環境の管理や、その実行環境でのREPL(Read-eval-print loop:対話型評価環境)を提供するのがJupyter Notebook。
Anacondaをインストールすると一緒に入る。

タイタニック号の生存予測

20世紀初頭に建造された豪華客船、タイタニック号が北大西洋上で氷山に接触して沈没した事故があったが、その時の乗客名簿(名前、性別、年齢、客室)と生存の情報(助かったか、助からなかったか)が残っている。
これを題材に機械学習を試すことが出来る。

Titanic: Machine Learning from Disaster | Kaggle

参考

基本、上で書いた内容は伊藤 直也さんの受け売り。
これを足がかりに学習する・・・。

Rebuild: 171: Psychologically Safe Podcast (naoya)

WEB+DB PRESS Vol.97|技術評論社
Emerging Web Technology研究室
【第22回】Jupyter Notebook+pandasによるPythonデータ解析……伊藤 直也

JavaFXで印刷

年賀状印刷をMS Word+MS Excelの組み合わせでやっていたが、毎年印刷方法に泣かされるので簡単な自前アプリで出来ないものかと、まずは試してみようと思う。
手近な印刷用SDKとしては.NETかJavaがあったが、迷わずJavaで試す。
Javaには古くからあるAWTやJava Print Serviceなどがあるが、それらではなくJavaFX 8から追加になったJavaFX printing APIを使う。

サンプルアプリ

簡単なJavaFXのUIを使っていくつかのAPIを試して行く。

以下のようなサンプルアプリを作った。
f:id:nave_kazu:20170106185642p:plain

上のツールバーAPIを呼び出す各種ボタンと、そのツールバーの右側に部数を入力するエリア。
下の「Log area」にAPIを呼び出した結果をテキストで出力するエリア。
真ん中にアンカーペインを置いて、その中にラベルで「Hello JavaFX Print.」を表示している。
今回はこのアンカーペインを印刷する。

Scene Builderで見たHierarchyは下記の通り。
f:id:nave_kazu:20170106185651p:plain

画面の表示方法やイベント処理方法は今回の範囲外なので適当に。
まずはここまでのソースとFXMLの内容は下記の通り。

まずはFXML。
パスは「/fxml/Main.fxml」

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tools.javafx_printsample.MainController">
   <top>
      <ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
        <items>
          <Button fx:id="defaultPrinterButton" mnemonicParsing="false" onAction="#handleDefaultPrinterButtonAction" text="DefaultPrinter" />
            <Button fx:id="allPrintersButton" mnemonicParsing="false" onAction="#handleAllPrintersButtonAction" text="AllPrinters" />
            <Button fx:id="printButton" mnemonicParsing="false" onAction="#handlePrintButtonAction" text="Print" />
            <Separator orientation="VERTICAL" />
            <Label text="Copies" />
            <TextField fx:id="copiesField" alignment="CENTER_RIGHT" prefWidth="50.0" text="1" />
        </items>
      </ToolBar>
   </top>
   <bottom>
      <TextArea fx:id="logArea" prefHeight="100.0" promptText="Log area" BorderPane.alignment="CENTER" />
   </bottom>
   <center>
      <AnchorPane fx:id="printCanvas" BorderPane.alignment="CENTER">
         <children>
            <Label text="Hello JavaFX Print." />
         </children>
      </AnchorPane>
   </center>
</BorderPane>


続いてmain関数があるApp.java

package tools.javafx_printsample;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
        loader.load();
        Parent root = loader.getRoot();
        Scene scene = new Scene(root);
        primaryStage.setTitle("Print sample");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}


最後に画面のコントローラ。

package tools.javafx_printsample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;

public class MainController {
    @FXML Button defaultPrinterButton;
    @FXML Button allPrintersButton;
    @FXML Button printButton;
    @FXML TextField copiesField;
    @FXML AnchorPane printCanvas;
    @FXML TextArea logArea;

    @FXML
    public void handleDefaultPrinterButtonAction(ActionEvent event) {
    }

    @FXML
    public void handleAllPrintersButtonAction(ActionEvent event) {
    }

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
    }

    private void addLog(String text) {
        logArea.appendText(text+"\n");
    }
}

一番下の addLog メソッドはログエリアに引数の文字列と改行を追記する "手抜き" ユーティリティメソッド。

ここまでは下準備で、以降ではこのアプリに印刷用の処理を入れて行く。

プリンタの選択

印刷をするには印刷を実行させたいプリンタを選択しないとならない。
まずはプリンタ情報の取得を試してみる。

JavaFXの印刷APIは、パッケージで言うと「javafx.print」に入っている。
このパッケージのクラスを見ると、そのものずばりな「Printer」クラスがあり、プリンタの情報はこのクラスを通じて取得できる。

Printerクラスから各種情報を取得してログエリアに出力するメソッドをMainControllerに用意。

    private void outputPrinterInformation(Printer printer) {
        addLog("*** Name:"+printer.getName());

        // ページ・レイアウトの情報を取得して出力
        PageLayout pageLayout = printer.getDefaultPageLayout();
        PageOrientation pageOrientation = pageLayout.getPageOrientation();
        addLog("    PageLayout:");
        addLog("        TopMargin:"+pageLayout.getTopMargin());
        addLog("        LeftMargin:"+pageLayout.getLeftMargin());
        addLog("        RightMargin:"+pageLayout.getRightMargin());
        addLog("        BottomMargin:"+pageLayout.getBottomMargin());
        addLog("        PrintableHeight:"+pageLayout.getPrintableHeight());
        addLog("        PrintableWidth:"+pageLayout.getPrintableWidth());
        addLog("        PageOrientation:"+pageOrientation.name());

        // 用紙の情報を取得して出力
        Paper paper = pageLayout.getPaper();
        addLog("        Paper:");
        addLog("            Name:"+paper.getName());
        addLog("            Height:"+paper.getHeight());
        addLog("            Width:"+paper.getWidth());
    }

デフォルトプリンタの取得

Windowsで言う「通常使うプリンター」を取得するには、Printerクラスのスタティックメソッド getDefaultPrinter() を使う。
このアプリを試したWindows PCのプリンタ設定は下記のようになっている。
f:id:nave_kazu:20170106185657p:plain

3台登録されていて、「通常使うプリンター」にはPDF出力用のCubePDF、あとはWindows7標準のFAXとXPS Writerという構成。

「DefaultPrinter」ボタンをクリックした時のイベントハンドラに対して、getDefaultPrinter() の結果を outputPrinterInformation() に渡す処理を記述する。

    @FXML
    public void handleDefaultPrinterButtonAction(ActionEvent event) {
        Printer defaultPrinter = Printer.getDefaultPrinter();
        outputPrinterInformation(defaultPrinter);
    }


アプリを起動して実行し、「DefaultPrinter」ボタンをクリックした時の実行結果は下記の通り。

*** Name:CubePDF
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:734.0
        PrintableWidth:487.0
        PageOrientation:PORTRAIT
        Paper:
            Name:A4
            Height:842.0
            Width:595.0

プリンタの名前が「CubePDF」で、四方のマージンの情報や、用紙の情報が出力された。
現在「通常使うプリンター」に設定しているのは「CubePDF」なので、意図した結果が得られた。

ちなみに数値の単位は「ポイント」で、これはDTPの用語。
1ポイントは「1/72 インチ」なので、上のA4用紙の高さ「842.0」は
842/72で約「11.69 インチ」。1インチは25.4mmなので、mmにすると「297mm」。
WikipediaによるとA4用紙のサイズは「210mm×297mm」だそうなので、842ポイントは297mmで正しいというのが分かる。

プリンタの検索

現在インストールされているプリンタ全ての情報を取得するには、Printerクラスのスタティックメソッド getAllPrinters() を使う。

「AllPrinters」ボタンをクリックした時のイベントハンドラに対して、getAllPrinters() の結果を outputPrinterInformation() に渡す処理を記述する。

    @FXML
    public void handleAllPrintersButtonAction(ActionEvent event) {
        Collection<Printer> collection = Printer.getAllPrinters();
        collection.stream().forEach(this::outputPrinterInformation);
    }

アプリを起動して実行し、「AllPrinters」ボタンをクリックした時の実行結果は下記の通り。

*** Name:CubePDF
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:734.0
        PrintableWidth:487.0
        PageOrientation:PORTRAIT
        Paper:
            Name:A4
            Height:842.0
            Width:595.0
*** Name:Fax
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:684.0
        PrintableWidth:504.0
        PageOrientation:PORTRAIT
        Paper:
            Name:Letter
            Height:792.0
            Width:612.0
*** Name:Microsoft XPS Document Writer
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:734.0
        PrintableWidth:487.0
        PageOrientation:PORTRAIT
        Paper:
            Name:A4
            Height:842.0
            Width:595.0

CubePDF、Fax、XPS Writerの3つ全ての情報が得られた。

プリンタの情報が得られたのは分かったので、実際に印刷を行なってみる。

出力方法の設定

印刷の単位は「印刷ジョブ」と呼ばれる。
javafx.print」パッケージを見ると、これまたドンピシャな「PrinterJob」クラスがある。
APIドキュメントにはご丁寧にサンプルまである。
印刷の手順は、

  • 印刷ジョブを作る
  • 印刷対象のノードを渡す
  • 印刷を終了する

の3ステップ。

ここで言うノードは、JavaFXのシーングラフのことで、印刷対象となるGUIのツリー階層のルートノードのこと。
ルートノードを渡せば、それに登録されているサブノードも一式印刷される。

今回は印刷対象にラベル「Hello JavaFX Print.」を子に持つアンカーペイン「printCanvas」を渡す。
「Print」ボタンをクリックした時のイベントハンドラに対して、APIドキュメントのサンプルを参考にprintCanvasを印刷する処理を記述する。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();
        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

PrinterJob.createPrinterJob() は「通常使うプリンター」に対してジョブを投入する。
もし対象のプリンタを指定したければプリンタを指定するPrinterJob.createPrinterJob(Printer printer) があるのでそちらを実行するか、setPrinter(Printer printer) メソッドで指定をする。

この状態で実行して「Print」ボタンをクリックすると、CubePDFが起動してPDFの保存先設定が表示され、「変換」をクリックするとPDFが保存される。
f:id:nave_kazu:20170106185703p:plain

保存したPDFを表示すると下記の通り、printCanvas の内容が印刷されていることがわかる。
f:id:nave_kazu:20170106185708p:plain

(仮に)部数の設定

もうちょっといじってみよう。
PrinterJob クラスから取得出来る JobSettings クラスを使用すると、印刷ジョブの設定が出来る。
JobSettings を使って、ジョブの名前やページ範囲や色(モノクロ・カラーの選択)が変更できる。
部数を入力する欄をツールバーに設けているので、その内容を渡して部数設定をしてみる。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();

        JobSettings jobSettings = job.getJobSettings();
        jobSettings.setCopies(Integer.parseInt(copiesField.getText()));

        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

部数を入力するエリアに「2」と入れて実行・・・したが、CubePDFの問題なのか分からないが2部印刷が出来ない。
実際のプリンタに出力してみたら2部出た。
何だろ?
f:id:nave_kazu:20170106185712j:plain

ページ設定ダイアログ

印刷時のページ設定を JobSettings などを通して設定も出来るが、印刷時にユーザに設定させることも出来る。
それが「ページ設定ダイアログ」で、下記のように PrinterJob に対して showPageSetupDialog() メソッドで表示させる。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();

        JobSettings jobSettings = job.getJobSettings();
        jobSettings.setCopies(Integer.parseInt(copiesField.getText()));

        job.showPageSetupDialog(null);

        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

親のWindowを渡してモーダルにすることも出来るが、面倒だったのでnullを渡してモーダレスで試す。
実行すると下記のようにページ設定ダイアログが表示される。
f:id:nave_kazu:20170106185719p:plain

印刷ダイアログ

印刷対象のプリンタを選択する印刷ダイアログを選択させることも出来る。
それが「印刷ダイアログ」で、下記のように PrinterJob に対して showPrintDialog() メソッドで表示させる。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();

        JobSettings jobSettings = job.getJobSettings();
        jobSettings.setCopies(Integer.parseInt(copiesField.getText()));

        job.showPageSetupDialog(null);
        job.showPrintDialog(null);

        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

実行すると下記のように印刷ダイアログが表示される。
f:id:nave_kazu:20170106185723p:plain

まとめ

ページ送りはどうするとかまだ課題はあるが、まずは入り口としてはこのような感じで。