---
category: swing
folder: NotPopupOverlapTaskBar
title: JPopupMenuがTaskBarと重ならないように表示位置を調整する
tags: [JPopupMenu, TaskBar]
author: aterai
pubdate: 2023-01-30T06:05:32+09:00
description: JPopupMenuがTaskBarと重なる場合、マウスカーソル位置がその右下隅になるよう表示位置を調整し、内部のJMenuItemの配置も反転します。
image: https://drive.google.com/uc?id=1qKrSZUuSGcLCP3cebYbv07ITdlv5w2_I
---
* 概要 [#summary]
`JPopupMenu`が`TaskBar`と重なる場合、マウスカーソル位置がその右下隅になるよう表示位置を調整し、内部の`JMenuItem`の配置も反転します。

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

* サンプルコード [#sourcecode]
#code(link){{
List<Component> list = Arrays.asList(
    new JMenuItem("JMenuItem 1"),
    new JMenuItem("JMenuItem 2"),
    new JMenuItem("JMenuItem 3"),
    new JMenuItem("JMenuItem 4"),
    new JMenuItem("JMenuItem 5"));
JPopupMenu popup = new JPopupMenu() {
  @Override public void show(Component c, int x, int y) {
    Point popupLocation = getInvokerOrigin(x, y, c.getLocationOnScreen());
    Rectangle scrBounds = getScreenBounds(c, popupLocation);
    Dimension popupSize = getPreferredSize();
    long popupBottomY = (long) popupLocation.y + (long) popupSize.height;
    Point p = new Point(x, y);
    removeAll();
    if (popupBottomY > scrBounds.y + scrBounds.height) {
      p.translate(-popupSize.width, -popupSize.height);
      for (int i = list.size() - 1; i >= 0; i--) {
        add(list.get(i));
      }
    } else {
      list.forEach(this::add);
    }
    super.show(c, p.x, p.y);
  }
};
list.forEach(popup::add);
setComponentPopupMenu(popup);
}}

* 解説 [#explanation]
- `JPopupMenu`の親コンポーネントが表示されている`GraphicsConfiguration`を取得
-- `JPopupMenu#getCurrentGraphicsConfiguration(Point)`を参考

#code{{
GraphicsConfiguration getCurrentGraphicsConfiguration2(Component c, Point p) {
  GraphicsConfiguration gc = null;
  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  GraphicsDevice[] gd = ge.getScreenDevices();
  for (GraphicsDevice graphicsDevice : gd) {
    if (graphicsDevice.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
      GraphicsConfiguration dgc = graphicsDevice.getDefaultConfiguration();
      if (dgc.getBounds().contains(p)) {
        gc = dgc;
        break;
      }
    }
  }
  // If not found, and we have invoker, ask invoker about his gc
  if (gc == null && c != null) {
    gc = c.getGraphicsConfiguration();
  }
  return gc;
}
}}

- 取得した`GraphicsConfiguration`からスクリーン領域を取得し`TaskBar`などの余白を除去
-- デフォルトの`JPopupMenu`では`JPopupMenu#adjustPopupLocationToFitScreen(int xPosition, int yPosition)`メソッド内で`((SunToolkit) Toolkit.getDefaultToolkit()).canPopupOverlapTaskBar()`が`false`の場合`TaskBar`などの余白を除去してスクリーン領域サイズを計算しているが`Windows 10`環境では常に`true`でこれを変更する方法が不明
-- このためこのサンプルでは以下のように`((SunToolkit) Toolkit.getDefaultToolkit()).canPopupOverlapTaskBar()`を無視して`TaskBar`領域をスクリーン領域サイズから除去している
-- デフォルトの`JPopupMenu`では`JPopupMenu#adjustPopupLocationToFitScreen(int xPosition, int yPosition)`メソッド内で`( (SunToolkit) Toolkit.getDefaultToolkit() ).canPopupOverlapTaskBar()`が`false`の場合`TaskBar`などの余白を除去してスクリーン領域サイズを計算しているが`Windows 10`環境では常に`true`でこれを変更する方法が不明
-- このためこのサンプルでは以下のように`( (SunToolkit) Toolkit.getDefaultToolkit() ).canPopupOverlapTaskBar()`を無視して`TaskBar`領域をスクリーン領域サイズから除去している

#code{{
private static Rectangle getScreenBounds(Component c, Point popupLocation) {
  Rectangle scrBounds;
  GraphicsConfiguration gc = getCurrentGraphicsConfiguration2(c, popupLocation);
  Toolkit toolkit = Toolkit.getDefaultToolkit();
  if (gc != null) {
    // If we have GraphicsConfiguration use it to get screen bounds
    scrBounds = gc.getBounds();
  } else {
    scrBounds = new Rectangle(toolkit.getScreenSize());
  }
  Insets scrInsets = toolkit.getScreenInsets(gc);
  scrBounds.x += scrInsets.left;
  scrBounds.y += scrInsets.top;
  scrBounds.width -= scrInsets.left + scrInsets.right;
  scrBounds.height -= scrInsets.top + scrInsets.bottom;
  return scrBounds;
}
}}

- `JPopupMenu#show(...)`をオーバーライドして取得したスクリーン領域の外に`JPopupMenu`の下辺が表示される場合は、マウスカーソル位置が`JPopupMenu`の右下隅になるよう表示位置を調整
-- 左上隅から右下隅にマウスカーソル配置が変更されるので`JPopupMenu`内部の`JMenuItem`の配置もマウスカーソル移動距離が短くなるよう昇順から降順に変更
-- `JPopupMenu#show(...)`内などで参照されている`popupPostionFixDisabled`は`popupPositionFixDisabled`の`typo`?

* 参考リンク [#reference]
- [[JPopupMenuの表示を親コンポーネント領域内のみに制限する>Swing/AdjustPopupLocation]]

* コメント [#comment]
#comment
#comment