---
category: swing
folder: ExtraToolBarInTitleBar
title: JFrameのデフォルトWindow装飾タイトルバー上にJButtonを配置する
tags: [JFrame, JWindow, JButton, WindowListener]
author: aterai
pubdate: 2025-09-22T16:47:35+09:00
description: JFrameがデフォルトWindow装飾を使用する場合、JButtonなどを配置したJWindowをタイトルバー内のアイコン化ボタン位置に連動して表示します。
image: https://drive.google.com/uc?id=1xtEpIPfhARrCvMt0IzawZ448jeA1YefK
---
* Summary [#summary]
`JFrame`がデフォルト`Window`装飾を使用する場合、`JButton`などを配置した`JWindow`をタイトルバー内のアイコン化ボタン位置に連動して表示します。

#download(https://drive.google.com/uc?id=1xtEpIPfhARrCvMt0IzawZ448jeA1YefK)

* Source Code Examples [#sourcecode]
#code(link){{
class ExtraBarPositionHandler extends WindowAdapter implements ComponentListener {
  private final JWindow extraBar;

  protected ExtraBarPositionHandler(JWindow extraBar) {
    this.extraBar = extraBar;
  }

  @Override public void windowActivated(WindowEvent e) {
    extraBar.setBackground(UIManager.getColor("activeCaption"));
  }

  @Override public void windowDeactivated(WindowEvent e) {
    extraBar.setBackground(UIManager.getColor("inactiveCaption"));
  }

  @Override public void windowOpened(WindowEvent e) {
    setLocationRelativeTo(e.getWindow());
    extraBar.setVisible(true);
  }

  @Override public void windowClosed(WindowEvent e) {
    extraBar.setVisible(false);
  }

  @Override public void windowIconified(WindowEvent e) {
    extraBar.setVisible(false);
  }

  @Override public void windowDeiconified(WindowEvent e) {
    setLocationRelativeTo(e.getWindow());
    extraBar.setVisible(true);
  }

  @Override public void componentResized(ComponentEvent e) {
    // System.out.println("componentResized");
    // ???: setLocationRelativeTo(e.getComponent());
    EventQueue.invokeLater(() -> setLocationRelativeTo(e.getComponent()));
  }

  @Override public void componentMoved(ComponentEvent e) {
    setLocationRelativeTo(e.getComponent());
  }

  @Override public void componentShown(ComponentEvent e) {
    setLocationRelativeTo(e.getComponent());
    extraBar.setVisible(true);
  }

  @Override public void componentHidden(ComponentEvent e) {
    extraBar.setVisible(false);
  }

  private void setLocationRelativeTo(Component p) {
    EventQueue.invokeLater(() -> updateExtraBarLocation(p));
  }

  private void updateExtraBarLocation(Component p) {
    JRootPane root = SwingUtilities.getRootPane(p);
    Icon iconifyIcon = UIManager.getIcon("InternalFrame.iconifyIcon");
    Insets zeroIns = new Insets(0, 0, 0, 0);
    Border bdr = root.getBorder();
    Insets ins = bdr == null ? zeroIns : bdr.getBorderInsets(root);
    if (p instanceof Frame &&
        ((Frame) p).getExtendedState() == Frame.MAXIMIZED_BOTH) {
      ins = zeroIns;
    }
    JButton iconifyIconButton = SwingUtils
        .descendants(root)
        .filter(JButton.class::isInstance)
        .map(JButton.class::cast)
        .filter(b -> Objects.equals(b.getIcon(), iconifyIcon))
        .findFirst()
        .orElse(null);
    Point pt = iconifyIconButton == null
        ? new Point()
        : iconifyIconButton.getLocation();
    SwingUtilities.convertPointToScreen(pt, root);
    int x = pt.x - extraBar.getWidth();
    int y = p.getY() + ins.top + 1;
    extraBar.setLocation(x, y);
  }
}
}}

* Description [#description]
- `JFrame.setDefaultLookAndFeelDecorated(true)`を指定して`JFrame`がデフォルトの`Window`装飾を使用するよう設定
-- このため`MetalLookAndFeel`以外には未対応で、`LookAndFeel`の切り替えにも対応していない
- デフォルトの`Window`装飾を使用する場合、アイコン化`JButton`は`UIManager.getIcon("InternalFrame.iconifyIcon")`アイコンが適用されるので、`JRootPane`内でこれを使用する`JButton`を探し、その位置を取得する
- アイコン化`JButton`の位置をスクリーン相対に`SwingUtilities.convertPointToScreen(pt, root)`で変換して、`Dialog.ModalExclusionType.APPLICATION_EXCLUDE`な子`JWindow`をアイコン化`JButton`のとなりに配置する
-- [https://forums.oracle.com/ords/apexds/post/how-to-add-icon-to-jframe-s-title-bar-5581 How to add icon to JFrame's title bar - Oracle Forums]
-- `JWindow#setAlwaysOnTop(true)`を設定すると別アプリケーションより手前に子`JWindow`が表示されてしまう
- `WindowAdapter`を継承、かつ`ComponentListener`を実装するイベントリスナーを作成して、常に親`JFrame`の移動やリサイズに子`JWindow`の位置が追従するよう設定
-- 親`JFrame`のリサイズで子`JWindow`のサイズ以下になると、`JButton`がタイトルバーの外に表示されるので、これを防ぐために`JFrame#setMinimumSize(...)`で最小サイズを設定
-- 親`JFrame`がアイコン化される場合は子`JWindow`を非表示に変更する
-- `WindowAdapter#windowActivated(...)`が実行されたら子`JWindow`の背景色を`UIManager.getColor("activeCaption")`、`WindowAdapter#windowDeactivated(...)`が実行されたら`UIManager.getColor("inactiveCaption")`に切り替える
-- 最大化解除時に子`JWindow`の位置が正しく更新されない場合がある?ため、`ComponentListener#componentResized(...)`が実行されたら`EventQueue.invokeLater(...)`を二重に掛けて子`JWindow`の位置更新を実行している
-- 親`JFrame`が最大化される場合はその`JRootPane`の余白が無くなるので、子`JWindow`の`y`座標をその分修正する必要がある

* Reference [#reference]
- [https://forums.oracle.com/ords/apexds/post/how-to-add-icon-to-jframe-s-title-bar-5581 How to add icon to JFrame's title bar - Oracle Forums]
- [[JFrameがデフォルトのウィンドウ装飾を使用する場合のタイトルバー背景色を変更>Swing/WindowTitleBackground]]

* Comment [#comment]
#comment
#comment