2013年2月9日土曜日

JavaFX Node紹介 No.1

第1回は、ボタン、チェックボックス、チョイスボックス、カラーピッカー、コンボボックスの5つを紹介します。
I introduce 5 nodes, Button, CheckBox, ChoiceBox,ColorPicker, and ComboBox.


まずScene Builderをぽちぽちとやってこのような画面を作りました。
At first, I made such a window using ScenBuilder(SB).



チョイスボックスとコンボボックスには初めから3つの要素が入っていて、クリックするとこんな感じです。コンボボックスは、「プロパティ」タブでeditableにチェックを入れるとテキストフィールドのように入力できるようになります。
CheckBox and Combo box have 3 elements in the initial states.If you check "editable" in the "property" tab on SB, you can edit the field in the ComboBox.



カラーピッカーは大変便利で、リストにない色は専用のウィンドウで作って使うことができます。
ColorPicker is very convenient. If there is not a color you want in the Palette(See the left image), you can make color in the custom colors window.



それぞれのノードの名前は頭文字を小文字にした文字列にしました。
そしてボタンにはこのようにイベントハンドラを登録します。(頭の#を忘れずに)
I registered the event handler for the action event in this way.(Don't forget the # of the head)


On Actionは、マウスやキーによってボタンが押された時に呼ばれます。
buttonAction will be called when the Button pressed.

ちなみに、Buttonの「プロパティ」タブでDefault Buttonにチェックを入れると、そのボタンにフォーカスがなくても押されるようになります。Cancel Buttonの場合はESCキーです。(テキストエリアなどエンターキーが何か役割をもつノードの上では発動しないそうです)
To press the Button from anywhere(without focus), check "default button" in "property" tab.
If you check "cancel button", that Button will be pressed with ESC key.
*On the node which Enter key has a role in,(for example: TextArea) that can't trigger the action.

コントローラにはこのように書いて実行します。
I wrote like this in the controller and excuted.
public class JavaFXApplication1Controller implements Initializable {

    //@FXML //名指ししないので不要
    //private Button button;
    @FXML
    private CheckBox checkBox;
    @FXML
    private ChoiceBox choiceBox;
    @FXML
    private ColorPicker colorPicker;
    @FXML
    private ComboBox comboBox;

    @FXML  //こっちには「#」をつけない!no need #
    public void buttonAction(ActionEvent e) {
        //チェックされていればtrue,そうでなければfalse whether checkBox is checked or not
        boolean selected = checkBox.isSelected();
        System.out.println("checkBox= " + selected);
        //選択されている要素が上から何番目か(0から) get the selected index
        int selection = choiceBox.getSelectionModel().getSelectedIndex();
        //選択されている要素の内容(Objectで返ってくる) get the selected item
        String value = choiceBox.getValue() + "";
        System.out.println("choiceBox num= " + selection + " item= " + value);
        //getItemsで要素のリストが得られ、削除などの操作ができる getItems() gets the lists of the items
        choiceBox.getItems().remove(2);
//        choiceBox.getItems().remove("アイテム2");
//        choiceBox.getItems().clear();
//        choiceBox.getItems().add("要素の追加");
//        choiceBox.getItems().addAll("コンマ区切りで","何個でも","追加");
        //選択されている色を返す get selected color
        Color color = colorPicker.getValue();
        //こうすると.toString()されてrgbaの8桁の16進数になる toString() converts color into String
        System.out.println("colorPicker= " + color);
        //選択されている要素が上から何番目か(0から) get index
        selection = comboBox.getSelectionModel().getSelectedIndex();
        //選択されている要素の内容とは限らない get String from the field in the ComboBox
        value = comboBox.getValue() + "";
        System.out.println("comboBox num= " + selection + " item= " + value);
    }


例えば画面をこのように操作すると、(コンボボックスはアイテム3を選び、表示された文字列の「3」を消しました)
I operated like this for example.(select item3 in the comboBox and deleted "3")


このような出力が得られます。
the output is here.
checkBox= true
choiceBox num= 0 item= アイテム1
colorPicker= 0xb31a1aff
comboBox num= 2 item= アイテム

JavaFX Node紹介 No.0

先日JavaFXのコミュニティに招待され、雑魚からも学ぶことはあるよと言われてやる気になったので、JavaFXのノードを紹介していこうと思います。

一度記事を書いたんですが、先にコントロール系を紹介しようと思って消しました。

ノードって何?

ノードとは(雑魚の直感的定義では)、UIの部品です。ボタンだったりテキストフィールドだったりそれらを収める枠であったり。それらはすべてjavafx.scene.Nodeを多分継承しています。

コントロール系って何?

ノードを大まかな種類に分けると、
  • コントロール系
    ボタンやテキストフィールドなどユーザーが操作するもの
  • コンテナ系
    ノードを綺麗に整列させる機能などを持った枠
  • シェイプ系
    図形
  • チャート系
    折れ線グラフなどの図表
といった感じになります。これは僕が勝手に分けただけというか、ドキュメントもろくに読まずに機能で分けて言っているだけで、完璧な区別はそれこそOracleのドキュメントにあるわけです。javafx.scene.control.Controlなどと混同しないためにコントロール系と呼ぶことにしています。

また、TabPaneのような、枠だけどギミックがあるからControlだとか、LabelのようにユーザーはコントロールしないけどControl、ということもあるので、きっちりした区分けをする気もありません。




2013年2月8日金曜日

最初のプログラムを解剖

前回の記事でHello,Worldしたプログラムの構造を見て行きたいと思います。

まずはメインのクラスです。(以降、あまり登場しません。)
package javafxapplication1;

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

public class JavaFXApplication1 extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("JavaFXApplication1.fxml"));
        
        Scene scene = new Scene(root);
        
        stage.setScene(scene);
        stage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}

起動時に最初に呼び出されるのがstart(Stage stage)のようです。通常はmainは呼ばれません。
fxmlからシーングラフ(コンポーネントの構造)を読み込んでその根っこをSceneとしてそれをstageにsetして表示という流れのようですね。
で、そのfxmlとやらを見てみます。

    
        



うわ、投稿画面で変なボタンが生成されてしまいました。大丈夫かこれ…
とにかくこの意味不明で無駄に横長いソースがシーングラフを表したもののようです。でもSceneBuilderが自動で書いてくれるのでもう開くこともないでしょう。

そしてUI操作のほとんどを担う部分がこちら。(以降、コントローラと呼びます。正式名称は、謎です)
package javafxapplication1;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class JavaFXApplication1Controller implements Initializable {
    
    @FXML
    private Label label;
    
    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }    
}

