2017年9月24日日曜日

テキストエディタをJavaで作成する計画3

さてさて、今回はテキストエディタの「新規作成」機能と「閉じる」機能を実装します。

よく考えたら新規作成にもいろんなパターンがありますね。
①新しいウィンドウが開く
②新しいタブが作成される
③作業中のファイルを閉じてからそのまま開く

個人的には①が好きなので、この方法で実装します。



新しいウィンドウが開く処理はダイアログの表示でも使えるはずなので、どちらにしても作れるようになっておきたい部分です。

結論から言うと、JavaFXで新しくウィンドウを作るのはかなり大変な作業でした。
これはStageクラスの使い方がある程度理解が必要なことと、[閉じる]ボタンを押したときに全ウィンドウ閉じられたり、別のウィンドウが閉じられたりするなど、色々予定外のバグがでました。主にスレッド関係が原因のようでした。

また、ネットの情報は断片的でテキストエディタを作るとなると自分で考えないと無理でした。
と言うことで、ここから先については僕のオリジナルな部分が多いので、設計の良し悪しは不明です。動作確認では一応、違和感なく動作しました。

【ProcessManagerクラス】このクラスのmainメソッドが初めに実行されます。残念なことにApplicationクラスのサブクラスである必要がありました。
public class ProcessManager extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        createTextEditer();
    }

    public static void main(String[] args) {
        launch(args);
    }

    /**
     * 新しいテキストエディタを作成します。
     */
    public void createTextEditer() {
        try {
            new TextEditer01().start(new Stage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 引数で指定されたウィンドウを非表示にします。
     * @param scene 終了対象の画面
     */
    public void terminateTextEditer(Scene scene) {
        try {
            scene.getWindow().hide();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

【TextEditer01クラス】ProcessManagerクラスから呼ばれます。普通はこのクラスのプロパティにSceneがありますが、イベントからSceneをうまく取得できなかったので、代替案としてSceneをFXMLDocumentControllerクラスに持っていき、FXMLに紐づいているコントローラの情報を保持しておきます。
public class TextEditer01 extends Application {

    private FXMLDocumentController controller;
    private ProcessManager pm = new ProcessManager();

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
        Parent root = fxmlLoader.load();
     
        controller = (FXMLDocumentController) fxmlLoader.getController();
        controller.setScene(new Scene(root));
     
        stage.setScene(controller.getScene());
        stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            @Override
            public void handle(WindowEvent event) {
                event.consume();
                try {
                    pm.terminateTextEditer(controller.getScene());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        stage.show();
    }
}


【FXMLDocumentControllerクラス】Sceneをプロパティにするとイベントとうまく紐付けできました。
public class FXMLDocumentController implements Initializable {

    private ProcessManager pm;
    private Scene scene;

    /**
     * 新しいウィンドウを表示します。
     * @param event
     */
    @FXML
    private void newAction(ActionEvent event) {
        pm.createTextEditer();
    }

    /**
     * MenuItem[閉じる] イベントが発生したウィンドウを非表示にします。
     * @param event
     */
    @FXML
    void closeAction(ActionEvent event) {
        pm.terminateTextEditer(getScene());
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        pm = new ProcessManager();
    }

    public Scene getScene() {
        return scene;
    }

    public void setScene(Scene scene) {
        this.scene = scene;
    }


こんな感じで「新規作成」と「閉じる」が実装できました。
次は「開く」を追加します。

2017年9月16日土曜日

テキストエディタをJavaで作成する計画2

前回に続きまして、じっくり一晩考えてみてテキストエディタの機能を決めました。

メニュー
├── ファイル
   ├── 新規作成
   ├── 開く
   ├── 上書き保存
   ├── 名前を付けて保存
   ├── 印刷
   └── 閉じる
├── 編集
   ├── 元に戻す
   ├── やり直す
   ├── すべて選択
   ├── 切り取り
   ├── コピー
   ├── 貼り付け
   ├── 検索
   └── 置換
├── 表示
   ├── 右端で折り返す
   └── フォント
└── ヘルプ
    └── バージョン情報

もう十分ですね。これくらいならなんとかなるでしょう。というかほとんどメモ帳と同じになってしまいました。
右クリックの機能は一旦保留にします。

問題なのは印刷ですね。作ったことないのでどう実装するのか分かりません。
あとフォントは設定が残るようにしないといけないので、設定ファイルがいりそうです。

まだまだ仕様がふわふわしてますけど、SceneBuilderを使ってとりあえず画面だけ作ってみます。



画面だけなら5分でできました!
もう完成したような気持ちになるけど、保存とかできませんからね。
青空文庫から「吾輩は猫である」をそのまま貼ってみますと、動作が結構重くなりました。
そして長い横スクロールもあってすごく使いにくいです。
TextAreaにwrapText="true" を付け加えると治りました。

<TextArea prefHeight="200.0" prefWidth="200.0" wrapText="true" BorderPane.alignment="CENTER" />



この部分はメニューの[右端で折り返す]の機能なので、またコードを少しいじらないといけないですね。
ちなみに動作はさらに重たくなった感じしますが、どうしようもないですね。

次はメニューで何か選択された時に呼び出されるメソッドの名前を定義します。

メニュー
├── ファイル
   ├── 新規作成・・・・・・private void newAction(ActionEvent event)
   ├── 開く・・・・・・・・private void openAction(ActionEvent event)
   ├── 上書き保存・・・・・private void saveAction(ActionEvent event)
   ├── 名前を付けて保存・・private void saveAsAction(ActionEvent event)
   ├── 印刷・・・・・・・・private void printAction(ActionEvent event)
   └── 閉じる・・・・・・・private void closeAction(ActionEvent event)
├── 編集
   ├── 元に戻す・・・・・・private void undoAction(ActionEvent event)
   ├── やり直す・・・・・・private void redoAction(ActionEvent event)
   ├── すべて選択・・・・・private void selectAllAction(ActionEvent event)
   ├── 切り取り・・・・・・private void cutAction(ActionEvent event)
   ├── コピー・・・・・・・private void copyAction(ActionEvent event)
   ├── 貼り付け・・・・・・private void pasteAction(ActionEvent event)
   ├── 検索・・・・・・・・private void findAction(ActionEvent event)
   └── 置換・・・・・・・・private void replaceAction(ActionEvent event)
├── 表示
   ├── 右端で折り返す・・・private void wrapAction(ActionEvent event)
   └── フォント・・・・・・private void fontAction(ActionEvent event)
└── ヘルプ

    └── バージョン情報・・・private void aboutAction(ActionEvent event)

こんな感じでしょうか。
あと下の方に、行数とか処理内容とか表示されたら良さそうなので、Labelも画面に追加しました。




例えばフォントを選択すると下に「フォント」と出てきます。
「右端で折り返す」はデフォルトがtrueのCheckMenuItemにしてあります。

【FXMLDocumentController.java】
public class FXMLDocumentController implements Initializable {
 
    @FXML
    private Label bottomLabel;
 
    @FXML
    private void newAction(ActionEvent event) {
        bottomLabel.setText("新規作成");
    }
    ・
    ・
    ・
}

【FXMLDocument.fxml】
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="textediter01.FXMLDocumentController">
    <top>
        <MenuBar BorderPane.alignment="CENTER">
            <menus>
                <Menu mnemonicParsing="false" text="ファイル">
                    <items>
                        <MenuItem mnemonicParsing="false" onAction="#newAction" text="新規作成" />
                        <MenuItem mnemonicParsing="false" onAction="#openAction" text="開く" />
                        <MenuItem mnemonicParsing="false" onAction="#saveAction" text="上書き保存" />
                        <MenuItem mnemonicParsing="false" onAction="#saveAsAction" text="名前を付けて保存" />
                        <MenuItem mnemonicParsing="false" onAction="#printAction" text="印刷" />
                        <MenuItem mnemonicParsing="false" onAction="#closeAction" text="閉じる" />
                    </items>
                </Menu>
                <Menu mnemonicParsing="false" text="編集">
                    <items>
                        <MenuItem mnemonicParsing="false" onAction="#undoAction" text="元に戻す" />
                        <MenuItem mnemonicParsing="false" onAction="#redoAction" text="やり直す" />
                        <MenuItem mnemonicParsing="false" onAction="#selectAllAction" text="すべて選択" />
                        <MenuItem mnemonicParsing="false" onAction="#cutAction" text="切り取り" />
                        <MenuItem mnemonicParsing="false" onAction="#copyAction" text="コピー" />
                        <MenuItem mnemonicParsing="false" onAction="#pasteAction" text="貼り付け" />
                        <MenuItem mnemonicParsing="false" onAction="#findAction" text="検索" />
                        <MenuItem mnemonicParsing="false" onAction="#replaceAction" text="置換" />
                    </items>
                </Menu>
                <Menu mnemonicParsing="false" text="表示">
                    <items>
                        <CheckMenuItem mnemonicParsing="false" onAction="#wrapAction" selected="true" text="右端で折り返す" />
                        <MenuItem mnemonicParsing="false" onAction="#fontAction" text="フォント" />
                    </items>
                </Menu>
                <Menu mnemonicParsing="false" text="ヘルプ">
                    <items>
                        <MenuItem mnemonicParsing="false" onAction="#aboutAction" text="バージョン情報" />
                    </items>
                </Menu>
            </menus>
        </MenuBar>
    </top>
    <center>
        <TextArea prefHeight="200.0" prefWidth="200.0" wrapText="false" BorderPane.alignment="CENTER" />
    </center>
   <bottom>
      <Label fx:id="bottomLabel" BorderPane.alignment="CENTER_LEFT" />
   </bottom>
</BorderPane>

次回に続く。。。かも



2017年9月9日土曜日

テキストエディタをJavaで作成する計画1

やっぱりプログラムは書かなきゃ覚えられません。設計の良し悪しも失敗しないと理解できません。

ということで、練習でテキストエディタを作ってみたいと思います。ファイルの取り込みと編集や保存、多少の設定など基本的なことが練習できて良い経験になる予定です。

さて仕様について考え中ですが、
Webアプリケーションにする案も一瞬ありましたが、誰も得しそうにないのでやめました。
普通にデスクトップから起動してWindows標準装備の「メモ帳」に少し機能が追加されてるぐらいのエディタになるといいなと思いました。

ということで、Windowsの「メモ帳」が僕にとってのベンチマークになるので、仕様を確認してみます。メニューを見るとだいたい機能が分かりそうなので、メニューを確認してみました。


【メモ帳】
メニュー
├── ファイル(F)
│ ├── 新規(N)
│ ├── 開く(O)
│ ├── 上書き保存(S)
│ ├── 名前を付けて保存(A)
│ ├── ページ設定
│ ├── 印刷(P)
│ └── メモ帳の終了(X)
├── 編集(E)
│ ├── 元に戻す(U)
│ ├── 切り取り(T)
│ ├── コピー(C)
│ ├── 貼り付け(P)
│ ├── 削除(L)
│ ├── 検索(F)
│ ├── 次を検索(N)
│ ├── 置換(R)
│ ├── 行へ移動(G)
│ ├── すべて選択(A)
│ └── 日付と時刻(D)
├── 書式(O)
│ ├── 右端で折り返す(W)
│ └── フォント(F)...
├── 表示
│ └── ステータスバー(S)
└── ヘルプ
  ├── ヘルプの表示(H)
  └── バージョン情報(A)


こうして見ると思ったより多いですね。
そういえば、右クリックというやつもありましたね。

「右クリック」
元に戻す(U)
切り取り(T)
コピー(C)
貼り付け(P)
削除(D)
すべて選択(A)
右から左に読む(R)
Unicode 制御文字の表示(S)
Unicode 制御文字の挿入(I)
IMEを開く(O)
再変換(R)


これも意外に多い気がしました。日曜大工なのでテキパキしないと、かなり時間かかりそうです。
ついでにMac標準装備の「メモ」も機能を確認してみました。

メニュー
├── メモ
│ ├── メモについて
│ ├── 環境設定…
│ ├── アカウント…
│ ├── ロックされたメモを全て閉じる
│ ├── サービス▶︎
│ ├── メモを隠す
│ ├── ほかを隠す
│ ├── すべて表示
│ └── メモを終了
├── ファイル
│ ├── 新規メモ
│ ├── 新規メモ
│ ├── 環境設定…
│ ├── 共有 ▶︎
│ ├── ”メモ”に読み込む… ▶︎
│ ├── PDFとして書き出す…
│ ├── このメモをロック
│ └── プリント…
├── 編集
│ ├── 取り消す
│ ├── やり直す
│ ├── カット
│ ├── コピー
│ ├── ペースト
│ ├── ペーストしてスタイルを合わせる
│ ├── ペーストしてスタイルを保持
│ ├── 削除
│ ├── 全てを選択
│ ├── ファイルを添付…
│ ├── リンクを追加…
│ ├── 添付ファイルを名称変更…
│ ├── 検索 ▶︎
│ ├── スペルと文法 ▶︎
│ ├── 自動置換 ▶︎
│ ├── 変換 ▶︎
│ ├── スピーチ ▶︎
│ ├── 音声入力を開始…
│ └── 絵文字と記号
├── 表示
│ ├── サイドバーを隠す
│ ├── サムネール
│ ├── 目次
│ ├── ハイライトとメモ
│ ├── ブックマーク
│ ├── コンタクトシート
│ ├── 連続スクロール
│ ├── 単一ページ
│ ├── 2ページ
│ ├── プロファイルを使ってソフトプルール
│ ├── イメージ背景を表示
│ ├── 実際のサイズ
│ ├── ウインドウに合わせる
│ ├── 拡大
│ ├── 縮小
│ ├── 選択部分に合わせて拡大
│ ├── マークアップツールバーを表示
│ ├── ツールバーを隠す
│ ├── ツールバーをカスタマイズ…
│ ├── スライドショー
│ └── フルスクリーンにする
├── 移動
│ ├── 上
│ ├── 下
│ ├── 前の項目
│ ├── 次の項目
│ ├── ページ移動…
│ ├── 戻る
│ └── 進む
├── ツール
│ ├── インスペックタを表示
│ ├── 拡大鏡を表示
│ ├── カラーを調整
│ ├── サイズを調整
│ ├── テキスト選択
│ ├── 長方形で選択
│ ├── 注釈 ▶︎
│ ├── ブックマークを追加
│ ├── 反時計回りに回転
│ ├── 時計回りに回転
│ ├── 水平方向に反転
│ ├── 垂直方向に反転
│ ├── 切り取り
│ ├── プロファイルを割り当てる…
│ └── 位置情報を表示
├── ウインドウ
│ ├── しまう
│ ├── 拡大/縮小
│ └── すべてを手前に移動
└── ヘルプ
  ├── 検索
  └── プレビューヘルプ


「右クリック」
’’を調べる
Googleで検索
カット
コピー
ペースト
段階スタイル ▶︎
チェックリスト
チェック済みにする
共有 ▶︎
フォント▶︎
スペルと文法 ▶︎
自動置換 ▶︎
変換 ▶︎
スピーチ ▶︎
レイアウトの方向 ▶︎
スポークントラックトしてiTunesに追加
テキストを半角に変換



機能としてはメモどころじゃありません。しかも「▶︎」や「...」がついている項目はさらに深い階層に機能があるので、これは大変です。


心が折れそうなので、今日はここまでです。

2017年9月3日日曜日

JavaFXで画面遷移してみた件

JavaFXで画面遷移できるように実装してみました。Webアプリケーションでも画面遷移は勉強し始めの頃は大変困りました。そして今でも困ってます。

今回のアプリの仕様は3画面ほど作りまして、[前へ]ボタンと[次へ]ボタンで双方向に遷移できます。シンプルすぎて役に立たないアプリが完成しました。



     ↑        ↓



     ↑        ↓




さて、プログラムの構成はこんな感じです。
JDKは古いバージョンだとJavaFXは使えないので注意です。




今回はFXMLを使っていまして、ビューとコントローラは1対1になるようにしてます。

【Mainクラス】

public class Main extends Application {

    private static Stage stage;

    @Override
    public void start(Stage stage) throws Exception {
        Main.stage = stage;
        changeView("View0101.fxml");
        Main.stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    public void changeView(String fxml) {
        try {
            stage.setScene(new Scene(FXMLLoader.load(getClass().getResource(fxml))));
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

画面遷移するにはStageに紐づいているFXMLファイルを切り替えてあげればいいだけでした。コントローラから画面遷移できるようにしたいので、ChangeViewメソッドを作成し、引数には遷移対象のFXMLのファイル名としました。




【Controller0101〜Controller0103クラス】の抜粋
    
    @FXML
    private void nextButtonAction(ActionEvent event) {
        new Main().changeView("View0102.fxml");
    }

ChangeViewの呼び出しはこんな感じになりました。

改善の余地がありそうですが、一旦完成です。