Javaの基礎知識(実践編)

【Javaの基礎知識】JavaFXで作るシンプルなエディタアプリ|保存・開く・編集

JavaFXを使って、シンプルな「エディタ」を作成する方法を解説します。本記事では、基本的なUI設計からイベント処理の実装まで、実践的なステップを詳しく説明します。初心者でも理解しやすいように、コード付きで解説しているので、ぜひ参考にしてください。

Javaの基礎知識

Java の基礎知識(実践編)
📌 現場で使える力を。アプリ制作で学ぶ実践型トレーニング
└─【Javaの基礎知識(実践編)】現場で使えるWeb・DB・GUI開発の実践構築
  ├─【Javaの基礎知識】Javaとは? Javaの基本概要をわかりやすく解説!
  ├─【Javaの基礎知識】Eclipse+TomcatでWeb開発環境を構築!
  ├─【Javaの基礎知識】Todoアプリで学ぶ!ServletとJSPの基礎とWebアプリ開発
  ├─【Javaの基礎知識】DockerでMySQL環境構築|Javaから接続する手順
  ├─【Javaの基礎知識】Todoアプリで学ぶ!JDBCを使ったDB連携と実装手順
  ├─【Javaの基礎知識】Todoアプリで学ぶ!データ更新処理とコネクションプールの使い方
  ├─【Javaの基礎知識】設定地獄はもう嫌!シンプルな共通ログ出力クラスを作ってみた
  ├─【Javaの基礎知識】JavaFXでGUIアプリ作成入門!基本から実践まで!
  ├─【Javaの基礎知識】JavaFXで作るシンプルなエディタアプリ|保存・開く・編集
  ├─【Javaの基礎知識】Spring Boot環境構築&プロジェクトセットアップ完全ガイド!
  ├─【Javaの基礎知識】Spring BootでシンプルなMVC構造のWebアプリを作る
  ├─【Javaの基礎知識】Spring Boot × MySQL!DB接続からCRUD実装まで解説!| 
  ├─【Javaの基礎知識】Spring Bootアプリの実行環境とデプロイ手順
  |
  └─共通DBアクセスクラス
    📌 SQL記述を最小化、業務ロジックに集中できる共通基盤
    ├─ORMにはうんざり!第1回:シンプルなJava DBアクセスクラスを考えてみた
    ├─ORMにはうんざり!第2回:共通DBアクセスクラスでSQLを直感的に操作するJava設計
    ├─ORMにはうんざり!第3回:JavaでDB接続の最適化と共通プールの構築
    ├─ORMにはうんざり!第4回:Java共通ログ出力とsystem.xml設定の構成を解説
    ├─ORMにはうんざり!第5回:例外の闇を断つ 堅牢なJavaエラーハンドリングとログ設計
    └─ORMにはうんざり!第6回:Java共通DBアクセスクラスの実用例で脱フレームワーク

エディタアプリの概要

本アプリは、開発者やライターを対象としたシンプルなエディタです。シンプルな操作性を保ちつつ、フォントやテーマ機能を提供します。

本アプリの目的

本アプリは、サンプルとして作成したエディタであるため、最低限の機能のみ実装することとします。基本的なテキスト編集機能にフォーカスしています。

ユーザーごとのカスタマイズや拡張機能については、今後のアップデートにて追加される可能性がありますが、現時点では最小限の機能を提供しています。

エディタとして実現する機能一覧

本アプリで提供する主要な機能を以下の表にまとめます。これらの機能により、快適なテキスト編集環境を実現します。

機能概要
テキスト入力・編集機能基本的なテキストの入力や編集が可能
ファイルの読み込み・保存外部ファイルの読み込みおよび編集後の保存
フォント・テーマの変更フォントの種類やサイズ、エディタのテーマを変更可能
自動保存機能一定間隔で自動的に編集中のファイルを保存

操作性とUIの要件

  • ダークモード対応
  • リサイズ可能なレイアウト
  • ステータスバーの表示(テーマ変更やフォントサイズ指定など)

エディタアプリの全体設計書の構成と設計要素

本アプリは、シンプルなテキスト編集機能だけを備えた最小限のサンプルエディタです。拡張性やメンテナンス性は考慮していないため、機能追加や修正は予定していません。

設計は極力シンプルにまとめ、最小限のUI構成と機能のみを実装しています。アーキテクチャやデータ管理については特にこだわらず、基本的な動作のみを実現しています。

このアプリは、ユーザー設定やシンタックスハイライトなどの高度な機能には対応しておらず、シンプルなテキスト編集ができるだけのものです。 

設計方針

具体的には、MVC(Model-View-Controller)アーキテクチャを採用し、UI(View)、制御(Controller)、データ管理(Model)の役割を明確に分離することで、コードの見通しを良くし、機能の追加や修正を容易にします。また、ユーザーの好みに応じた設定変更を可能にするため、カスタマイズしやすい設定管理を組み込み、フォントやテーマの変更、ショートカットキーのカスタマイズなどを柔軟に対応できるようにします。

これらの設計方針により、開発のしやすさと拡張性を両立させ、実用的かつ持続的に改善可能なエディタアプリを構築します。

設計方針

  1. シンプルかつ拡張性の高い設計
  2. MVC(Model-View-Controller)アーキテクチャ
  3. カスタマイズしやすい設定管理

アプリの主要コンポーネント

エディタアプリの開発においては、機能の拡張や保守性を考慮し、アーキテクチャを適切に設計することが重要です。本アプリでは、View(UI)、Controller(制御)、Model(データ管理)の3つの主要コンポーネントに分割し、それぞれの役割を明確にします。

View(UI) では、ユーザーが直感的に操作できるインターフェースを構築し、エディタエリア、ステータスバーなどの主要なUI要素を配置します。Controller(制御部) では、ユーザーの操作を受け取り、ファイルの読み書きなどの処理を担います。Model(データ管理) では、現在開いているファイルの情報や、ユーザー設定の保存・読み込みを管理します。