22行めのinitializeというメソッドが、このクラスの中では最初に呼ばれます。ここで各コンポーネントの操作をしても構いませんが、ここでコンポーネントのサイズや座標を取得しようとするとうまくいかないと思います。

13行目のLabelの宣言ですが、これはScenBuilderで配置したLabelと同じ名前にします(この例でも、SceneBuilderを開くと同名のLabelがあります。文字数0で見つけにくいですが)
※この宣言はすべてのコンポーネントに対し行う必要はありません。現にこのプログラムではボタンがあり、しかもイベントハンドラもありますが宣言されていません。名指しする必要があるときだけで構いません。

16行目のhandleButtonActionメソッドは、ボタンに登録されたイベントハンドラです。SceneBuilderでボタンの「コード」タブを見ると一番上の「On Action」に#handleButtonActionとあります。(なぜかこっちは「#」が必要です。理由は、謎です)

FXML上のコンポーネントやイベントハンドラに関してここで書く場合は直前の行に「@FXML」というおまじないが必要です。

このおまじないの書き忘れや、変数名/メソッド名の.fxml-.java間での食い違いが使い始めによく遭遇するエラーの原因だそうです。いつものJavaのように書いてる途中で警告してくれませんからね。

次はこのプログラムをベースにいろいろ遊ぶ予定です。

こんにちわ、JavaFX

さて、これからはJavaFXでいこうというわけですが…

  • まだ成長途中
  • 日本語解説が少ない
  • Javaのコード*1中心でやりたいのにFXスクリプト*2ばっかり、もはや検索妨害
