2013年1月27日日曜日

TreeItemを開くときと閉じるときのイベント処理

TreeViewの中のTreeItemが開いたときと閉じたときのイベント処理を書いてみた.
まずは,EventHandlerインターフェースを継承して,イベントハンドラを作る.
開くときのイベントは,TreeItemにbranchExpandedEventというのがあったので,
それかどうかを判定してみる.閉じるときのイベントはbranchCollapsedEventらしい.

import java.io.File;
import javafx.event.EventHandler;
import javafx.scene.control.TreeItem;

public class FileTreeModificationEventHandler
    implements EventHandler<
        TreeItem.TreeModificationEvent<File>>
{
    public void handle(
        TreeItem.TreeModificationEvent<File>
        event
    )
    {
        if(event.getEventType() ==
            TreeItem.<File>branchExpandedEvent())
        {
            System.out.println("expanded");
        }else if(event.getEventType() ==
            TreeItem.<File>branchCollapsedEvent())
        {
            System.out.println("collapsed");
        }
    }
}

これを,前に作ったFileTreeItemのコンストラクタで登録させる.

    public FileTreeItem(final File f){
        super(f);

        // 開くときのイベントを登録
        addEventHandler(
            TreeItem.<File>branchExpandedEvent(),
            new FileTreeModificationEventHandler());
        // 閉じるときのイベントを登録
        addEventHandler(
            TreeItem.<File>branchCollapsedEvent(),
            new FileTreeModificationEventHandler());
    }

これで,開いたり閉じたりする度にexpandedやcollapsedが表示される.
ただ,これだと木の奥の方に行くほど何度もexpandedが表示されるので,
イベントを一回処理したら終わりにしてみる.

    public void handle(
        TreeItem.TreeModificationEvent<File>
        event
    )
    {
        if(event.getEventType() ==
            TreeItem.<File>branchExpandedEvent())
        {
            System.out.println("expanded");
            event.consume();
        }else if(event.getEventType() ==
            TreeItem.<File>branchCollapsedEvent())
        {
            System.out.println("collapsed");
            event.consume();
        }
    }

Eventのconsumeを呼び出すと,その後のイベントが伝わっていくのを抑えられる.
で,抑えてみた結果を実行すると,木が開かなくなった.expandedの方のconsumeを
消すと,今度は開くけど閉じることが出来なくなった.どうもイベントは木の根から
呼び出されていくらしい.イベントの発生源から親に向かって呼び出されていくと思っていた.
イベントのハンドラは根にだけ登録して,getSource()で処理する感じになるのかな.

で,これ書くときに一番引っかかったのがジェネリクスに対応した
staticメソッドを呼び出すときは,クラス名. の後に型名を書くということ.
クラス名に型名を指定していてずっと引っかかっていた.
Javaのジェネリクスちゃんと勉強しないとなぁ.

2013年1月25日金曜日

JavaFX TreeViewとTreeItem

JavaFXのTreeViewを使ってみた.ファイルツリー的なものを作ろうと思ったら,TreeItemのドキュメントにすでに書いてあった.
で,それを自分なりに整理して書いてみた.

まずは,ファイルツリーの要素を表すクラスを,TreeItem<File>を継承して作る.

import java.io.File;
import javafx.collections.ObservableList;
import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem;

// ファイルツリーの根や分岐や葉を表すクラス
// 根は値を持たないものとする
public class FileTreeItem extends TreeItem<File> {
    public FileTreeItem(){
        this(null);
    }

    public FileTreeItem(final File file){
        // 要素の値を設定
        super(file);
    }

    // 木の要素が葉かどうか(末端かどうか)を返す
    // 要素が持つ値は根の場合はnullで,
    // それ以外では通常のFileオブジェクトになる
    @Override
    public boolean isLeaf(){
        File file = getValue();
        return file != null && file.isFile();
    }

    @Override
    public ObservableList<TreeItem<File>> getChildren(){
        // 既に一度子要素を作っている場合はそのキャッシュを利用する
        ObservableList<TreeItem<File>> children = super.getChildren();
        if(!children.isEmpty())
            return children;
 
        // 根にファイルを追加する前に通る
        File file = getValue();
        if(file == null)
            return children;

        // ファイルの場合
        if(!file.isDirectory()){
            return FXCollections.emptyObservableList();
        }

        // ディレクトリの場合
        File [] files = file.listFiles();
        for(File f : files){
            children.add(new FileTreeItem(f));
        }
        return children;
    }
}

根の持つ値をnullにしたのは,TreeViewが当たり前ではあるが根として1つしか要素を持てないため.
MacやLinuxでは/だけがルートなので良いが,Windowsの場合CドライブやDドライブと言った
複数のルートが存在するので,何も値を持たない根を用意し,その下にルートを置くようにしたいから.

表示部分は次のように.

