JTable lacks support for showing a footer with aggregated data for each column. Inspired by a suggested solution at Oracle/Suns bug database that looked promising, I started with the approach that the footer is painted by the border of the scrollpane.
Now at first I just want a 'proof of concept' and it seems I'm almost there, each column have a footer, widths are in sync. etc. So it seems to work pretty good except one thing, no text is painted! I expect the dummy values returned from getFooterValueAt
painted on each "footer cell" but they're all blank, only the background (just for testing) is painted.
Why isn't any text painted? Is there something wrong with my position/size calculations in paintFooter
?
import static java.awt.BorderLayout.CENTER;
import static java.awt.Color.BLUE;
import static java.awt.Color.CYAN;
import static java.awt.Color.GREEN;
import static java.awt.Color.LIGHT_GRAY;
import static java.awt.Color.MAGENTA;
import static java.awt.Color.ORANGE;
import static java.awt.Color.PINK;
import static java.awt.Color.RED;
import static java.awt.Color.WHITE;
import static java.awt.Color.YELLOW;
import static javax.swing.JTable.AUTO_RESIZE_OFF;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.CellRendererPane;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
/**
* Demo application for JTable with footer.
*
* @author Martin Uhlén
*/
public class TableFooterBorderDemo extends JFrame
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
new TableFooterBorderDemo().setVisible(true);
}
});
}
private TableFooterBorderDemo()
{
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLayout(new BorderLayout());
JTable table = createTable();
JScrollPane scroll = new JScrollPane(table);
add(scroll, CENTER);
TableFooter.install(scroll, table);
pack();
positionAtMiddleOfScreen();
}
private void positionAtMiddleOfScreen()
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setLocation((int) ((screenSize.getWidth() / 2) - (getWidth() / 2)),
(int) ((screenSize.getHeight() / 2) - (getHeight() / 2)));
}
private JTable createTable()
{
Object[][] data = new Object[][]
{
{ "A", "*", "*", "*", "*", "*" },
{ "*", "B", "*", "*", "*", "*" },
{ "*", "*", "C", "*", "*", "*" },
{ "*", "*", "*", "D", "*", "*" },
{ "*", "*", "*", "*", "E", "*" },
{ "*", "*", "*", "*", "*", "F" }
};
Object[] columns = new Object[] { "A", "B", "C", "D", "E", "F" };
DefaultTableModel model = new DefaultTableModel(data, columns);
JTable table = new JTable(model);
table.setAutoResizeMode(AUTO_RESIZE_OFF);
return table;
}
/**
* A Border for JScrollPane that paints a footer for JTable.
*/
private static class TableFooter implements Border
{
private static final Color[] COLORS = {RED, GREEN, BLUE, YELLOW, PINK, CYAN, LIGHT_GRAY, MAGENTA, ORANGE, WHITE};
private final JScrollPane scroll;
private final JTable table;
private final CellRendererPane cellRendererPane;
TableFooter(JScrollPane scroll, JTable table)
{
this.scroll = scroll;
this.table = table;
cellRendererPane = new CellRendererPane();
}
public static TableFooter install(JScrollPane scroll, JTable table)
{
verify(scroll, table);
TableFooter footer = new TableFooter(scroll, table);
RepaintListener repainter = new RepaintListener(scroll);
scroll.getViewport().addChangeListener(repainter);
scroll.getHorizontalScrollBar().addAdjustmentListener(repainter);
table.getColumnModel().addColumnModelListener(repainter);
scroll.setViewportBorder(footer);
return footer;
}
private static void verify(JScrollPane scroll, JTable table)
{
if (scroll.getViewport().getView() != table)
{
throw new IllegalArgumentException("Given table must be inside given scroll pane");
}
}
/**
* @see javax.swing.border.Border#paintBorder(java.awt.Component, java.awt.Graphics, int, int, int, int)
*/
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
System.out.println("x: " + x + ", y: " + y + ", width: " + width + ", height: " + height);
System.out.println("viewRect: " + scroll.getViewport().getViewRect());
paintFooter(g, x, y, width, height);
}
private void paintFooter(Graphics g, int x, int y, int width, int height)
{
Color oldColor = null;
Component cellRendererComponent = null;
int columnWidths = x - scroll.getViewport().getViewRect().x;
for (int column = 0; column < table.getColumnCount(); column++)
{
TableCellRenderer cellRenderer = table.getCellRenderer(0, column);
cellRendererComponent = cellRenderer.getTableCellRendererComponent(table, getFooterValueAt(column), false, false, 0, column);
if (oldColor == null)
{
oldColor = cellRendererComponent.getBackground();
}
int columnWidth = table.getColumnModel().getColumn(column).getWidth();
cellRendererComponent.setBackground(COLORS[column % COLORS.length]);
cellRendererPane.paintComponent(g, cellRendererComponent, scroll, columnWidths, y, columnWidth, height);
columnWidths += columnWidth;
}
if (cellRendererComponent != null)
{
cellRendererComponent.setBackground(oldColor);
}
}
private Object getFooterValueAt(int viewColumn)
{
return "Column " + viewColumn;
}
@Override
public Insets getBorderInsets(Component c)
{
return new Insets(0, 0, table.getRowHeight(), 0);
}
@Override
public boolean isBorderOpaque()
{
return true;
}
}
/**
* Repaints JScrollPane when needed.
*/
private static class RepaintListener implements ChangeListener, AdjustmentListener, TableColumnModelListener
{
private final JScrollPane scroll;
RepaintListener(JScrollPane scroll)
{
this.scroll = scroll;
}
@Override
public void columnAdded(TableColumnModelEvent e)
{
repaint();
}
@Override
public void columnRemoved(TableColumnModelEvent e)
{
repaint();
}
@Override
public void columnMoved(TableColumnModelEvent e)
{
repaint();
}
@Override
public void columnMarginChanged(ChangeEvent e)
{
repaint();
}
@Override
public void columnSelectionChanged(ListSelectionEvent e)
{
repaint();
}
@Override
public void adjustmentValueChanged(AdjustmentEvent e)
{
repaint();
}
@Override
public void stateChanged(ChangeEvent e)
{
repaint();
}
private void repaint()
{
scroll.repaint();
}
}
}
The problem is the location of the rendererPane: what happens in your code is that the label is located at x/y, that is near the upper borderline having the complete height of the viewport rect, then the text is painted somewhere in the middle of the viewport, and later overpainted by the viewport. The see that effect, use a zero row tableModel and make the viewport not-opaque.
Instead you have to position it at the lower bottom of the border.. A very dirty (did nothing to get it pixel-correct, on the run into the weekend :) snippet
g.setColor(Color.RED);
int scrollBottom = scroll.getInsets().bottom;
int lowerBorderTop = height - scrollBottom;
g.drawLine(x, height - scrollBottom - 1, width - 10, height - scrollBottom - 1);
g.drawRect(x + 1, height - scrollBottom, width - 10, 10);
Color oldColor = null;
Component cellRendererComponent = null;
int columnWidths = x ;//- scroll.getViewport().getViewRect().x;
for (int column = 0; column < table.getColumnCount(); column++)
{
TableCellRenderer cellRenderer = table.getCellRenderer(0, column);
cellRendererComponent = cellRenderer.getTableCellRendererComponent(table,
getFooterValueAt(column), false, false, 0, column);
if (oldColor == null)
{
oldColor = cellRendererComponent.getBackground();
}
int columnWidth = table.getColumnModel().getColumn(column).getWidth();
cellRendererComponent.setForeground(Color.BLACK);
cellRendererComponent.setBackground(COLORS[column % COLORS.length]);
cellRendererPane.paintComponent(g, cellRendererComponent, scroll, columnWidths,
lowerBorderTop , columnWidth, table.getRowHeight(), false);
columnWidths += columnWidth;
}
if (cellRendererComponent != null)
{
cellRendererComponent.setBackground(oldColor);
}
the additional line and rectangle is just to see where we are. BTW, nice approach :-)
place JComponent/JTextComponents
to JVievport
(advantage take MouseEvents
)
place JComponent/JTextComponents
to Glasspane
(advantage take MouseEvents
)
place JComponent/JTextComponents
to JPanel
, then this JPanel
to the JScrollPane
, by implements ComponentListener
you can resize JPanel
to the JViewport's Dimension
only is required resize smaller JPanel upto the JViewport's Dimension
in all case required add own Borders
to JScrollPane
and with same for "TableFooter"
is possible wrote I don't know how to call it ????
simpliest way should be workaround for two JTables or example by @camickr
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class TableFilterRow extends JFrame implements TableColumnModelListener {
private static final long serialVersionUID = 1L;
private JTable table;
private JPanel filterRow;
public TableFilterRow() {
table = new JTable(3, 5);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane);
table.getColumnModel().addColumnModelListener(this);
// Panel for text fields
filterRow = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
for (int i = 0; i < table.getColumnCount(); i++) {
filterRow.add(new JTextField(" Sum at - " + i));
}
columnMarginChanged(new ChangeEvent(table.getColumnModel()));
getContentPane().add(filterRow, BorderLayout.SOUTH);
}
// Implement TableColumnModelListener methods
// (Note: instead of implementing a listener you should be able to
// override the columnMarginChanged and columMoved methods of JTable)
@Override
public void columnMarginChanged(ChangeEvent e) {
TableColumnModel tcm = table.getColumnModel();
int columns = tcm.getColumnCount();
for (int i = 0; i < columns; i++) {
JTextField textField = (JTextField) filterRow.getComponent(i);
Dimension d = textField.getPreferredSize();
d.width = tcm.getColumn(i).getWidth();
textField.setPreferredSize(d);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
filterRow.revalidate();
}
});
}
@Override
public void columnMoved(TableColumnModelEvent e) {
Component moved = filterRow.getComponent(e.getFromIndex());
filterRow.remove(e.getFromIndex());
filterRow.add(moved, e.getToIndex());
filterRow.validate();
}
@Override
public void columnAdded(TableColumnModelEvent e) {
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
public static void main(String[] args) {
JFrame frame = new TableFilterRow();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With