Java Swingにおけるカスタム背景付きウィンドウの実装と動的サイズ調整

Java Swingアプリケーションで、カスタムの背景画像を持つウィンドウを作成し、様々な画面解像度に適応するように動的にサイズを調整する方法について解説します。特に、ログイン画面のような特定のデザイン要件を持つウィンドウに役立ちます。

カスタム背景パネルの作成

まず、ウィンドウの背景として画像を表示するためのカスタムJPanelを作成します。このパネルは、指定された画像を読み込み、自身の描画メソッド(paintComponent)でその画像をパネル全体に伸縮させて描画します。

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

/**
 * 画像を背景として描画するカスタムJPanel。
 * パネル全体に画像を拡大・縮小して表示します。
 */
public class ImageBackgroundPanel extends JPanel {
    private BufferedImage backgroundImage;
    private String imagePath;

    /**
     * 指定されたパスから画像を読み込み、背景パネルを作成します。
     * @param imagePath 背景画像のファイルパス
     */
    public ImageBackgroundPanel(String imagePath) {
        this.imagePath = imagePath;
        loadImage();
        // パネルの描画設定
        setOpaque(false); // 背景を透明にして、paintComponentが全てを描画するようにする
    }

    /**
     * 指定された画像パスから画像を読み込みます。
     * リソースとして画像を扱う場合は、ClassLoder.getResourceAsStreamを使用するなど、パスの指定を調整してください。
     */
    private void loadImage() {
        try {
            // 例: ClassLoader.getSystemResourceAsStream(imagePath) など
            backgroundImage = ImageIO.read(new File(imagePath));
        } catch (IOException e) {
            System.err.println("背景画像の読み込みに失敗しました: " + imagePath);
            e.printStackTrace();
            backgroundImage = null; // 読み込み失敗時は画像をクリア
        }
    }

    /**
     * コンポーネントの描画処理をオーバーライドし、背景画像を描画します。
     * @param g グラフィックコンテキスト
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); // 親クラスの描画処理を呼び出す
        if (backgroundImage != null) {
            // 画像をパネルの現在のサイズに合わせて描画
            g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
        }
    }

    /**
     * 背景画像を新しいパスに変更します。
     * @param newPath 新しい背景画像のファイルパス
     */
    public void setImagePath(String newPath) {
        this.imagePath = newPath;
        loadImage();
        repaint(); // 新しい画像を再描画
    }
}

カスタムフレームの実装

次に、上記のImageBackgroundPanelを組み込み、ウィンドウの初期設定(サイズ、位置、装飾なし)と、画面解像度に応じた動的なサイズ調整を行うJFrameを実装します。

import javax.swing.*;
import java.awt.*;
import java.io.IOException;

/**
 * カスタム背景を持ち、画面中央に配置され、動的にサイズが調整されるJFrameの例。
 */
public class CustomLoginFrame extends JFrame {
    // 画面デザインの基準となる解像度(例: フルHDディスプレイを想定)
    private static final int DESIGN_RESOLUTION_WIDTH = 1920;
    private static final int DESIGN_RESOLUTION_HEIGHT = 1080;

    // ウィンドウの初期サイズ(デザイン時のサイズ)
    private static final int INITIAL_FRAME_WIDTH = 400;
    private static final int INITIAL_FRAME_HEIGHT = 300;

    private ImageBackgroundPanel backgroundPanel;
    private Container contentPane;

    /**
     * 新しいカスタムログインフレームを作成します。
     * @param title フレームのタイトル
     * @param imagePath 背景画像のファイルパス
     * @throws IOException 画像読み込みエラーが発生した場合
     */
    public CustomLoginFrame(String title, String imagePath) throws IOException {
        super(title); // フレームのタイトルを設定

        // フレームの基本設定
        setSize(INITIAL_FRAME_WIDTH, INITIAL_FRAME_HEIGHT);
        contentPane = getContentPane();
        contentPane.setLayout(null); // レイアウトマネージャーを使用しない

        // ウィンドウのタイプをUTILITYに設定し、OSのフレーム装飾を削除
        setType(JFrame.Type.UTILITY);
        setUndecorated(true);

        // 背景パネルの追加
        addCustomBackground(imagePath);

        // フレームを画面中央に配置
        centerFrameOnScreen();

        // 画面解像度に応じてフレームとコンポーネントのサイズを調整
        applyDynamicScaling();
    }

    /**
     * 背景パネルをフレームに追加します。
     * @param imagePath 背景画像のファイルパス
     * @throws IOException 画像読み込みエラーが発生した場合
     */
    private void addCustomBackground(String imagePath) throws IOException {
        backgroundPanel = new ImageBackgroundPanel(imagePath);
        // 背景パネルをコンテンツペイン全体に設定
        backgroundPanel.setBounds(0, 0, INITIAL_FRAME_WIDTH, INITIAL_FRAME_HEIGHT);
        contentPane.add(backgroundPanel);
        // 背景パネルが一番下にくるように設定
        contentPane.setComponentZOrder(backgroundPanel, contentPane.getComponentCount() - 1);
    }

