---
category: swing
folder: ThreeDotsMenuButton
title: JListのセルがハイライト表示中にのみJButtonを表示する
tags: [JList, JLayer, JButton]
author: aterai
pubdate: 2024-12-09T08:19:05+09:00
description: JListのセルハイライト表示とコンテキストメニュー表示用のJButtonをJLayer上で描画します。
image: https://drive.google.com/uc?id=16fRrYb7io_62_SL3QoDEAvwI7QTD1cj4
---
* Summary [#summary]
`JList`のセルハイライト表示とコンテキストメニュー表示用の`JButton`を`JLayer`上で描画します。
#download(https://drive.google.com/uc?id=16fRrYb7io_62_SL3QoDEAvwI7QTD1cj4)
* Source Code Examples [#sourcecode]
#code(link){{
class RolloverLayerUI extends LayerUI<JScrollPane> {
private final JPanel renderer = new JPanel();
private int rolloverIdx = -1;
private final Point loc = new Point(-100, -100);
private final JButton button = new JButton(new ThreeDotsIcon()) {
@Override public void updateUI() {
super.updateUI();
setBorderPainted(false);
setContentAreaFilled(false);
setFocusPainted(false);
setFocusable(false);
setOpaque(false);
setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));
}
};
@Override public void installUI(JComponent c) {
super.installUI(c);
if (c instanceof JLayer) {
((JLayer<?>) c).setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
}
@Override public void uninstallUI(JComponent c) {
if (c instanceof JLayer) {
((JLayer<?>) c).setLayerEventMask(0);
}
super.uninstallUI(c);
}
@Override protected void processMouseEvent(
MouseEvent e, JLayer<? extends JScrollPane> l) {
super.processMouseEvent(e, l);
Component c = e.getComponent();
if (c instanceof JList) {
JList<?> list = (JList<?>) c;
int id = e.getID();
if (id == MouseEvent.MOUSE_CLICKED && SwingUtilities.isLeftMouseButton(e)) {
Rectangle r = list.getCellBounds(rolloverIdx, rolloverIdx);
Dimension d = button.getPreferredSize();
r.width = l.getView().getViewportBorderBounds().width - d.width;
JPopupMenu popup = ((JComponent) c).getComponentPopupMenu();
Point pt = e.getPoint();
if (popup != null && !r.contains(pt)) {
popup.show(c, pt.x, pt.y);
}
} else if (id == MouseEvent.MOUSE_EXITED && rolloverIdx >= 0) {
list.repaint(list.getCellBounds(rolloverIdx, rolloverIdx));
rolloverIdx = -1;
loc.setLocation(-100, -100);
}
}
}
@Override protected void processMouseMotionEvent(
MouseEvent e, JLayer<? extends JScrollPane> l) {
super.processMouseMotionEvent(e, l);
Component c = e.getComponent();
if (e.getID() == MouseEvent.MOUSE_MOVED && c instanceof JList) {
JList<?> list = (JList<?>) c;
loc.setLocation(e.getPoint());
int prev = rolloverIdx;
rolloverIdx = list.locationToIndex(loc);
// #30 If you scroll too fast, multiple layers will be displayed
if (rolloverIdx >= 0) {
Rectangle r = list.getCellBounds(rolloverIdx, rolloverIdx);
r.width = l.getView().getViewportBorderBounds().width;
r.grow(0, r.height);
Rectangle rr = prev >= 0 ? r.union(list.getCellBounds(prev, prev)) : r;
list.repaint(rr);
}
}
}
@Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
JList<?> list = getList(c);
if (list != null && rolloverIdx >= 0) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
Graphics2D g2 = (Graphics2D) g.create();
Rectangle cellBounds = list.getCellBounds(rolloverIdx, rolloverIdx);
Component rc = getRendererComponent(list, rolloverIdx);
Dimension d = button.getPreferredSize();
cellBounds.width = scroll.getViewportBorderBounds().width - d.width;
boolean buttonRollover = !cellBounds.contains(loc);
button.getModel().setRollover(buttonRollover);
Rectangle rect = SwingUtilities.convertRectangle(list, cellBounds, c);
SwingUtilities.paintComponent(g2, rc, renderer, rect);
rect.x += rect.width;
rect.width = d.width;
g2.setPaint(rc.getBackground());
g2.fill(rect);
SwingUtilities.paintComponent(g2, button, renderer, rect);
g2.dispose();
}
}
private static JList<?> getList(JComponent layer) {
JList<?> list = null;
if (layer instanceof JLayer) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) layer).getView();
Component view = scroll.getViewport().getView();
if (view instanceof JList) {
list = (JList<?>) view;
}
}
return list;
}
private static <E> Component getRendererComponent(JList<E> list, int idx) {
E value = list.getModel().getElementAt(idx);
ListCellRenderer<? super E> r = list.getCellRenderer();
boolean isSelected = list.isSelectedIndex(idx);
boolean cellHasFocus = list.getSelectionModel().getLeadSelectionIndex() == idx;
Component c = r.getListCellRendererComponent(
list, value, idx, isSelected, cellHasFocus);
if (!isSelected) {
c.setBackground(Color.GRAY);
c.setForeground(Color.WHITE);
}
return c;
}
}
}}
----
- [https://github.com/aterai/java-swing-tips/issues/30 ThreeDotsMenuButton: If you scroll too fast, multiple layers will be displayed · Issue #30 · aterai/java-swing-tips]
-- 上記で指摘して貰った「マウス高速移動でハイライトの残像が残る」バグと、「`JSplitPane`に配置するとディバイダー移動で`NullPointerException`が発生する」バグを修正
* Description [#explanation]
* Description [#description]
- `JList`の親`JScrollPane`に`JLayer`を設定
-- セルのハイライト表示を`ListCellRenderer`ではなく`LayerUI#paint(...)`をオーバーライドして、`JList#getListCellRendererComponent(...)`で取得した描画用コンポーネントの文字色、背景色を変更し`SwingUtilities.paintComponent(...)`で描画
-- 描画領域をセル全体の幅ではなくコンテキストメニュー表示用の`JButton`の幅を除外するよう縮小する
--- [[JListのセルをカーソル移動でロールオーバー>Swing/RollOverListener]]
-- コンテキストメニュー表示用の`JButton`も同様に`SwingUtilities.paintComponent(...)`でセル右端に描画
-- `SwingUtilities.paintComponent(...)`での描画で`JButton`の`MouseListener`などは反応しないため、`LayerUI#processMouseEvent(...)`中でクリックされたかを判別し`JList#getComponentPopupMenu()`で取得した`JPopupMenu`を表示
--- `Windows`環境で右クリックだけではなく、この`JButton`の左クリックで一般的?な`Web`アプリのように`JPopupMenu`が表示可能になる
* Reference [#reference]
- [[JListのセルをカーソル移動でロールオーバー>Swing/RollOverListener]]
- [[JListのセル内にJButtonを配置する>Swing/ButtonsInListCell]]
- [[JListのセルに項目選択チェックボックスを追加する>Swing/ListCellItemCheckBoxes]]
* Comment [#comment]
#comment
#comment