このように各コンポーネントを明確に分割することで、コードの保守性を高め、機能の追加や改修をスムーズに行える設計を実現します。

View(UI)

  • メニューバー
  • エディタエリア(テキスト入力部分)
  • ステータスバー

Controller(制御部)

  • ユーザー操作の管理(ボタンやショートカット)
  • ファイルの読み書き制御

Model(データ管理)

  • 現在開いているファイルの情報
  • 設定データの保存・読み込み

ファイル管理とユーザー設定

エディタアプリにおいて、ファイルの読み込みや保存は最も基本的でありながら、柔軟な設計が求められる重要な機能の一つです。ユーザーがスムーズにファイルを操作できるよう、本アプリでは直感的なUIと効率的なファイル入出力処理を両立させた設計を行います。

具体的には、ファイルの選択や保存を簡単に行えるように FileChooser を利用したファイルダイアログ を実装し、ユーザーが自由にファイルを開閉できるようにします。また、ファイルの読み書き処理には BufferedWriter / BufferedReader を使用し、大容量のテキストファイルでも高速に処理できる仕組みを採用します。

  • ファイル選択ダイアログの実装
  • FileChooser を使ったファイルの入出力
  • BufferedWriter / BufferedReader<によるファイル管理

エディタアプリをより使いやすくするためには、ユーザーごとにカスタマイズ可能な設定機能が不可欠です。本アプリでは、フォントの種類やサイズ、テーマ(ダークモード対応)に対応する仕組みを設計します。

エディタアプリの使用する全クラス一覧

本アプリでは、各クラスを役割ごとに分割し、必要に応じて実行構成の設定を適用する設計を採用しています。以下の表では、各クラスの役割と実行構成の設定の有無を示します。なお、実行構成の設定が「あり」となるのは、main() メソッドを持つクラスのみです。

クラス名役割実行構成の設定
TextEditorJavaFXアプリケーションのエントリーポイントあり
EditorViewエディタのメインUI(レイアウト管理)なし
AutoSaveManager定期的な自動保存を制御なし
MenuControllerメニューバーの操作制御なし
FileManagerファイルの読み込み・保存処理なし
EditorControllerエディタのUI操作を制御(フォント、テーマなど)なし
EditorSettingsエディタの設定(フォント、テーマ)の保存と管理なし

エディタアプリの実装手順

本アプリの実装手順を順を追って説明します。各機能を段階的に構築しながら、エディタとして最低限必要な機能を追加していきます。まずはメインアプリの作成から始め、UIの構築、テキスト編集機能、ファイル管理、設定管理などを実装していきます。開発環境のセットアップについては、前回の記事を参照してください。

開発環境のセットアップに関しては、下記の記事を参照してください。

メインアプリの実装

エディタアプリの起点となるメインアプリを実装します。JavaFXの Application クラスを継承し、start() メソッド内でウィンドウの初期設定を行います。このクラスが全体のエントリーポイントとなり、UIのロードや制御の基盤を担います。

TextEditor.java の実装

JavaFXアプリのエントリーポイントとなるMainApp.javaの実装を行います。

package application;

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

/**
 * TextEditorクラスは、シンプルなエディタアプリケーションを作成するためのエントリーポイントです。
 * JavaFXを使用して、基本的なウィンドウとシーンを表示します。
 */
public class TextEditor extends Application {

    /**
     * アプリケーションのエントリーポイント。シンプルなウィンドウとシーンを作成し、表示します。
     * @param primaryStage アプリケーションのメインステージ
     */
    @Override
    public void start(Stage primaryStage) {
        // ウィンドウのタイトルを設定
        primaryStage.setTitle("エディタアプリ");

        // シンプルな空のシーンを設定
        // Groupを使って、コンテンツを配置するための親ノードを作成
        primaryStage.setScene(new Scene(new Group(), 300, 200));

        // アプリケーションのウィンドウを表示
        primaryStage.show();
    }

    /**
     * アプリケーションの開始メソッド。JavaFXアプリケーションを起動します。
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        // JavaFXアプリケーションの起動
        launch(args);
    }
}

UI(エディタ画面)の構築

エディタアプリのユーザーインターフェースを作成します。直感的な操作性を考慮し、シンプルかつ拡張性の高いレイアウトを設計します。メインの編集エリアに加え、メニューバーやツールバーを配置し、快適な編集環境を提供します。

EditorView.java の実装

エディタのメインUIを構築し、レイアウトを設定します。JavaFXのレイアウトマネージャーを活用し、メインウィンドウの構成を定義します。

package application;

import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;

/**
 * EditorViewクラスは、エディタのユーザーインターフェース(UI)部分を定義します。
 * ここでは、エディタのレイアウトとテキストエリアの作成を行います。
 */
public class EditorView {
    private TextArea textArea;  // テキスト入力エリア
    private BorderPane layout;  // レイアウトのための親ノード

    /**
     * EditorViewのコンストラクタ。テキストエリアとレイアウトを初期化します。
     * テキストエリアは`BorderPane`の中央に配置されます。
     */
    public EditorView() {
        // `TextArea` を作成。ユーザーがテキストを入力するための領域。
        textArea = new TextArea();

        // `BorderPane` レイアウトを作成。中央に`TextArea`を配置します。
        layout = new BorderPane();
        layout.setCenter(textArea);  // `TextArea` を中央に配置
    }

    /**
     * 現在のレイアウト(`BorderPane`)を取得します。
     * @return layout `BorderPane` レイアウト
     */
    public BorderPane getLayout() {
        return layout;
    }

