JTable でセルのないところに行っぽい表示を出せますか?

のようなことを,先週末ぐらいにきかれて「できるんじゃないですかね?」と答えて作ってみた.
意外にサクッと作れた.Swing ってうまくできてますね.

普通の JTable + JScrollPane

こんな感じになると思います.

普通の JTable + カスタマイズした JViewport

JScrollPane を Javadoc で調べてみると,セル領域外の表示をごにょごにょしたいときは JViewport を改造すればよいことが解ります.
Oracle Technology Network for Java Developers


で,こんな↓感じのコードを書くと,


TestMain.java

public class TestMain {
	
	static class TestFrame extends JFrame {

		public TestFrame() {

			TestTable table = new TestTable();
			TestTableViewport viewport = new TestTableViewport();
			viewport.setView(table);

			JScrollPane scroll = new JScrollPane();
			scroll.setViewport(viewport);

			getContentPane().add(scroll);

			setSize(480, 320);
			setDefaultCloseOperation(EXIT_ON_CLOSE);

			setTitle("Swing ちょっとだけ好きになれた");
		}
	}

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new TestFrame().setVisible(true);
			}
		});
	}

}


TestTable.java

public class TestTable extends JTable {

	public static final Icon ICON = new ImageIcon(TestTable.class
			.getResource("start.gif"));

	public TestTable() {
		super(new AbstractTableModel() {
			public int getColumnCount() {
				return 3;
			}
			public int getRowCount() {
				return 30;
			}
			public Object getValueAt(int rowIndex, int columnIndex) {
				return "Dummy";
			}
		});

		setDefaultRenderer(Object.class, new CancelSelectionRenderer());

		setShowGrid(false);

		getTableHeader().setReorderingAllowed(false);
		setAutoResizeMode(AUTO_RESIZE_OFF);
	}

	static class CancelSelectionRenderer extends DefaultTableCellRenderer {
		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {

			setOpaque(false);
			setFont(table.getFont());
			setValue(value);
			setIcon(ICON);

			return this;
		}
	}
}


TestTableViewport.java

public class TestTableViewport extends JViewport {

	public void setView(final JTable table) {
		table.setOpaque(false);
		table.getSelectionModel().addListSelectionListener(
				new ListSelectionListener() {

					public void valueChanged(ListSelectionEvent e) {
						repaint();
					}

				});
		super.setView(table);
		super.setBackground(table.getBackground());
	}

	@Override
	public JTable getView() {
		return super.getView() instanceof JTable
				? (JTable) super.getView()
				: null;
	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		JTable table = getView();
		if (table == null || table.getRowCount() < 1) {
			return;
		}

		final Color saveColor = g.getColor();

		// 表示中の領域
		Rectangle viewRect = getViewRect();

		// 交互に行を描画
		final int rowCount = table.getRowCount();
		final Color back = table.getBackground();
		final Color blue = getTranslucentColor(Color.BLUE);
		boolean isColor = false;

		for (int r = 0; r < rowCount; ++r) {
			g.setColor((isColor = !isColor) ? blue : back);

			Rectangle rc = table.getCellRect(r, 0, true);
			g.fillRect(0, rc.y - viewRect.y, viewRect.width, rc.height - 1);
		}

		// 選択状態を描画
		g.setColor(getTranslucentColor(Color.RED));
		int[] selections = table.getSelectedRows();
		for (int sel : selections) {
			Rectangle rc = table.getCellRect(sel, 0, true);
			g.fillRect(0, rc.y - viewRect.y, viewRect.width - 1, rc.height - 1);
		}

		// フォーカス状態を描画
		int lead = table.getSelectionModel().getLeadSelectionIndex();
		if (0 <= lead) {
			g.setColor(Color.BLACK);
			Rectangle rc = table.getCellRect(lead, 0, true);
			BasicGraphicsUtils.drawDashedRect(g, 0, rc.y - viewRect.y,
					viewRect.width, rc.height - 1);
		}
		
		g.setColor(saveColor);
	}

	private Color getTranslucentColor(Color color) {
		return new Color(color.getRed(), color.getGreen(), color.getBlue(), 30);
	}
}

↓こんな感じになります.


セルのないところで行を選択できるようにするためには,JViewport に MouseListener を追加して対応すればいける.