---
category: swing
folder: DraggableMenu
title: JMenuBarに配置したJMenuをドラッグして並べ替える
title-en: Drag and rearrange JMenus placed on the JMenuBar
tags: [JMenu, JMenuBar, JLayer, JWindow, DragAndDrop]
author: aterai
pubdate: 2026-02-02T05:04:04+09:00
description: JMenuBarに配置されたトップレベルJMenuをマウスドラッグで並べ替え可能にします。
summary-jp: JMenuBarに配置されたトップレベルJMenuをマウスドラッグで並べ替え可能にします。
summary-en: Enables rearranging top-level JMenus placed on the JMenuBar via mouse drag.
image: https://drive.google.com/uc?id=1swDjzlMY-XCB9Bq6C0HpM8gN0_LIK-JD
---
* Summary [#summary]
JMenuBarに配置されたトップレベルJMenuをマウスドラッグで並べ替え可能にします。
// #en{{Enables rearranging top-level JMenus placed on the JMenuBar via mouse drag.}}
`JMenuBar`に配置されたトップレベル`JMenu`をマウスドラッグで並べ替え可能にします。
// #en{{Enables rearranging top-level `JMenu`s placed on the `JMenuBar` via mouse drag.}}

#download(https://drive.google.com/uc?id=1swDjzlMY-XCB9Bq6C0HpM8gN0_LIK-JD)

* Source Code Examples [#sourcecode]
#code(link){{
class MenuDragLayerUI extends LayerUI<JMenuBar> {
  private static final int DRAG_THRESHOLD = 8;
  private JMenu draggingMenu;
  private JWindow ghostWindow;
  private JLabel ghostLabel;
  private Point startPt;
  private boolean isDragging;
  private int targetIndex = -1;
  private int dividerX = -1;

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    ((JLayer<?>) c).setLayerEventMask(
        AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    ghostWindow = new JWindow();
    ghostWindow.setOpacity(0.7f);
    ghostLabel = new JLabel();
    ghostLabel.setOpaque(false);
    ghostLabel.setBorder(BorderFactory.createCompoundBorder(
        BorderFactory.createLineBorder(Color.GRAY),
        BorderFactory.createEmptyBorder(2, 5, 2, 5)
    ));
    ghostLabel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    ghostWindow.add(ghostLabel);
  }

  @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JMenuBar> l) {
    JMenuBar bar = l.getView();
    Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), bar);
    if (e.getID() == MouseEvent.MOUSE_PRESSED) {
      Component c = bar.getComponentAt(p);
      if (c instanceof JMenu) {
        draggingMenu = (JMenu) c;
        startPt = p;
      }
    } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
      if (isDragging && draggingMenu != null) {
        finalizeDrop(bar);
      }
      resetDragState(l);
    }
  }

  @Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JMenuBar> l) {
    if (draggingMenu != null) {
      JMenuBar bar = l.getView();
      Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), bar);
      if (!isDragging && startPt != null && startPt.distance(p) > DRAG_THRESHOLD) {
        initiateDrag(e);
      }
      if (isDragging) {
        updateDragFeedback(e, bar, p);
        l.repaint();
        e.consume();
      }
    }
  }

  private void initiateDrag(MouseEvent e) {
    isDragging = true;
    MenuSelectionManager.defaultManager().clearSelectedPath();
    draggingMenu.setEnabled(false);
    ghostLabel.setText(draggingMenu.getText());
    ghostLabel.setFont(draggingMenu.getFont());
    ghostWindow.pack();
    updateGhostLocation(e);
    ghostWindow.setVisible(true);
  }

  private void updateDragFeedback(MouseEvent e, JMenuBar bar, Point p) {
    updateGhostLocation(e);
    Component[] menus = bar.getComponents();
    targetIndex = 0;
    dividerX = (menus.length > 0) ? menus[0].getX() : 0;
    for (int i = 0; i < menus.length; i++) {
      Component m = menus[i];
      if (Objects.equals(m, draggingMenu)) {
        continue;
      }
      int midX = m.getX() + m.getWidth() / 2;
      if (p.x < midX) {
        targetIndex = i;
        dividerX = m.getX();
        break;
      } else {
        targetIndex = i + 1;
        dividerX = m.getX() + m.getWidth();
      }
    }
  }

  private void finalizeDrop(JMenuBar bar) {
    int currentIdx = -1;
    for (int i = 0; i < bar.getComponentCount(); i++) {
      if (Objects.equals(bar.getComponent(i), draggingMenu)) {
        currentIdx = i;
        break;
      }
    }

    int finalIdx = targetIndex;
    if (currentIdx != -1 && currentIdx < targetIndex) {
      finalIdx--;
    }

    bar.add(draggingMenu, Math.max(0, finalIdx));
    draggingMenu.setEnabled(true);
  }

  private void resetDragState(JLayer<? extends JMenuBar> l) {
    if (draggingMenu != null) {
      draggingMenu.setEnabled(true);
    }
    ghostWindow.setVisible(false);
    draggingMenu = null;
    isDragging = false;
    targetIndex = -1;
    dividerX = -1;
    l.getView().revalidate();
    l.repaint();
  }

  private void updateGhostLocation(MouseEvent e) {
    Point screenPt = e.getLocationOnScreen();
    ghostWindow.setLocation(screenPt.x + 15, screenPt.y + 15);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (isDragging && dividerX != -1) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setColor(UIManager.getColor("List.dropLineColor"));
      g2.setStroke(new BasicStroke(2.0f));
      g2.drawLine(dividerX, 0, dividerX, c.getHeight());
      g2.dispose();
    }
  }
}
}}

* Description [#description]
- `JLayer`を使用してドラッグ開始イベントを取得するので、`JLayer`でラップした`JMenuBar`を`JFrame#setJMenuBar(menuBar)`で配置することができない
-- 代わりに`BorderLayout`を設定した`JPanel`に`JPanel#add(menuBar, BorderLayout.NORTH)`を使用して`JLayer`でラップした`JMenuBar`を配置
- トップレベル`JMenu`上で閾値以上マウスがドラッグされたら並び替えを開始する
-- `LayerUI#processMouseEvent(...)`、`LayerUI#processMouseMotionEvent(...)`内で取得可能な`MouseEvent`のイベントソースはラップする`JMenuBar`やその子コンポーネントなので`SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), bar)`で座標を`JMenuBar`基準に変換し、マウスカーソル下の並べ替え対象の`JMenu`を取得する必要がある
-- 取得したドラッグ対象の`JMenu`を`setEnabled(false)`で無効化してハイライトし、そのタイトルをコピーした`JLabel`を半透明の`JWindow`に配置してゴーストウィンドウを作成
-- `JMenu`ドラッグ中はゴーストウィンドウの位置更新と`JMenuBar`の挿入位置にドロップラインを描画する
- マウスリリースで並び替えを終了し、以下の後始末を実行
-- `JMenuBar#add(draggingMenu, index)`で`JMenu`の位置を更新
-- `JMenu#setEnabled(true)`で`JMenu`を有効化
-- ゴーストウィンドウの非表示化

* Reference [#reference]
- [[JToolBarに配置したアイコンをドラッグして並べ替える>Swing/RearrangeToolBarIcon]]
- [[JFrameの外側でもドラッグアイコンを表示する>Swing/DragSourceMotionListener]]

* Comment [#comment]
#comment
#comment