    /**
     * テキストエリアを取得します。テキストの編集を行うために使用します。
     * @return textArea テキスト入力エリア
     */
    public TextArea getTextArea() {
        return textArea;
    }
}

TextEditor.javaの修正

EditorView.java の実装を反映させるために「MainApp.java」へ下記コードを追加します。

// EditorViewのインスタンスを作成し、ウィンドウにセット
new EditorView(primaryStage);

package application;

import javafx.application.Application;
import javafx.stage.Stage;

/**
 * TextEditorクラスは、JavaFXを使用してエディタアプリケーションを起動するエントリーポイントです。
 * メインウィンドウの作成と、エディタビューを初期化します。
 */
public class TextEditor extends Application {

    /**
     * アプリケーションのメインメソッド。アプリケーションのウィンドウ(Stage)を作成し、
     * エディタビューを表示します。
     * @param primaryStage アプリケーションのメインステージ
     */
    @Override
    public void start(Stage primaryStage) {
        // ウィンドウのタイトルを設定
        primaryStage.setTitle("エディタアプリ");

        // EditorView を作成し、primaryStage(ウィンドウ)を渡す
        // EditorViewは、エディタのUIレイアウトやインターフェースを担当
        new EditorView(primaryStage);
    }

    /**
     * アプリケーションを起動します。JavaFXアプリケーションを開始するためのエントリーポイントです。
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        // JavaFXアプリケーションを起動
        launch(args);
    }
}
エディタのサイズが広がりました。

メニューバーの作成

エディタの操作性を向上させるために、メニューバーを実装します。メニューバーでは、ファイルの開閉や保存などの基本操作を提供ます。

MenuController.java の実装

エディタアプリに「メニューバー」を追加し、基本的な操作(開く・保存など)を管理できるようにします。

package application;

import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;

/**
 * MenuControllerクラスは、エディタのメニューを制御するクラスです。
 * 「ファイル」メニューを作成し、その中に「開く」と「保存」のメニューアイテムを追加します。
 */
public class MenuController {
    private MenuBar menuBar;   // メニューのバー
    private MenuItem openItem; // 「開く」メニューアイテム
    private MenuItem saveItem; // 「保存」メニューアイテム

    /**
     * MenuControllerのコンストラクタ。
     * メニューの初期化処理を行います。
     */
    public MenuController() {
        initialize();
    }

    /**
     * メニューを初期化するメソッド。
     * 「ファイル」メニューを作成し、その中に「開く」および「保存」のメニューアイテムを追加します。
     */
    private void initialize() {
        // メニューのバーを作成
        menuBar = new MenuBar();

        // 「ファイル」メニューを作成
        Menu fileMenu = new Menu("ファイル");

        // 「開く」「保存」メニューアイテムを作成
        openItem = new MenuItem("開く");
        saveItem = new MenuItem("保存");

        // メニューアイテムを「ファイル」メニューに追加
        fileMenu.getItems().addAll(openItem, saveItem);

        // メニューをメニューバーに追加
        menuBar.getMenus().add(fileMenu);
    }

    /**
     * メニューのバー(MenuBar)を取得します。
     * @return menuBar メニューのバー
     */
    public MenuBar getMenuBar() {
        return menuBar;
    }

    /**
     * 「開く」メニューアイテムを取得します。
     * @return openItem 「開く」メニューアイテム
     */
    public MenuItem getOpenItem() {
        return openItem;
    }

    /**
     * 「保存」メニューアイテムを取得します。
     * @return saveItem 「保存」メニューアイテム
     */
    public MenuItem getSaveItem() {
        return saveItem;
    }
}

TextEditor.javaの修正

MenuController.java及び、ToolbarController.java の実装を反映させるために「TextEditor.java」へ下記コードを追加します。

// メニューバーをトップに追加
MenuController menuController = new MenuController();
root.setTop(menuController.getMenuBar());

// ツールバーをメニューバーの下に配置
ToolbarController toolbarController = new ToolbarController();
root.setCenter(toolbarController.getToolBar());

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 * TextEditorクラスは、エディタアプリケーションのエントリーポイントであり、アプリケーションのウィンドウ(Stage)を作成します。
 * メニュー、ツールバー、エディタビューを組み合わせて、基本的なエディタのUIを構築します。
 */
public class TextEditor extends Application {
    private EditorView editorView;          // エディタビュー
    private MenuController menuController;  // メニューを制御するコントローラー
    private ToolbarController toolbarController; // ツールバーを制御するコントローラー

    /**
     * アプリケーションのメインメソッド。ウィンドウを作成し、エディタのレイアウトを構築して表示します。
     * @param primaryStage アプリケーションのメインステージ
     */
    @Override
    public void start(Stage primaryStage) {
        // アプリケーションのタイトルを設定
        primaryStage.setTitle("エディタアプリ");

        // EditorView を作成(エディタのレイアウトを担当)
        editorView = new EditorView();

        // MenuController と ToolbarController を作成
        // メニューとツールバーを管理するコントローラーをインスタンス化
        menuController = new MenuController();
        toolbarController = new ToolbarController();

        // レイアウトを設定(BorderPane を使用)
        BorderPane root = new BorderPane();

        // メニューバーを上部に配置
        root.setTop(menuController.getMenuBar());

        // エディタビューを中央に配置
        root.setCenter(editorView.getLayout());

        // ツールバーを下部に配置
        root.setBottom(toolbarController.getToolBar());

        // シーンを作成し、ウィンドウに設定
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();  // ウィンドウを表示
    }

    /**
     * JavaFXアプリケーションを起動します。
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        launch(args); // アプリケーションの起動
    }
}
メニューバーが表示されます。

ファイル管理の実装

エディタアプリでは、作成したテキストをファイルに保存し、後で再編集できるようにする必要があります。ここでは、ファイルの読み込み・保存処理と、作業内容を定期的に自動保存する機能を実装します。

FileManager.java の実装

ファイルの読み込みと保存を管理するFileManager.javaを実装します。このクラスは、ユーザーが手動でファイルを開いたり、編集内容を保存できるようにします。

package application;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javafx.stage.FileChooser;
import javafx.stage.Stage;

/**
 * FileManagerクラスは、ファイルの読み書きを管理するクラスです。
 * ユーザーがファイルを開いたり、保存したりする機能を提供します。
 */
public class FileManager {
    private Stage stage;  // アプリケーションのウィンドウ