import java.io.File;
import javafx.application.Application;
import javafx.scene.control.TreeView;
import javafx.scene.Scene;
import javafx.stage.Stage;

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

    public void start(Stage primaryStage){
        // ルートファイルのリストを取得
        File [] rootFiles = File.listRoots();
        FileTreeItem root = new FileTreeItem();
        // 根の要素に各ルートファイルを追加していく
        for(File f : rootFiles){
            root.getChildren().add(new FileTreeItem(f));
        }

        // 根の要素を渡してファイルツリーのビューを作成
        TreeView treeView = new TreeView(root);
        // 無駄なnullのルートは表示しないようにする
        treeView.setShowRoot(false);
        // TreeViewをシーンに追加して表示する
        Scene scene = new Scene(treeView);
        primaryStage.setWidth(640);
        primaryStage.setHeight(480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Macでは動作確認したけど,これでWindowsでも上手く各種ドライブが
表示されるかどうかは未確認.

Javaはジェネリクスが出た頃くらいから触っていなかったのでドキュメントを読むのも
一苦労だ・・・.コンパイラに型が間違ってるとか言われまくった.

2013年1月19日土曜日

JavaFX入門

Pythonをやろうと思っていたはずが,JavaFXの勉強を始めていました.
今年も興味がうつろいやすそうです.「うつろわざるもの」になりたい.

JavaFXは,AWTやSwingのようなJavaのGUIフレームワークです.
ちょっとマルチプラットフォームでGUIのあるツールを作りたくなったため,触ってみました.

もっともシンプルなコード(Simple.java)はこんな感じ.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;

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

    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(new Group()));
        // Macでは幅と高さを指定しておかないとウィンドウが見えない
        // primaryStage.setWidth(100);
        // primaryStage.setHeight(100);
        primaryStage.show();
    }
}

で,これをコンパイルするためにはJavaFXのjarをクラスパスに追加する必要があります.
JavaFXはJava SE 7からは付いてきているようなので,最新版のJava SEを入れれば入っています.

Windows
Javaのインストールフォルダ\jre\lib\jfxrt.jar

Mac
`/usr/libexec/java_home`/jre/lib/jfxrt.jar

このパスをクラスパスに追加してJavaを実行すれば,ウィンドウが表示されます.

JavaFXでは,WPFのXAMLのようにFXMLというXMLでGUIを記述できます.
さらに,GUIをWYSIWYGエディタで作成できるJavaFX Scene Builderも提供されています.
上の例をFXML(Simple.fxml)で書くとこんな感じ.

<?xml version = "1.0"?>
<?import javafx.scene.Scene?>
<?import javafx.scene.Group?>
<Scene width = "100" height = "100">
  <Group />
</Scene>

上のコードと比較して何となく気付くかもしれませんが,オブジェクト間の関係と
XMLの入れ子関係が関連しているようです.
import文はXMLのProcessing Instructionとして指定するようです.

このFXMLのファイルを先ほどのクラスと同じ階層に置いて,
このファイルを使ってウィンドウを表示するコードは次のような感じに.

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

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

    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(
            (Scene)FXMLoader.load(
                getClass().getResource("Simple.fxml")));
        // Macでは幅と高さを指定しておかないとウィンドウが見えない
        // primaryStage.setWidth(100);
        // primaryStage.setHeight(100);
        primaryStage.show();
    }
}

FXMLLoaderというのが提供されているわけです.
getClass().getResource("Simple.fxml")はこのクラスのある場所から指定した
リソース(この場合ファイル)のパスを取得してくれるメソッドのようです.

他にもCSSで見た目を変えられる機能もあるようなので,つい色々試したくなってきます.
目的のツールを作ることを忘れてしまいそう.

2013年1月12日土曜日

プログラミングとデザイン(設計ではない)

 UIデザイナーの方におすすめされて,ノンデザイナーズデザインブック,という
 本を買って読みました. 


 この本は,デザインの素人でもいくつかの原則を意識すればデザインは良くなる
 んですよ,ということを解説しています.その原則は,素人の自分でも確かに意
 識すれば分かる,という内容でした.その原則は次の4つです.

  • 近接
    • 関連のあるものは近くにまとめる
  • 整列
    • 右揃えや左揃えなど全体で揃える
  • コントラスト
    • 違いをはっきりとさせる
  • 反復
    • 全体で同じことを繰り返すことで,類似の情報を見つけやすくする

 相当ざっくりとまとめました.で,これを読んでいてふと思ったことがあり
 ます.読みやすいコードって,この原則が結構当てはまるのではないかと.

  • 近接
    • 関連のあるコードは近くにあると見つけやすい
  • 整列
    • インデントがある,揃っているコードは読みやすい
  • コントラスト
    • ちょっと思いつきません
  • 反復
    • コメントのスタイルが全体で同じ形を繰り返している

 こちらも結構ざくっ,としてますが,何が言いたいかと言うと,プログラマが
 何かしらのソースコードが美しいと言ってもプログラマ以外には理解されない,
 と良く言われますが,実は理解されるんじゃなかろうか,と思ったわけです.
 アーティストから見たデザイン的に良い文章(つまりコード)は,プログラマに
 とっても読みやすいコードで,プログラマにとって読みやすいコードとはアー
 ティスト的にデザインの良いコードなのでは無いか,と.

 後は,何かを以てコントラストが表せれば良いのですが.

2013年1月7日月曜日

2013年の抱負

新年明けましておめでとうございます.

基本的に熱しやすく冷めやすい性格なので,ブログも継続しない.というわけで,今年の目標は「継続」にしようと思います.

あと,巳年なのでPythonをやってみようかと.
単に言語としてのPythonを習得するだけでも良いのだけれど,ゲームプログラマとしては,Blenderとかのプラグインとか何か作れるようになると良いなぁ,と.

というわけで,Pythonの学習ネタでしばらくブログを書けたらな,と思う年明けでした.

部屋の片付けも継続しないとな・・・