Java Swing コンポーネントを重ねて配置したい

こんにちは、kisseです。

JavaのSwingでコードを書かなきゃいけない課題があったのですが、コンポーネントを重ねて貼る方法に困ってしまいました。
これまではレイアウトマネージャにnullを設定して、座標でコンポーネントを配置していました。
しかし、背景としてラベルを設置して、その上にボタンを設置しようとした時に、ボタンがラベルの後ろに隠れてしまう現象が起こりました。

普通にJPanelなどを使っている場合には、Z軸の定義ができないのでどうしようかなと思ってたんです。
JPanelの代わりにJLayeredPaneを使えば良いことが分かったので、それについて調べました。



JLayeredPaneとは

JLayeredPaneは、Swingのコンテナに深さの要素を追加したクラスです。
基本的に、JPanelと使い方は変わらないので簡単に使えます。

JLayeredPaneでは、レイヤーの深さの範囲をいくつかのレイヤーに分割しています。
下から順に、

  1. DEFAULT_LAYER
  2. PALETTE_LAYER
  3. MODAL_LAYER
  4. POPUP_LAYER
  5. DRAG_LAYER

基本的にDEFAULT_LAYERだけで完結させることができます。
例えばユーザーが操作するものをPALETTE_LAYER上に置いて、それ以外をDEFAULT_LAYER上に置いたりすると、ユーザーが操作するものが見やすくなります。
JLayeredPane.setLayer(Component c, int layer)メソッドの第2引数に上記のレイヤー名を渡すと、第1引数で指定したコンポーネントが指定したレイヤーに配置されます。

レイヤー内でのコンポーネントの位置を設定することもできます。
JLayeredPane.setLayer(Component c, int layer, int position)メソッドの第3引数に渡した値が大きいほど、より手前(上)に表示されるようになります。

書いたコード

挙動を確認するために、試しにコードを書いてみました。
書いたコードをそのまま載っけます。

 
public class Main {
    // booter
    public static void main(String[] args) {
        new View();
    }
}
 
import javax.swing.*;
public class View extends JFrame {

    private Pane pane;

    public View() {
        pane = new Pane();

        add(pane);

        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(0, 0, Params.VIEW_WIDTH, Params.VIEW_HEIGHT);

        setTitle("test");

    }
}
 
import javax.swing.*;
import java.awt.*;

public class Pane extends JLayeredPane {
    JLabel label;
    JButton button;

    public Pane() {
        setLayout(null);

        label = new JLabel();
        label.setOpaque(true);
        label.setBackground(Color.RED);
        label.setBounds(0, 0, Params.VIEW_WIDTH, Params.VIEW_HEIGHT);
        add(label);
        setLayer(label, DEFAULT_LAYER, 0);

        button = new JButton("click me!");
        button.setBackground(Color.white);
        button.setOpaque(true);
        button.addActionListener(e -> {
            label.setBackground(Color.white); // 背景色を変更するとボタンが消える -> repaint()で解決した。
            button.setText("clicked!");
            button.setEnabled(false);
            repaint();
        });
        button.setBounds(Params.VIEW_WIDTH / 2 - 80, Params.VIEW_HEIGHT / 2 - 45, 160, 90);
        add(button);
        setLayer(button, PALETTE_LAYER, 100);
    }
}
 
public class Params {
    public static final int VIEW_WIDTH = 800;
    public static final int VIEW_HEIGHT = 450;
}

コピペして実行すると挙動がわかると思います。
試してみてください。
(Paneクラス内で、ボタンの処理記述してる部分で強制で再描画されるようにしてるのですが、これってありな書き方なんでしょうか?詳しい方教えてください。笑)



まとめ

コンポーネントが重なるような場合が想定されている場合には、JLayeredPaneクラスを使ってコンポーネントの重なりかたを意図したものになるように制御しましょう。
重なりがちゃんと指定できなくて、ボタンが表示されないなどの憂き目をみないようにしましょう。(僕です)

最後まで読んでいただきありがとうございます!

あわせて読みたい