    /**
     * コンストラクタ。`FileManager`オブジェクトを作成します。
     * @param stage ファイル選択ダイアログを表示するために必要なステージ
     */
    public FileManager(Stage stage) {
        this.stage = stage;
    }

    /**
     * ユーザーが選択したファイルを開き、その内容を読み込んで返します。
     * @return ファイルの内容(テキスト)
     */
    public String openFile() {
        // ファイル選択ダイアログを表示
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("ファイルを開く");
        File file = fileChooser.showOpenDialog(stage);

        if (file != null) {
            // ファイルが選択された場合、その内容を読み込む
            try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                StringBuilder content = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                return content.toString();
            } catch (IOException e) {
                // エラーが発生した場合、スタックトレースを出力
                e.printStackTrace();
            }
        }
        return ""; // ファイルが選択されなかった場合、空文字を返す
    }

    /**
     * ユーザーが選択した場所にファイルを保存します。
     * @param content 保存するテキスト内容
     */
    public void saveFile(String content) {
        // ファイル選択ダイアログを表示
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("ファイルを保存");
        File file = fileChooser.showSaveDialog(stage);

        if (file != null) {
            // ファイルが選択された場合、その内容を保存する
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
                writer.write(content);
            } catch (IOException e) {
                // エラーが発生した場合、スタックトレースを出力
                e.printStackTrace();
            }
        }
    }
}

AutoSaveManager.javaの実装

エディタの編集内容を定期的に保存するため、自動保存機能を追加します。一定時間ごとに現在のテキストをバックアップファイルとして保存し、万が一のデータ消失を防ぎます。

package application;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javafx.scene.control.TextArea;

/**
 * AutoSaveManagerクラスは、テキストエリアの内容を定期的に自動保存する機能を提供します。
 * 変更があった場合、指定したファイルに自動で内容を保存し、ユーザーのデータ損失を防ぎます。
 */
public class AutoSaveManager {
    private String backupFilePath = "backup.txt";  // 自動保存するバックアップファイルのパス
    private Timer timer;                           // タイマーオブジェクト
    private TextArea textArea;                     // テキストエリア
    private String lastSavedContent = "";          // 最後に保存した内容

    /**
     * コンストラクタ。AutoSaveManagerを初期化し、指定されたTextAreaに対して自動保存を開始します。
     * @param textArea 自動保存対象となるテキストエリア
     */
    public AutoSaveManager(TextArea textArea) {
        this.textArea = textArea;
        timer = new Timer(true);  // バックグラウンドで実行されるタイマー
        startAutoSave();  // 自動保存を開始
    }

    /**
     * 自動保存を開始するメソッド。指定した間隔でテキストエリアの内容を保存します。
     * 変更があった場合にのみ保存が行われます。
     */
    private void startAutoSave() {
        // 5秒ごとに自動保存を実行するタスクをスケジュール
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // テキストエリアの現在の内容を取得
                String content = textArea.getText();

                // 最後に保存した内容と異なる場合のみ保存
                if (!content.equals(lastSavedContent)) {
                    try (BufferedWriter writer = new BufferedWriter(new FileWriter(backupFilePath))) {
                        writer.write(content);  // バックアップファイルに内容を保存
                        lastSavedContent = content;  // 最後に保存した内容を更新
                    } catch (IOException e) {
                        // ファイル書き込みエラーが発生した場合、スタックトレースを表示
                        e.printStackTrace();
                    }
                }
            }
        }, 0, 5000); // 0ms後に開始し、5000ms(5秒)ごとに繰り返し実行
    }

    /**
     * 自動保存を停止するメソッド。タイマーをキャンセルして自動保存を停止します。
     */
    public void stopAutoSave() {
        timer.cancel();  // タイマーをキャンセルして自動保存を停止
    }
}

TextEditor.javaの修正

FileManager.java(開く・保存)と AutoSaveManager.java(自動保存)をTextEditor.javaへ組み込みます

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 * TextEditorクラスは、エディタアプリケーションのエントリーポイントです。
 * メインウィンドウを作成し、エディタビュー、メニュー、ツールバー、ファイル操作、自動保存などの機能を統合します。
 */
public class TextEditor extends Application {
    private EditorView editorView;              // エディタのUIを管理するビュー
    private MenuController menuController;      // メニューの操作を管理
    private ToolbarController toolbarController; // ツールバーの操作を管理
    private EditorController editorController;  // エディタのロジックを制御
    private FileManager fileManager;            // ファイルの読み書きを管理
    private AutoSaveManager autoSaveManager;    // 自動保存を管理

    /**
     * アプリケーションのメインメソッド。ウィンドウを作成し、レイアウトを構築して表示します。
     * @param primaryStage アプリケーションのメインステージ
     */
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("エディタアプリ");

        // エディタビューを作成
        editorView = new EditorView();

        // エディタコントローラを作成し、TextAreaを管理
        editorController = new EditorController(editorView.getTextArea());

        // メニューコントローラを作成
        menuController = new MenuController();

        // ツールバーコントローラを作成
        toolbarController = new ToolbarController(editorController);

        // ファイルマネージャを作成(ウィンドウを渡す)
        fileManager = new FileManager(primaryStage);

        // 自動保存マネージャを作成(TextAreaを渡す)
        autoSaveManager = new AutoSaveManager(editorView.getTextArea());

        // メニューの「開く」と「保存」項目にアクションを設定
        menuController.getOpenItem().setOnAction(event -> {
            // ファイルを開き、内容をTextAreaにセット
            String content = fileManager.openFile();
            if (!content.isEmpty()) {
                editorView.getTextArea().setText(content);
            }
        });