    /**
     * フレームを画面の中央に配置します。
     */
    private void centerFrameOnScreen() {
        // スクリーン全体のサイズを取得
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        // フレーム自身のサイズを取得
        Dimension frameSize = getSize();

        // 中央に配置するための座標を計算
        int x = (screenSize.width - frameSize.width) / 2;
        int y = (screenSize.height - frameSize.height) / 2;

        setLocation(x, y); // フレームの位置を設定
    }

    /**
     * 画面解像度に合わせてフレームと内部コンポーネントのサイズを動的に調整します。
     * DESIGN_RESOLUTIONを基準として、現在の画面サイズに比例して調整します。
     */
    private void applyDynamicScaling() {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

        // 現在の画面解像度とデザイン基準解像度の比率を計算
        double scaleFactorX = (double) screenSize.width / DESIGN_RESOLUTION_WIDTH;
        double scaleFactorY = (double) screenSize.height / DESIGN_RESOLUTION_HEIGHT;

        // フレームの新しい目標サイズを計算(初期デザインサイズを基準にスケーリング)
        int targetFrameWidth = (int) (INITIAL_FRAME_WIDTH * scaleFactorX);
        int targetFrameHeight = (int) (INITIAL_FRAME_HEIGHT * scaleFactorY);
        setSize(targetFrameWidth, targetFrameHeight);
        
        // 背景パネルのサイズも新しいフレームサイズに合わせる
        backgroundPanel.setBounds(0, 0, targetFrameWidth, targetFrameHeight);

        // コンポーネントのスケーリング係数を計算(新しいフレームサイズと初期デザインサイズを基準)
        // このロジックは、フレームが拡大・縮小された際に内部コンポーネントも同様にスケーリングされることを想定しています。
        double componentScaleX = (double) targetFrameWidth / INITIAL_FRAME_WIDTH;
        double componentScaleY = (double) targetFrameHeight / INITIAL_FRAME_HEIGHT;

        scaleAllComponents(this, componentScaleX, componentScaleY);
    }

    /**
     * フレーム内のすべてのコンポーネント(とフォントサイズ)をスケーリングします。
     * @param frame スケーリング対象のJFrame
     * @param scaleX X方向のスケーリング係数
     * @param scaleY Y方向のスケーリング係数
     */
    private static void scaleAllComponents(JFrame frame, double scaleX, double scaleY) {
        try {
            // コンテンツペイン内のすべてのコンポーネントを取得
            Component[] components = frame.getContentPane().getComponents();
            for (Component comp : components) {
                // 背景パネルは別途サイズ調整済みのため、スケーリング対象から除外
                if (comp instanceof ImageBackgroundPanel) {
                    continue;
                }

                // 新しい位置とサイズを計算
                int newX = (int) (comp.getX() * scaleX);
                int newY = (int) (comp.getY() * scaleY);
                int newWidth = (int) (comp.getWidth() * scaleX);
                int newHeight = (int) (comp.getHeight() * scaleY);

                comp.setLocation(newX, newY);
                comp.setSize(newWidth, newHeight);

                // フォントサイズもスケーリング
                Font currentFont = comp.getFont();
                if (currentFont != null) {
                    int newFontSize = (int) (currentFont.getSize() * scaleY);
                    // 最小フォントサイズを保証(ゼロや負のサイズにならないように)
                    comp.setFont(new Font(currentFont.getFontName(), currentFont.getStyle(), Math.max(1, newFontSize)));
                }
            }
        } catch (Exception e) {
            System.err.println("コンポーネントのスケーリング中にエラーが発生しました: " + e.getMessage());
        }
    }

    /**
     * テスト用のメインメソッド。
     * 適切な画像パスを指定して実行してください。
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            try {
                // ここに実際の画像パスを指定してください。例: "resources/images/background.jpg"
                // 実行環境に合わせてパスを調整してください。
                // テスト用の画像ファイルをプロジェクトルート直下に置く場合: "background.jpg"
                CustomLoginFrame frame = new CustomLoginFrame("ログイン画面", "background.jpg");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            } catch (IOException e) {
                e.printStackTrace();
                JOptionPane.showMessageDialog(null, "アプリケーションの起動に失敗しました。\n背景画像を読み込めませんでした。", "エラー", JOptionPane.ERROR_MESSAGE);
            }
        });
    }
}

この実装により、アプリケーションは特定の画面デザインを維持しつつ、ユーザーの画面解像度に合わせて適切な表示サイズに自動的に調整されます。背景画像は指定されたパスから読み込まれ、JFrameのコンテンツペイン全体に広がり、装飾のないカスタムウィンドウとして機能します。

タグ: Java Swing JFrame JPanel GUI

5月14日 16:05 投稿