と、JavaFXは2.xになっても多くのモヤモヤ感を抱えているので、雑魚グラマの分際で恐縮ですがメモと言うよりも初心者向け解説的立場で、詰みそうなところを書いていく感じでしばらく行こうと思います。こっちの勉強にもなるしね。

いかにも最初から細かく開設するような書き方をしてしまいましたがその辺は他の神様方にお願いします。

Swing経験者なら扱いはかなり楽だと思います。コンポーネントはみんなNodeというクラスを継承してるので親子関係(シーングラフっていうみたい)がとてもスッキリしています。また操作も統一感のある印象です。(ただ色とか外観の設定は専用のメソッドがないこともしばしば…styleというCSSチックな書式での設定が必要です)





ここでは開発環境の構築は省略します。必要なのは新しいJavaとNetBeans、ScenBuilderです。

プロジェクト作成

ググるとEclipseでの解説が多いので、NetBeansでの様子を。
まぁ普通にプロジェクト作成です。

JavaFXというカテゴリがない場合は、Javaのバージョンが古いと思います。更新しましょう。
ここではJavaFXアプリケーションまたはJavaFX FXMLアプリケーションを選択します。
  • JavaFXアプリケーション
    JavaのコードだけでUIの構造つまりシーングラフを記述します。
  • JavaFX FXMLアプリケーション
    シーングラフの記述をFXMLというxml形式っぽい書式で行います。ですがもちろん.javaの中でJavaのコードで書き足すこともできます。また、UIの設計をScenBuilderというソフトで視覚的に行えるので便利です。筆者はこっちしか使ったことがありません。プログラミングとデザインを切り離して開発できるとかで画期的らしいです。
他の2つは、謎です。

次の画面でプロジェクト名などを設定します。好きな名前にしましょう。
FXMLの名前はSampleのままでも結構ですが複数のプロジェクトを開くと混乱するので同じ名前をつけるといいと思います。

Hello, World

プロジェクトを作り終えると~~.fxml、~~.java、~~Controller.javaの3つができているはずです。主に~~Controller.javaをいじります。このときすでに実行できるようにコードが書かれているので実行してみましょう。

実行すると、「Device "Intel(R) Graphics Media Accelerator HD" (\\.\DISPLAY2) initialization failed : 
WARNING: bad driver version detected, device disabled. Please update your driver to at least version 8.15.10.2302」みたいな警告文が出る人がいるかもしれません。多分マルチモニタとかのせいだと思いますが、謎です。無視しましょう。


ひとまず今回はここまでです。