        menuController.getSaveItem().setOnAction(event -> {
            // 現在のテキスト内容をファイルに保存
            fileManager.saveFile(editorView.getTextArea().getText());
        });

        // レイアウトを設定
        BorderPane root = new BorderPane();
        root.setTop(menuController.getMenuBar());         // メニューバーを上部に配置
        root.setCenter(editorView.getLayout());           // エディタビューを中央に配置
        root.setBottom(toolbarController.getToolBar());   // ツールバーを下部に配置

        // シーンを作成し、ステージに設定
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();  // アプリケーションを表示
    }

    /**
     * アプリケーションのエントリーポイント。
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        launch(args); // アプリケーションを起動
    }
}
任意の文字を入力してメニューから保存。
手動でファイルをプロジェクト内へ保存
変更があった場合、5秒ごとに自動で保存

フォント・テーマの変更機能の実装

エディタのフォントやテーマを変更することで、ユーザーがより快適に作業できる環境を提供できます。このセクションでは、フォントの種類やサイズの変更、ダークテーマ・ライトテーマの切り替え方法について解説します。

エディタのフォントを変更することで、ユーザーが見やすい環境をカスタマイズできます。特に、プログラミング向けのエディタでは、可読性の高いフォントの選択が重要です。

クラスの配置場所

この辺りからクラスの数が増え始めるので現在までのクラス配置イメージを記載しておきます。

project-root/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   ├── controllers/
│   │   │   │   ├── TextEditor.java
│   │   │   │   ├── EditorView.java
│   │   │   │   ├── MenuController.java
│   │   │   │   ├── FileManager.java
│   │   │   │   ├── EditorController.java
│   │   │   │   ├── EditorSettings.java
│   │   ├── resources/
│   │   │   ├── dark-theme.css
│   │   │   ├── editor.fxml

フォントの種類とサイズ変更機能の実装

JavaFXでは、 TextArea のフォントを変更することで、ユーザーが好みのフォントやサイズを設定できます。以下のコードを適用することで、フォントをリアルタイムに変更できます。

import javafx.scene.text.Font;
textArea.setFont(Font.font("Courier New", 14));

エディタのテーマ編集仕様

エディタのテーマを下記へまとめます。下記のテーブルは、ダークテーマとライトテーマの色分けに関する情報を簡潔に表示するためのものです。

コンポーネント背景色文字色備考
ダークテーマ(全体背景)#333333whiteダークグレー背景、白文字
テキストエリア#222222white背景暗いグレー、白文字
選択範囲#555555white背景色、選択テキストの色
プレースホルダー文字#888888noneプレースホルダーの文字色
ComboBox, Spinner#444444white背景色、テキストの色(アイテム)
ComboBoxリストアイテム#444444whiteリストアイテム背景、文字色
Spinnerテキストフィールド#555555whiteスピナーの文字色
ボタン#555555whiteボタン背景、白文字
設定パネル#333333white設定パネル背景、白文字
ダークテーマの詳細設定nonewhite各コンポーネントの文字色を白に設定
ライトテーマ(全体背景)#ffffff#000000白背景、黒文字
テキストエリア#ffffff#000000白背景、黒文字
選択範囲#cccccc#000000選択範囲の背景色、選択テキストの色
プレースホルダー文字#666666noneプレースホルダー文字の色
ComboBox, Spinner#ffffff#000000背景色、テキストの色
ComboBoxリストアイテム#ffffff#000000リストアイテム背景、文字色
Spinnerテキストフィールド#ffffff#000000スピナーの文字色
ボタン#eeeeee#000000ボタン背景、黒文字
テキストエリア(文字色)#ffffff#000000黒文字
ComboBoxアイテム(マウスオーバー)#4CAF50whiteアイテムにマウスオーバーしたとき
Spinnerアイテム(マウスオーバー)#4CAF50whiteスピナーにマウスオーバーしたとき
ダークテーマ時のSpinner#555555white背景色を暗く、文字色を白に
ダークテーマ時のSpinnerボタン#555555whiteボタン背景色、ボタン内の文字色を白に

ダークテーマとライトテーマの切り替え

ダークテーマやライトテーマを切り替えることで、目の負担を軽減し、作業環境を改善できます。JavaFXでは、CSSのスタイルを変更することで簡単にテーマを切り替えられます。

以下のコードを適用することで、ボタンを押すとテーマが切り替わるようになります。

root.getStylesheets().add(getClass().getResource("dark-theme.css").toExternalForm());

ダークテーマのスタイルは、以下のCSSで設定できます。

.root {
  -fx-background-color: #333;
  -fx-text-fill: #fff;
}
.text-area {
  -fx-background-color: #222;
  -fx-text-fill: #ddd;
}

dark-theme.cssの実装

本プロジェクトのフォルダ構成は以下の通りです。
CSSファイルは src/main/resources/ に保存されています。

/* ダークテーマ */
.dark-mode {
    -fx-background-color: #333333; /* ダークグレーの背景 */
    -fx-text-fill: white; /* 白文字 */
}

/* テキストエリアのスタイル */
.dark-mode .text-area {
    -fx-background-color: #222222; /* 背景を暗いグレーに */
    -fx-control-inner-background: #222222; /* TextArea の内部背景 */
    -fx-text-fill: white; /* テキスト色は白 */
    -fx-highlight-fill: #555555; /* 選択範囲の背景色 */
    -fx-highlight-text-fill: white; /* 選択テキストの色 */
    -fx-prompt-text-fill: #888888; /* プレースホルダー文字の色 */
}

/* ComboBox, Spinner のテキストカラー設定 */
.dark-mode .combo-box, .dark-mode .spinner {
    -fx-background-color: #444444; /* 背景を暗いグレーに */
    -fx-text-fill: white; /* テキストの色を白に */
    -fx-prompt-text-fill: white; /* プレースホルダー文字色も白 */
}

