JTableの列の境界上に追加挿入カーソルを表示する
Total: 113
, Today: 2
, Yesterday: 2
Posted by aterai at
Last-modified:
概要
JTable
の各列の間にマウスを移動したときその境界線上にクリックで新規のTableColumn
が挿入可能なカーソルを描画します。
Screenshot
Advertisement
サンプルコード
class ColumnInsertLayerUI extends LayerUI<JScrollPane> {
private static final Color LINE_COLOR = new Color(0x00_78_D7);
private static final int LINE_WIDTH = 4;
private final Rectangle2D line = new Rectangle2D.Double();
private final Ellipse2D plus = new Ellipse2D.Double(0d, 0d, 10d, 10d);
@Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (c instanceof JLayer && !line.isEmpty()) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
JTableHeader header =
((JTable) scroll.getViewport().getView()).getTableHeader();
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Point pt0 = line.getBounds().getLocation();
Point pt1 = SwingUtilities.convertPoint(header, pt0, c);
g2.translate(pt1.getX() - pt0.getX(), pt1.getY() - pt0.getY());
// paint Insert Line
g2.setPaint(LINE_COLOR);
g2.fill(line);
// paint Plus Icon
g2.setPaint(Color.WHITE);
g2.fill(plus);
g2.setPaint(LINE_COLOR);
double cx = plus.getCenterX();
double cy = plus.getCenterY();
double w2 = plus.getWidth() / 2d;
double h2 = plus.getHeight() / 2d;
g2.draw(new Line2D.Double(cx - w2, cy, cx + w2, cy));
g2.draw(new Line2D.Double(cx, cy - h2, cx, cy + h2));
g2.draw(plus);
g2.dispose();
}
}
private void updateLineLocation(JScrollPane scroll, Point loc) {
JTable table = (JTable) scroll.getViewport().getView();
JTableHeader header = table.getTableHeader();
Rectangle rect = scroll.getVisibleRect();
JScrollBar bar = scroll.getHorizontalScrollBar();
int scrollHeight = bar.isVisible() ? bar.getHeight() : 0;
Dimension d = new Dimension(
LINE_WIDTH, rect.height - scrollHeight);
for (int i = 0; i < table.getColumnCount(); i++) {
if (canInsert(header, loc, i, d)) {
return;
}
}
}
private boolean canInsert(
JTableHeader header, Point loc, int i, Dimension d) {
Rectangle r = header.getHeaderRect(i);
Rectangle r1 = getWestRect(r, i);
Rectangle r2 = getEastRect(r);
boolean hit = false;
if (r1.contains(loc)) {
updateInsertLineLocation(r1, loc, d, header);
hit = true;
} else if (r2.contains(loc)) {
updateInsertLineLocation(r2, loc, d, header);
hit = true;
} else if (r.contains(loc)) {
line.setFrame(0d, 0d, 0d, 0d);
header.setCursor(Cursor.getDefaultCursor());
hit = true;
}
return hit;
}
private Rectangle getWestRect(Rectangle r, int i) {
Rectangle rect = r.getBounds();
Rectangle bounds = plus.getBounds();
if (i != 0) {
rect.x -= bounds.width / 2;
}
rect.setSize(bounds.getSize());
return rect;
}
private Rectangle getEastRect(Rectangle r) {
Rectangle rect = r.getBounds();
Rectangle bounds = plus.getBounds();
rect.x += rect.width - bounds.width / 2;
rect.setSize(bounds.getSize());
return rect;
}
private void updateInsertLineLocation(
Rectangle r, Point loc, Dimension d, Component c) {
if (r.contains(loc)) {
double cx = r.getCenterX();
double cy = r.getCenterY();
line.setFrame(
cx - d.getWidth() / 2d, r.getY(), d.width, d.height);
double pw = plus.getWidth() / 2d;
double ph = plus.getHeight() / 2d;
plus.setFrameFromCenter(cx, cy, cx - pw, cy - ph);
c.setCursor(Cursor.getDefaultCursor());
} else {
line.setFrame(0d, 0d, 0d, 0d);
c.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
}
}
@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);
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
JScrollPane scroll = l.getView();
Point pt = e.getPoint();
if (plus.contains(pt) && !line.isEmpty()) {
JTable table = (JTable) scroll.getViewport().getView();
TableModel model = table.getModel();
int columnCount = table.getColumnCount();
int maxColumn = model.getColumnCount();
if (columnCount < maxColumn) {
int idx = table.columnAtPoint(
line.getBounds().getLocation());
TableColumn column = new TableColumn(columnCount);
column.setHeaderValue("Column" + columnCount);
table.addColumn(column);
table.moveColumn(columnCount, idx + 1);
updateLineLocation(scroll, pt);
}
}
l.repaint(scroll.getBounds());
}
}
@Override protected void processMouseMotionEvent(
MouseEvent e, JLayer<? extends JScrollPane> l) {
super.processMouseMotionEvent(e, l);
Component c = e.getComponent();
int id = e.getID();
JScrollPane scroll = l.getView();
if (id == MouseEvent.MOUSE_MOVED && c instanceof JTableHeader) {
updateLineLocation(scroll, e.getPoint());
} else {
line.setFrame(0d, 0d, 0d, 0d);
}
l.repaint(scroll.getBounds());
}
}
View in GitHub: Java, Kotlin解説
JTable
とJTableHeader
を配置するJScrollPane
にJLayer
を設定して挿入カーソルを描画JTableHeader#getHeaderRect(column)
で取得したTableColumn
領域を左右の境界付近2
つに分割し、その領域内にマウスが存在する場合は挿入カーソルを描画JTableHeader
ではなくJScrollPane
にJLayer
を設定しているので、SwingUtilities.convertPoint(header, pt, layer)
でTableColumn
領域の位置を変換しないと水平スクロールバーで移動して表示されるTableColumn
領域はずれてしまう
- 列名は
Excel
風にアルファベット26
進数(bijective base-26)
で表示private static String convertToColumnTitle(int columnNumber) { assert columnNumber > 0 : "Input is not valid!"; StringBuilder sb = new StringBuilder(); int num = columnNumber; while (num > 0) { int mod = (num - 1) % 26; int code = 'A' + mod; sb.insert(0, (char) code); // Java 11: sb.insert(0, Character.toString(code)); num = (num - mod) / 26; } return sb.toString(); }
- 列を入れ替えても列名は表示上の列順番から生成するヘッダセルレンダラーを
JTableHeader#setDefaultRenderer(...)
で設定- このサンプルを
Java 8
環境で実行し、WindowsLookAndFeel
から別のLookAndFeel
へ切り替えるとNullPointerException
が発生する Java 21
では修正されているがWindowsLookAndFeel
から別のLookAndFeel
に切り替えるとJTableHeader
の高さが変化してしまうException in thread "AWT-EventQueue-0" java.lang.NullPointerException at com.sun.java.swing.plaf.windows.WindowsTableHeaderUI$XPDefaultRenderer.paint(WindowsTableHeaderUI.java:171) at javax.swing.CellRendererPane.paintComponent(CellRendererPane.java:151) at javax.swing.plaf.basic.BasicTableHeaderUI.paintCell(BasicTableHeaderUI.java:710)
- このサンプルを
- 初期状態での表示上の列数は
3
列だが、TableModel
は16,384
列で作成しJTable#setAutoCreateColumnsFromModel(false)
でTableModel
から列を自動作成しないよう設定JTable#addColumn(...)
で表示上の列を追加挿入してもモデル上の列数を超えるまではArrayIndexOutOfBoundsException
の発生を防ぐことができる
JTable
やTableColumnModel
には指定した位置にTableColumn
を挿入するメソッドは用意されていない- 行挿入はDefaultTableModel#insertRow(row, ...)が存在する
- 代わりにJTable#addColumn(TableColumn)で列の末尾に追加した後、JTable#moveColumn(int last, int target)で挿入位置まで移動している
TableColumn column = new TableColumn(columnCount); column.setHeaderValue("Column" + columnCount); table.addColumn(column); table.moveColumn(columnCount, idx + 1);
参考リンク
- Modernized Excel Grid | Microsoft Community Hub
- このサンプルは
Web
版Excel
のSimplified insert options
機能を参考に作成している
- このサンプルは
- c# - How to convert a column number (e.g. 127) into an Excel column (e.g. AA) - Stack Overflow
- TableColumnのドラッグによる順序変更が可能な領域を制限する