JListのセルに項目選択チェックボックスを追加する
Total: 765
, Today: 1
, Yesterday: 0
Posted by aterai at
Last-modified:
概要
JList
のセルに項目選択チェックボックスを追加表示してキー操作なしで複数選択可能にします。
Screenshot
Advertisement
サンプルコード
@Override public void setSelectionInterval(int anchor, int lead) {
if (checkedIndex < 0 && isDragging()) {
super.setSelectionInterval(anchor, lead);
} else {
EventQueue.invokeLater(() -> {
if (checkedIndex >= 0 && lead == anchor && checkedIndex == anchor) {
super.addSelectionInterval(checkedIndex, checkedIndex);
} else {
super.setSelectionInterval(anchor, lead);
}
});
}
}
protected boolean isDragging() {
Rectangle r = getRubberBand().getBounds();
return r.width != 0 || r.height != 0;
}
@Override public void removeSelectionInterval(int index0, int index1) {
if (checkedIndex < 0) {
super.removeSelectionInterval(index0, index1);
} else {
EventQueue.invokeLater(() -> super.removeSelectionInterval(index0, index1));
}
}
private static <E> Optional<AbstractButton> getItemCheckBox(
JList<E> list, MouseEvent e, int index) {
if (e.isShiftDown() || e.isControlDown() || e.isAltDown()) {
return Optional.empty();
}
E proto = list.getPrototypeCellValue();
ListCellRenderer<? super E> cr = list.getCellRenderer();
Component c = cr.getListCellRendererComponent(
list, proto, index, false, false);
Rectangle r = list.getCellBounds(index, index);
c.setBounds(r);
Point pt = e.getPoint();
pt.translate(-r.x, -r.y);
return Optional
.ofNullable(SwingUtilities.getDeepestComponentAt(c, pt.x, pt.y))
.filter(AbstractButton.class::isInstance)
.map(AbstractButton.class::cast);
}
private final class ItemCheckBoxesListener extends MouseAdapter {
private final Point srcPoint = new Point();
@Override public void mouseDragged(MouseEvent e) {
checkedIndex = -1;
JList<?> l = (JList<?>) e.getComponent();
l.setFocusable(true);
Point destPoint = e.getPoint();
Path2D rb = getRubberBand();
rb.reset();
rb.moveTo(srcPoint.x, srcPoint.y);
rb.lineTo(destPoint.x, srcPoint.y);
rb.lineTo(destPoint.x, destPoint.y);
rb.lineTo(srcPoint.x, destPoint.y);
rb.closePath();
int[] indices = IntStream.range(0, l.getModel().getSize())
.filter(i -> rb.intersects(l.getCellBounds(i, i))).toArray();
l.setSelectedIndices(indices);
l.repaint();
}
@Override public void mouseExited(MouseEvent e) {
rollOverIndex = -1;
e.getComponent().repaint();
}
@Override public void mouseMoved(MouseEvent e) {
Point pt = e.getPoint();
int idx = locationToIndex(pt);
if (!getCellBounds(idx, idx).contains(pt)) {
idx = -1;
}
Rectangle rect = new Rectangle();
if (idx > 0) {
rect.add(getCellBounds(idx, idx));
if (rollOverIndex >= 0 && idx != rollOverIndex) {
rect.add(getCellBounds(rollOverIndex, rollOverIndex));
}
rollOverIndex = idx;
} else {
if (rollOverIndex >= 0) {
rect.add(getCellBounds(rollOverIndex, rollOverIndex));
}
rollOverIndex = -1;
}
((JComponent) e.getComponent()).repaint(rect);
}
@Override public void mouseReleased(MouseEvent e) {
getRubberBand().reset();
Component c = e.getComponent();
c.setFocusable(true);
c.repaint();
}
@Override public void mousePressed(MouseEvent e) {
JList<?> l = (JList<?>) e.getComponent();
int index = l.locationToIndex(e.getPoint());
if (l.getCellBounds(index, index).contains(e.getPoint())) {
l.setFocusable(true);
cellPressed(l, e, index);
} else {
l.setFocusable(false);
l.clearSelection();
l.getSelectionModel().setAnchorSelectionIndex(-1);
l.getSelectionModel().setLeadSelectionIndex(-1);
}
srcPoint.setLocation(e.getPoint());
l.repaint();
}
private void cellPressed(JList<?> l, MouseEvent e, int index) {
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1) {
ListItem item = getModel().getElementAt(index);
JOptionPane.showMessageDialog(l.getRootPane(), item.title);
} else {
checkedIndex = -1;
getItemCheckBox(l, e.getPoint(), index).ifPresent(rb -> {
checkedIndex = index;
if (l.isSelectedIndex(index)) {
l.setFocusable(false);
removeSelectionInterval(index, index);
} else {
setSelectionInterval(index, index);
}
});
}
}
}
View in GitHub: Java, Kotlin解説
JList#setLayoutOrientation(JList.HORIZONTAL_WRAP)
メソッドを使用してセルを左上から水平方向に並べる水平ニュースペーパー・スタイルレイアウトのJList
を作成- 各セルの右上隅に
JCheckBox
を表示するセルレンダラーを作成- セルが選択されている場合は
JCheckBox#setSelected(true)
で選択状態 - セルがロールオーバーの場合は
JCheckBox#setVisible(true)
で項目選択チェックボックスを表示- 幅が
JCheckBox
と同じで高さ0
のコンポーネントとあわせてJPanel(new BorderLayout())
に配置することでロールオーバーなしで非表示状態の場合でもセル内のレイアウトが変化しないよう設定
- 幅が
- セルが選択されている場合は
class ListItemCellRenderer implements ListCellRenderer<E> {
private final JPanel renderer = new JPanel(new BorderLayout(0, 0));
private final AbstractButton check = new JCheckBox();
private final JLabel icon = new JLabel("", null, SwingConstants.CENTER);
private final JLabel label = new JLabel("", SwingConstants.CENTER);
private final JPanel itemPanel = new JPanel(new BorderLayout(2, 2)) {
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (SELECTED_COLOR.equals(getBackground())) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(SELECTED_COLOR);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
}
};
protected ListItemCellRenderer() {
// ...
Dimension d = check.getPreferredSize();
JPanel p = new JPanel(new BorderLayout(0, 0));
p.setOpaque(false);
p.add(check, BorderLayout.NORTH);
p.add(Box.createHorizontalStrut(d.width), BorderLayout.SOUTH);
itemPanel.add(p, BorderLayout.EAST);
itemPanel.add(Box.createHorizontalStrut(d.width), BorderLayout.WEST);
itemPanel.add(icon);
itemPanel.add(label, BorderLayout.SOUTH);
itemPanel.setOpaque(true);
renderer.add(itemPanel);
renderer.setOpaque(false);
// ...
- セルがクリックされたとき、その位置に存在するコンポーネントを
SwingUtilities.getDeepestComponentAt(...)
メソッドで検索- JListのセル内にJButtonを配置する
- コンポーネントが
JCheckBox
の場合、オーバーライドしたJList#setSelectionInterval(...)
で現状の選択状態は維持したままクリックしたJCheckBox
の存在するセルのみsuper.addSelectionInterval(checkedIndex, checkedIndex)
で選択状態に追加- JListをマウスクリックのみで複数選択する
- すでにクリックした
JCheckBox
のセルが選択状態の場合は、オーバーライドしたJList#removeSelectionInterval(...)
で対象セルのみ選択状態を解除
- コンポーネントが
JCheckBox
ではない場合、通常のセル選択を実行