/* ComboBox のリストアイテム */
.dark-mode .combo-box .list-cell {
    -fx-text-fill: white; /* リストアイテムのテキストカラー */
    -fx-background-color: #444444; /* 背景色 */
}

/* Spinner のテキストフィールド */
.dark-mode .spinner .text-field {
    -fx-text-fill: white; /* スピナーの文字色を白に */
}

/* ボタンのスタイル */
.dark-mode .button {
    -fx-background-color: #555555; /* ボタンの背景 */
    -fx-text-fill: white; /* ボタンのテキスト色 */
    -fx-border-color: #777777; /* ボーダー色 */
}

/* ダークモードの設定パネル (設定項目) */
#settings-box {
    -fx-background-color: #333333; /* ダークモードの背景 */
}

/* ダークテーマでの詳細設定 */
.dark-mode .combo-box, .dark-mode .spinner, .dark-mode .button {
    -fx-text-fill: white; /* 各コンポーネントの文字色を白に設定 */
}

/* Lightテーマに適用される設定 */
.root {
    -fx-background-color: #ffffff; /* ライトモードの背景 */
    -fx-text-fill: black; /* テキスト色は黒 */
}

.text-area {
    -fx-background-color: #ffffff; /* 白背景 */
    -fx-control-inner-background: #ffffff; /* TextArea の背景 */
    -fx-text-fill: black; /* 黒文字 */
    -fx-highlight-fill: #cccccc; /* 選択範囲の背景色 */
    -fx-highlight-text-fill: black; /* 選択テキストの色 */
    -fx-prompt-text-fill: #666666; /* プレースホルダー文字色 */
}

.combo-box, .spinner {
    -fx-background-color: #ffffff; /* ライトモードの背景 */
    -fx-text-fill: black; /* テキストの色 */
    -fx-prompt-text-fill: black; /* プレースホルダー文字色 */
}

.combo-box .list-cell {
    -fx-text-fill: black; /* リスト内のテキストカラー */
    -fx-background-color: #ffffff; /* リストアイテムの背景 */
}

.spinner .text-field {
    -fx-text-fill: black; /* スピナーの文字色 */
}

.button {
    -fx-background-color: #eeeeee; /* ボタン背景 */
    -fx-text-fill: black; /* ボタン文字色 */
    -fx-border-color: #cccccc; /* ボーダー色 */
}

/* テキストエリアの文字色 */
.text-area {
    -fx-text-fill: black; /* 黒文字 */
}

/* ComboBoxアイテムのマウスオーバー時の色を変更 */
.combo-box .list-cell:hover {
    -fx-background-color: #4CAF50; /* 緑色の背景 */
    -fx-text-fill: white; /* 白文字に */
}

/* Spinnerのアイテムのマウスオーバー時の色を変更 */
.spinner .text-field:hover {
    -fx-background-color: #4CAF50; /* 緑色の背景 */
    -fx-text-fill: white; /* 白文字に */
}

/* ダークテーマ時のSpinnerのスタイル */
.dark-mode .spinner .text-field {
    -fx-background-color: #555555; /* 背景色を暗く */
    -fx-text-fill: white; /* 文字色を白に */
}

/* ダークテーマ時のSpinnerのボタン部分(上下の矢印) */
.dark-mode .spinner .increment-button,
.dark-mode .spinner .decrement-button {
    -fx-background-color: #555555; /* ボタン背景 */
    -fx-text-fill: white; /* ボタン内の文字色を白に */
}

EditorController.javaの実装

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Spinner;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;

/**
 * EditorControllerクラスは、エディタのUI操作を管理します。
 * フォント選択、フォントサイズ変更、テーマ切替などの機能を提供します。
 */
public class EditorController {

    @FXML private ComboBox<String> fontSelector;   // フォント選択用コンボボックス
    @FXML private Spinner<Integer> fontSizeSpinner; // フォントサイズ選択用スピナー
    @FXML private TextArea textArea;                 // テキストエリア
    @FXML private Button themeToggleButton;         // テーマ切り替えボタン
    @FXML private VBox root;                        // レイアウトのルート(VBox)

    /**
     * initializeメソッドは、FXMLファイルで指定されたUIコンポーネントを初期化し、イベントリスナーを設定します。
     */
    @FXML
    public void initialize() {
        // フォントリストを設定
        fontSelector.getItems().addAll("Arial", "Verdana", "Courier New", "Times New Roman");
        fontSelector.setValue(TextEditor.getFont().getFamily()); // 初期フォント設定

        // フォントサイズ設定
        fontSizeSpinner.setValueFactory(
            new javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory(10, 32, (int) TextEditor.getFont().getSize())
        );

        // テキストエリアに初期フォントを適用
        textArea.setFont(TextEditor.getFont());

        // フォント選択時のアクション設定
        fontSelector.setOnAction(e -> applyFont());
        fontSizeSpinner.valueProperty().addListener((obs, oldVal, newVal) -> applyFont()); // フォントサイズ変更リスナー

        // テーマ切替ボタンのアクション設定
        themeToggleButton.setOnAction(e -> {
            toggleTheme();
            TextEditor.getInstance().applyTheme((BorderPane) root.getParent()); // テーマを適用
        });
    }

    /**
     * フォント選択とサイズ変更の後にテキストエリアにフォントを適用するメソッド。
     */
    private void applyFont() {
        String selectedFont = fontSelector.getValue();  // 選択されたフォントを取得
        int fontSize = fontSizeSpinner.getValue();      // 選択されたフォントサイズを取得
        TextEditor.getInstance().setFont(selectedFont, fontSize); // インスタンス経由でフォント設定

        // テキストエリアに新しいフォントを適用
        textArea.setFont(Font.font(selectedFont, fontSize));
    }