*1:xmlでもJavascriptでもないJavaの文法で書かれたコード。正式名称は、謎です。
*2:Javascriptっぽい文法で書かれたコード、完全に謎です。

    2013年2月6日水曜日

    引越しました


    こちらに引越しました(引越し元: 雑魚グラマのメモ帳)


    これからもJavaメインですがSwingはオワコンなのでJavaFXでいきたいと思います


    //Syntax Highlighterを使って
    //こういうかっこいいソース表示をしていきたいとおもいます
    

    キーバインドを設定し直す



    JTableにはもともとキーストロークによる動作が決まっています(Oracle Technology Network for Java Developers)
    ですがエンターを押した時表を更新したいとか、タブキーを押した時に右に行くのではなく下に行きたいとかこだわりが出てくることがあります
    そこで、JTableのキーが押されたイベントから処理してもいいのですがこれだとデフォルトの動作は生きているのでそれも考えなくてはなりません
    昨日までそれで頑張ってたんですがうまいやり方を見つけました(ネタ元:JTableのEnterキーイベントのアクションをオーバーライドしたい - Life is Really Short, Have Your Life!!)



    private void tableAddKeyBind() {
    Action action = new AbstractAction() {

    @Override
    public void actionPerformed(ActionEvent e) {
    //対応した動作
    }
    };
    table.getActionMap().put("NEXT_ACTION", action);
    table.getInputMap().put(
    KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH, 0, false),
    "NEXT_ACTION");

    action = new AbstractAction() {

    @Override
    public void actionPerformed(ActionEvent e) {
    //対応した動作
    }
    };
    table.getActionMap().put("PREVIEW_ACTION", action);
    table.getInputMap().put(
    KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, 0, false),
    "PREVIEW_ACTION");
    table.getInputMap().put(
    KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0, false),
    "PREVIEW_ACTION");
    }



    そしてtableAddKeyBind();をtableのカスタムコードとかinitComponents()の後らへんとか適当なところに書けばOKです
    "NEXT_ACTION"とかいうのはアクションの名前なのでなんでもいいです
    //対応した動作 の部分にはそのキーが押された時の処理を書きます
    PREVIEW_ACTIONのように複数のキーに同じ動作を割り当てることも出来ます
    Ctrl+Cのようなショートカットキーは一つ目のリンク先をみる限り少ないので、キーイベントの処理部分でやるのが楽そうです
    それについては追々





    2012年9月24日月曜日

    Propertiesクラスが散らかってるのが気に入らない人のためのクラス



    設定ファイル要るな→Propertiesクラス便利→うわ順番めちゃくちゃやん
    →作ろう



    import java.io.*;
    import java.util.ArrayList;

    /**
    *
    * @author standstonecraft
    */
    public class MyProperties {

    private ArrayList<String> keyList;
    private ArrayList<String> valList;

    public MyProperties() {
    }

    public void load(FileInputStream fis) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    loadP(br);
    }

    public void load(FileInputStream fis, String encoding) throws UnsupportedEncodingException, IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(fis, encoding));
    loadP(br);
    }

    private void loadP(BufferedReader br) throws IOException {
    String s;
    keyList = new ArrayList<>();
    valList = new ArrayList<>();
    while ((s = br.readLine()) != null) {
    if (s.charAt(0) != '/') {
    String ss[] = s.split("=", 2);
    keyList.add(ss[0]);
    valList.add(ss[1]);
    }
    }
    br.close();
    }

    public String getProperty(String key, String value) {
    int i = keyList.indexOf(key);
    if (i > -1) {
    return valList.get(i);
    }
    return value;
    }

    public void setProperty(String key, String value) {
    int i = keyList.indexOf(key);
    if (i > -1) {
    keyList.remove(i);
    valList.remove(i);
    }
    keyList.add(key);
    valList.add(value);
    }

    public void setComment(String comment) {
    keyList.add(comment);
    valList.add(null);
    }

    public void store(FileOutputStream fos) throws IOException {
    String s;
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
    storeP(bw);
    }

    public void store(FileOutputStream fos, String encoding) throws UnsupportedEncodingException, IOException {
    String s;
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos, encoding));
    storeP(bw);
    }

    public void store(FileOutputStream fos, String comment, String encoding) throws UnsupportedEncodingException, IOException {
    keyList.add(0, comment);
    valList.add(0, null);
    String s;
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos, encoding));
    storeP(bw);
    }

    private void storeP(BufferedWriter bw) throws IOException {
    for (int i = 0; i < keyList.size(); i++) {
    String v=valList.get(i);
    if (v==null) {
    bw.write("//");
    bw.write(keyList.get(i));
    }else{
    bw.write(keyList.get(i)+"="+v);
    }
    bw.newLine();
    }
    bw.close();
    }
    }



    長々と書きました
    これを使うと設定ファイルが格納した順になります
    すでにPropertiesを使ってる場合Propertiesの頭にMyをつければ使えるようにしたかったのですが、
    文字コード指定とかポリモルフィズムの関係で妥協してます
    loadやstoreのあたりの引数にご注意ください

    他に注意事項としては、
    ・本家と同じようにキーと値は=で分けること(ファイルを直接いじる場合の注意)
    ・行区切りにすること
    ・独自機能としてsetComment(String)すると文頭に//がついたコメントとして格納されること
    ・コメントはキーと同じように格納した順番、場所に格納されること
    ・キーはそれぞれ異なる名前でなければならないこと(被ってる場合上にある方が使われます)
    ・getPropertyで返ってくるのはもちろんString
    ・XMLのメソッドとかキーリストを出すメソッドはないです

    多分動きます
    雑魚グラマなので冗長なところとか無駄なところとか要らないところとか間違ってるところとかあると思いますがご愛嬌です