---
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