    /**
     * 現在のテーマを切り替えるメソッド。
     * テーマが変更されると、適用されているテーマが画面に反映されます。
     */
    private void toggleTheme() {
        TextEditor.toggleTheme(); // テーマを切り替え
        TextEditor.getInstance().applyTheme((BorderPane) root.getParent()); // 新しいテーマを適用
    }
}

EditorSettings.javaの実装

package application;

import java.util.prefs.Preferences;

import javafx.scene.text.Font;

/**
 * EditorSettingsクラスは、エディタの設定を管理するクラスです。
 * フォント、フォントサイズ、テーマの設定を保存および取得します。
 */
public class EditorSettings {
    private static final String PREF_FONT = "font";           // 保存するフォントのキー
    private static final String PREF_FONT_SIZE = "fontSize";  // 保存するフォントサイズのキー
    private static final String PREF_THEME = "theme";         // 保存するテーマのキー

    private String currentFont;   // 現在のフォント
    private int currentFontSize;  // 現在のフォントサイズ
    private String currentTheme;  // 現在のテーマ
    private Preferences prefs;    // 設定を保存するためのPreferencesオブジェクト

    /**
     * コンストラクタ。エディタの設定をPreferencesから読み込みます。
     */
    public EditorSettings() {
        // Preferencesを使用して設定を取得または初期値を設定
        prefs = Preferences.userNodeForPackage(EditorSettings.class);
        currentFont = prefs.get(PREF_FONT, "Arial"); // デフォルトはArial
        currentFontSize = prefs.getInt(PREF_FONT_SIZE, 14); // デフォルトは14
        currentTheme = prefs.get(PREF_THEME, "light"); // デフォルトはlightテーマ
    }

    /**
     * 現在のフォント(フォントファミリとサイズ)を取得します。
     * @return 現在のフォント
     */
    public Font getCurrentFont() {
        return Font.font(currentFont, currentFontSize);
    }

    /**
     * 現在のテーマを取得します。
     * @return 現在のテーマ("light" または "dark")
     */
    public String getCurrentTheme() {
        return currentTheme;
    }

    /**
     * フォントとフォントサイズを設定します。
     * 設定はPreferencesに保存されます。
     * @param font フォントファミリ名
     * @param size フォントサイズ
     */
    public void setFont(String font, int size) {
        this.currentFont = font;
        this.currentFontSize = size;

        // 設定を保存
        prefs.put(PREF_FONT, font);
        prefs.putInt(PREF_FONT_SIZE, size);
    }

    /**
     * テーマを切り替えます。
     * 現在のテーマが"light"の場合、"dark"に変更し、逆も同様です。
     * 設定はPreferencesに保存されます。
     */
    public void toggleTheme() {
        // 現在のテーマがlightの場合はdarkに、darkの場合はlightに切り替え
        if (currentTheme.equals("light")) {
            currentTheme = "dark";
        } else {
            currentTheme = "light";
        }

        // 設定を保存
        prefs.put(PREF_THEME, currentTheme);
    }
}

TextEditor.javaの修正

package application;

import java.util.prefs.Preferences;

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

/**
 * TextEditorクラスは、エディタアプリケーションのメインウィンドウを構成するクラスです。
 * ユーザーのフォント選択、フォントサイズ、テーマ切り替えを管理し、アプリケーションのUIを構築します。
 */
public class TextEditor extends Application {
    private static TextEditor instance;  // インスタンス
    private EditorView editorView;      // エディタのUI
    private MenuController menuController;  // メニューの管理
    private FileManager fileManager;    // ファイル操作を管理
    private AutoSaveManager autoSaveManager;  // 自動保存を管理
    private HBox settingsBox;           // 設定用のUIボックス
    private static Preferences prefs = Preferences.userNodeForPackage(TextEditor.class);

    private static final String PREF_FONT = "font";      // フォントの設定キー
    private static final String PREF_FONT_SIZE = "fontSize";  // フォントサイズの設定キー
    private static final String PREF_THEME = "theme";    // テーマの設定キー

    private static String currentFont = prefs.get(PREF_FONT, "Arial");  // 現在のフォント
    private static int currentFontSize = prefs.getInt(PREF_FONT_SIZE, 14);  // 現在のフォントサイズ
    private static String currentTheme = prefs.get(PREF_THEME, "light");  // 現在のテーマ

    /**
     * アプリケーションのメインメソッド。ウィンドウを作成し、レイアウトを設定します。
     * @param primaryStage アプリケーションのメインステージ
     */
    @Override
    public void start(Stage primaryStage) {
        instance = this;
        primaryStage.setTitle("エディタアプリ");

        // 初期テーマを「light」に設定
        currentTheme = "light";
        prefs.put(PREF_THEME, currentTheme);
        
        // UIの各コンポーネントを作成
        editorView = new EditorView();
        menuController = new MenuController();
        fileManager = new FileManager(primaryStage);
        autoSaveManager = new AutoSaveManager(editorView.getTextArea());

        // フォント選択とテーマ切り替え用のUIを設定
        ComboBox<String> fontSelector = new ComboBox<>();
        fontSelector.getItems().addAll("Arial", "Verdana", "Courier New", "Times New Roman");
        fontSelector.setValue(currentFont);

        Spinner<Integer> fontSizeSpinner = new Spinner<>(10, 32, currentFontSize);
        Button themeToggleButton = new Button("テーマ切替");

        // レイアウトのためのBorderPaneを作成
        BorderPane root = new BorderPane();

        // フォント選択時にフォントを適用
        fontSelector.setOnAction(e -> {
            String selectedFont = fontSelector.getValue();
            int selectedSize = fontSizeSpinner.getValue();
            setFont(selectedFont, selectedSize);
            editorView.getTextArea().setFont(Font.font(selectedFont, selectedSize));
        });
        
        // フォントサイズ変更時にフォントを適用
        fontSizeSpinner.valueProperty().addListener((obs, oldVal, newVal) -> setFont(fontSelector.getValue(), newVal));

        // テーマ切り替えボタンのアクション
        themeToggleButton.setOnAction(e -> {
            toggleTheme();
            applyTheme(root);
        });

        // 設定用のボックスにフォントとサイズの選択UIを追加
        settingsBox = new HBox(10, new Label("フォント:"), fontSelector, new Label("サイズ:"), fontSizeSpinner, themeToggleButton);
        settingsBox.setId("settings-box");

        // 画面レイアウトを設定
        root.setTop(menuController.getMenuBar());
        root.setCenter(editorView.getLayout());
        root.setBottom(settingsBox);

        // 初期のフォントとテーマ設定
        editorView.getTextArea().setFont(Font.font(currentFont, currentFontSize));
        applyTheme(root);

        // シーンを設定し、表示
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * シングルトンインスタンスを取得します。
     * @return TextEditorインスタンス
     */
    public static TextEditor getInstance() {
        return instance;
    }

    /**
     * フォントとフォントサイズを設定します。設定はPreferencesに保存されます。
     * @param font フォントファミリ
     * @param size フォントサイズ
     */
    public void setFont(String font, int size) {
        currentFont = font;
        currentFontSize = size;
        prefs.put(PREF_FONT, font);
        prefs.putInt(PREF_FONT_SIZE, size);

        if (editorView != null) {
            editorView.getTextArea().setFont(Font.font(currentFont, currentFontSize));
        } else {
            System.err.println("Error: editorView is null!");
        }
    }

    /**
     * 現在のフォント設定を取得します。
     * @return 現在のフォント
     */
    public static Font getFont() {
        return Font.font(currentFont, currentFontSize);
    }

    /**
     * テーマを切り替えます。「light」から「dark」またはその逆に切り替え、設定を保存します。
     */
    public static void toggleTheme() {
        currentTheme = currentTheme.equals("light") ? "dark" : "light";
        prefs.put(PREF_THEME, currentTheme);
    }

    /**
     * 現在のテーマを適用します。ダークモードかライトモードかを切り替えます。
     * @param root レイアウトのルート要素(BorderPane)
     */
    public void applyTheme(BorderPane root) {
        String themePath = "dark-theme.css";
        java.net.URL resource = TextEditor.class.getResource(themePath);

        if (resource == null) {
            System.err.println("Error: Theme file not found: " + themePath);
            return;
        }

        root.getStylesheets().clear();
        root.getStylesheets().add(resource.toExternalForm());

        // ダークモードの場合のスタイル設定
        if (currentTheme.equals("dark")) {
            root.getStyleClass().add("dark-mode");
            settingsBox.setStyle("-fx-background-color: #333333;");
            for (Node node : settingsBox.getChildren()) {
                if (node instanceof Label) {
                    ((Label) node).setStyle("-fx-text-fill: white;");
                }
            }
        } else {
            // ライトモードの場合のスタイル設定
            root.getStyleClass().remove("dark-mode");
            settingsBox.setStyle("-fx-background-color: #ffffff;");
            for (Node node : settingsBox.getChildren()) {
                if (node instanceof Label) {
                    ((Label) node).setStyle("-fx-text-fill: black;");
                }
            }
        }
    }

    /**
     * アプリケーションのエントリーポイント。JavaFXアプリケーションを起動します。
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        launch(args);
    }
}
エディタ(ライトモード)
エディタ(ダークモード)

まとめ

この記事では、JavaFXを使ったシンプルなエディタ(エディタ)アプリの作成手順を解説しました。JavaFXの基本構造、UI設計、ファイル管理、テーマ切り替え、フォント設定、自動保存など実用的な機能を組み合わせながら、個人開発の練習素材として活用できる形で整理しています。

このアプリを実際に作ることで、Javaのオブジェクト指向やMVC設計、イベントドリブンの考え方、ファイル操作の基礎を実践的に学べます。作りながら理解することで知識が定着しやすく、今後の個人開発の基礎力向上に直結します。

将来フリーランスや副業で収入を得たいエンジニア志望者にとって、こうした小規模ツールを作れるスキルは大きな武器になります。JavaFXで作ったエディタは、単なる練習で終わらず、自分専用の便利ツールとして日常的に使える資産にもなります。

もし今の環境でキャリアに不安があるなら、会社依存から個人依存へ切り替える準備を少しずつ始める時期です。JavaFXを活用した個人開発で、小さな「稼げる仕組み」を自分の手で作る力を身につけていきましょう。

このエディタアプリでJavaFXを学んだら、次はSpring Bootを使ったWebアプリ開発にも挑戦してみましょう。Javaエンジニアとして副業や個人開発の幅を広げるために、Web開発スキルの習得は避けて通れません。

以下の記事で、Spring Bootの環境構築からプロジェクトのセットアップ方法までわかりやすく解説していますので、続けて学習してスキルアップに役立ててください。

👉 【Javaの基礎知識】Spring Boot環境構築&プロジェクトセットアップ完全ガイド!

よく読まれている記事

1

「私たちが日々利用しているスマートフォンやインターネット、そしてスーパーコンピュータやクラウドサービス――これらの多くがLinuxの力で動いていることをご存じですか? 無料で使えるだけでなく、高い柔軟 ...

2

Linux環境でよく目にする「Vim」という名前。サーバーにログインしたら突然Vimが開いてしまい、「どうやって入力するの?」「保存や終了ができない!」と困った経験をした人も多いのではないでしょうか。 ...

3

ネットワーク技術は現代のITインフラにおいて不可欠な要素となっています。しかし、ネットワークを深く理解するためには、その基本となる「プロトコル」と「レイヤ」の概念をしっかり把握することが重要です。 こ ...

4

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

5

Javaは世界中で広く使われているプログラミング言語であり、特に業務システムやWebアプリケーションの開発において欠かせない存在です。本記事では、初心者向けにJavaの基礎知識を網羅し、環境構築から基 ...

-Javaの基礎知識(実践編)