How to Display Row Header on JTable Instead of Column Header - java

How can i display my Jtable to this ..
currently i only know to create this kind of jtable
below is my code
HERE
Object rowData1[][] =
{
{ "","","","" },
{ "","","","" },
{ "","","","" },
{ "","","","" }
};
Object columnNames1[] = { "HEADER 1", "HEADER 2", "HEADER 3", "HEADER 4" };
JTable table1 = new JTable(rowData1, columnNames1);
table1.getColumnModel().getColumn(0).setPreferredWidth(120);
JScrollPane scrollPane1 = new JScrollPane(table1);

This is a proof of concept only
Disclaimer: Before I get a bunch of hate mail about the, obviously, horrible things I've done to make this work, I stole most of the painting code straight out of the source, this is how it's actually done within the look and feel code itself :P
I've also gone to the nth degree, meaning that I've literally assumed that you wanted the row headers to look like the column headers. If this isn't a requirement, it would be SO much easier to do...
Okay, this is a basic proof of concept, which provides the means to generate the row header and render them the same way as they are normally, just as row headers instead.
Things that need to be added/supported:
Detect when the table model is changed (that is, a new table model is set to the table)
Detect when the column model is changed (that is, a new column model is set to the table)
Much of this functionality would probably need to be added to the TableWithRowHeader implementation...
Basically, what this "tries" to do, is create a custom row header, based on a JTableHeader, remove the existing column header and add itself into the row header view position of the enclosing JScrollPane.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class TableRowHeaderTest {
public static void main(String[] args) {
new TableRowHeaderTest();
}
public TableRowHeaderTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Object rowData1[][]
= {
{"", "", "", ""},
{"", "", "", ""},
{"", "", "", ""},
{"", "", "", ""}
};
Object columnNames1[] = {"HEADER 1", "HEADER 2", "HEADER 3", "HEADER 4"};
JTable table1 = new TableWithRowHeader(rowData1, columnNames1);
table1.getColumnModel().getColumn(0).setPreferredWidth(120);
JScrollPane scrollPane1 = new JScrollPane(table1);
scrollPane1.setColumnHeaderView(null);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane1);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TableWithRowHeader extends JTable {
private TableRowHeader rowHeader;
public TableWithRowHeader(final Object[][] rowData, final Object[] columnNames) {
super(rowData, columnNames);
rowHeader = new TableRowHeader(this);
}
#Override
protected void configureEnclosingScrollPane() {
// This is required as it calls a private method...
super.configureEnclosingScrollPane();
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
JViewport port = (JViewport) parent;
Container gp = port.getParent();
if (gp instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane) gp;
JViewport viewport = scrollPane.getViewport();
if (viewport == null || SwingUtilities.getUnwrappedView(viewport) != this) {
return;
}
scrollPane.setColumnHeaderView(null);
scrollPane.setRowHeaderView(rowHeader);
}
}
}
}
public class TableRowHeader extends JTableHeader {
private JTable table;
public TableRowHeader(JTable table) {
super(table.getColumnModel());
this.table = table;
table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
#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) {
// Don't care about this, want to highlight the row...
}
});
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
repaint();
}
});
}
public JTable getTable() {
return table;
}
#Override
public Dimension getPreferredSize() {
Dimension size = new Dimension();
JTable table = getTable();
if (table != null) {
TableColumnModel model = table.getColumnModel();
if (model != null) {
for (int index = 0; index < model.getColumnCount(); index++) {
TableColumn column = model.getColumn(index);
TableCellRenderer renderer = column.getHeaderRenderer();
if (renderer == null) {
renderer = getDefaultRenderer();
}
Component comp = renderer.getTableCellRendererComponent(table, column.getHeaderValue(), false, false, -1, index);
size.width = Math.max(comp.getPreferredSize().width, size.width);
size.height += table.getRowHeight(index);
}
}
}
return size;
}
/**
* Overridden to avoid propagating a invalidate up the tree when the
* cell renderer child is configured.
*/
#Override
public void invalidate() {
}
/**
* If the specified component is already a child of this then we don't bother doing anything - stacking order doesn't matter for cell renderer components
* (CellRendererPane doesn't paint anyway).
*/
#Override
protected void addImpl(Component x, Object constraints, int index) {
if (x.getParent() == this) {
return;
} else {
super.addImpl(x, constraints, index);
}
}
#Override
protected void paintComponent(Graphics g) {
// super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
JTable table = getTable();
if (table != null) {
int width = getWidth();
TableColumnModel model = table.getColumnModel();
if (model != null) {
for (int index = 0; index < model.getColumnCount(); index++) {
TableColumn column = model.getColumn(index);
TableCellRenderer renderer = column.getHeaderRenderer();
if (renderer == null) {
renderer = getDefaultRenderer();
}
boolean selected = table.getSelectedRow() == index;
Component comp = renderer.getTableCellRendererComponent(table, column.getHeaderValue(), selected, false, 0, index);
add(comp);
comp.validate();
int height = table.getRowHeight(index) - 1;
comp.setBounds(0, 0, width, height);
comp.paint(g2d);
comp.setBounds(-width, -height, 0, 0);
g2d.setColor(table.getGridColor());
g2d.drawLine(0, height, width, height);
g2d.translate(0, height + 1);
}
}
}
g2d.dispose();
removeAll();
}
}
}
Disclaimer: This is likely to blow up in your face. I make no checks for preventing the header from responding to things like changes to the column row sortering and ... in theory ... it shouldn't try and "resize" the column but I didn't test that...

Related

Groupable table header with filter below header

Problem
I'd like to create a groupable table header with a filter row below the header.
I guess the straightforward approach would be to include the filter components in the header. The problem is that the components aren't editable.
I searched, but didn't find a good or working approach. The best and working solution I found so far regarding filter components was putting them outside of the table. But when you have a grouped table that just looks and feels ugly. They must be below the header. Putting the filter components in the footer isn't an option either.
I used the groupable table header code form this thread and added a filter component.
The problem is now that when I click on the components, I can't access them. Instead the row sorting is triggered. Even adding a mouse listener to the textfield doesn't help.
Question
Does anyone know how to make the textfields in the table header editable? Or does anyone have a better approach about placing a table filter below the table header?
Code
The code so far:
FilterHeader.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
public class FilterHeader extends JPanel {
public FilterHeader( JTable table, Object value, int columnIndex) {
setLayout( new BorderLayout());
// header
JLabel header = new JLabel();
header.setForeground(table.getTableHeader().getForeground());
header.setBackground(table.getTableHeader().getBackground());
header.setFont(table.getTableHeader().getFont());
header.setHorizontalAlignment(JLabel.CENTER);
header.setText(value.toString());
header.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
add( header, BorderLayout.CENTER);
// append filter components to header
if( columnIndex == 3) {
JComboBox cb = new JComboBox();
cb.setBackground(Color.yellow);
cb.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
cb.setBorder(new EmptyBorder(0, 0, 0, 0));
cb.setForeground(table.getTableHeader().getForeground());
cb.setPreferredSize(new Dimension(0,table.getRowHeight() + 4));
add( cb, BorderLayout.SOUTH);
} else {
JTextField tf = new JTextField( "enter filtertext");
tf.setBackground(Color.yellow);
tf.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
tf.setForeground(table.getTableHeader().getForeground());
tf.setHorizontalAlignment(JLabel.CENTER);
add( tf, BorderLayout.SOUTH);
tf.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("textfield clicked"); // doesn't work
}
});
}
}
}
ColumnGroup.java
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
public class ColumnGroup {
protected TableCellRenderer renderer;
protected List<TableColumn> columns;
protected List<ColumnGroup> groups;
protected String text;
protected int margin = 0;
public ColumnGroup(String text) {
this(text, null);
}
public ColumnGroup(String text, TableCellRenderer renderer) {
this.text = text;
this.renderer = renderer;
this.columns = new ArrayList<TableColumn>();
this.groups = new ArrayList<ColumnGroup>();
}
public void add(TableColumn column) {
columns.add(column);
}
public void add(ColumnGroup group) {
groups.add(group);
}
/**
* #param column
* TableColumn
*/
public List<ColumnGroup> getColumnGroups(TableColumn column) {
if (!contains(column)) {
return Collections.emptyList();
}
List<ColumnGroup> result = new ArrayList<ColumnGroup>();
result.add(this);
if (columns.contains(column)) {
return result;
}
for (ColumnGroup columnGroup : groups) {
result.addAll(columnGroup.getColumnGroups(column));
}
return result;
}
private boolean contains(TableColumn column) {
if (columns.contains(column)) {
return true;
}
for (ColumnGroup group : groups) {
if (group.contains(column)) {
return true;
}
}
return false;
}
public TableCellRenderer getHeaderRenderer() {
return renderer;
}
public void setHeaderRenderer(TableCellRenderer renderer) {
this.renderer = renderer;
}
public String getHeaderValue() {
return text;
}
public Dimension getSize(JTable table) {
TableCellRenderer renderer = this.renderer;
if (renderer == null) {
renderer = table.getTableHeader().getDefaultRenderer();
}
Component comp = renderer.getTableCellRendererComponent(table, getHeaderValue() == null || getHeaderValue().trim().isEmpty() ? " "
: getHeaderValue(), false, false, -1, -1);
int height = comp.getPreferredSize().height;
int width = 0;
for (ColumnGroup columnGroup : groups) {
width += columnGroup.getSize(table).width;
}
for (TableColumn tableColumn : columns) {
width += tableColumn.getWidth();
width += margin;
}
return new Dimension(width, height);
}
public void setColumnMargin(int margin) {
this.margin = margin;
for (ColumnGroup columnGroup : groups) {
columnGroup.setColumnMargin(margin);
}
}
}
GroupableTableHeader.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
#SuppressWarnings("serial")
public class GroupableTableHeader extends JTableHeader {
#SuppressWarnings("unused")
private static final String uiClassID = "GroupableTableHeaderUI";
protected List<ColumnGroup> columnGroups = new ArrayList<ColumnGroup>();
public GroupableTableHeader(TableColumnModel model) {
super(model);
setUI(new GroupableTableHeaderUI());
setReorderingAllowed(false);
// setDefaultRenderer(new MultiLineHeaderRenderer());
}
#Override
public void updateUI() {
setUI(new GroupableTableHeaderUI());
}
#Override
public void setReorderingAllowed(boolean b) {
super.setReorderingAllowed(false);
}
public void addColumnGroup(ColumnGroup g) {
columnGroups.add(g);
}
public List<ColumnGroup> getColumnGroups(TableColumn col) {
for (ColumnGroup group : columnGroups) {
List<ColumnGroup> groups = group.getColumnGroups(col);
if (!groups.isEmpty()) {
return groups;
}
}
return Collections.emptyList();
}
public void setColumnMargin() {
int columnMargin = getColumnModel().getColumnMargin();
for (ColumnGroup group : columnGroups) {
group.setColumnMargin(columnMargin);
}
}
}
GroupableTableHeaderUI.java
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class GroupableTableHeaderUI extends BasicTableHeaderUI {
protected GroupableTableHeader getHeader() {
return (GroupableTableHeader) header;
}
#Override
public void paint(Graphics g, JComponent c) {
Rectangle clipBounds = g.getClipBounds();
if (header.getColumnModel().getColumnCount() == 0) {
return;
}
int column = 0;
Dimension size = header.getSize();
Rectangle cellRect = new Rectangle(0, 0, size.width, size.height);
Map<ColumnGroup, Rectangle> groupSizeMap = new HashMap<ColumnGroup, Rectangle>();
for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
cellRect.height = size.height;
cellRect.y = 0;
TableColumn aColumn = enumeration.nextElement();
List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
int groupHeight = 0;
for (ColumnGroup group : groups) {
Rectangle groupRect = groupSizeMap.get(group);
if (groupRect == null) {
groupRect = new Rectangle(cellRect);
Dimension d = group.getSize(header.getTable());
groupRect.width = d.width;
groupRect.height = d.height;
groupSizeMap.put(group, groupRect);
}
paintCell(g, groupRect, group);
groupHeight += groupRect.height;
cellRect.height = size.height - groupHeight;
cellRect.y = groupHeight;
}
cellRect.width = aColumn.getWidth();
if (cellRect.intersects(clipBounds)) {
paintCell(g, cellRect, column);
}
cellRect.x += cellRect.width;
column++;
}
}
private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
TableCellRenderer renderer = aColumn.getHeaderRenderer();
if (renderer == null) {
// original
renderer = getHeader().getDefaultRenderer();
// modified
renderer = new DefaultTableCellRenderer() {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
FilterHeader header = new FilterHeader( table, value, column);
return header;
}
};
}
Component c = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false,
-1, columnIndex);
c.setBackground(UIManager.getColor("control"));
rendererPane.paintComponent(g, c, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
}
private void paintCell(Graphics g, Rectangle cellRect, ColumnGroup cGroup) {
TableCellRenderer renderer = cGroup.getHeaderRenderer();
if (renderer == null) {
renderer = getHeader().getDefaultRenderer();
}
Component component = renderer.getTableCellRendererComponent(header.getTable(), cGroup.getHeaderValue(), false,
false, -1, -1);
rendererPane
.paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
}
private int getHeaderHeight() {
int headerHeight = 0;
TableColumnModel columnModel = header.getColumnModel();
for (int column = 0; column < columnModel.getColumnCount(); column++) {
TableColumn aColumn = columnModel.getColumn(column);
TableCellRenderer renderer = aColumn.getHeaderRenderer();
if (renderer == null) {
renderer = getHeader().getDefaultRenderer();
}
Component comp = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false,
false, -1, column);
int cHeight = comp.getPreferredSize().height;
List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
for (ColumnGroup group : groups) {
cHeight += group.getSize(header.getTable()).height;
}
headerHeight = Math.max(headerHeight, cHeight);
}
return headerHeight;
}
#Override
public Dimension getPreferredSize(JComponent c) {
int width = 0;
for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
TableColumn aColumn = enumeration.nextElement();
width += aColumn.getPreferredWidth();
}
return createHeaderSize(width);
}
private Dimension createHeaderSize(int width) {
TableColumnModel columnModel = header.getColumnModel();
width += columnModel.getColumnMargin() * columnModel.getColumnCount();
if (width > Integer.MAX_VALUE) {
width = Integer.MAX_VALUE;
}
return new Dimension(width, getHeaderHeight());
}
}
GroupableHeaderExample.java
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
// original from https://stackoverflow.com/questions/21347647/how-to-combine-two-column-headers-in-jtable-in-swings
public class GroupableHeaderExample extends JFrame {
GroupableHeaderExample() {
super( "Groupable Header Example" );
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(new Object[][]{
{"1","a","b","c","d","e"},
{"2","f","g","h","i","j"},
{"3","k","l","m","n","o"},
{"4","p","q","r","s","t"}
},
new Object[]{"SNo.","1","2","Native","2","3"});
JTable table = new JTable( dm ) {
protected JTableHeader createDefaultTableHeader() {
return new GroupableTableHeader(columnModel);
}
};
TableColumnModel cm = table.getColumnModel();
ColumnGroup g_name = new ColumnGroup("Name");
g_name.add(cm.getColumn(1));
g_name.add(cm.getColumn(2));
ColumnGroup g_lang = new ColumnGroup("Language");
g_lang.add(cm.getColumn(3));
ColumnGroup g_other = new ColumnGroup("Others");
g_other.add(cm.getColumn(4));
g_other.add(cm.getColumn(5));
g_lang.add(g_other);
GroupableTableHeader header = (GroupableTableHeader)table.getTableHeader();
header.addColumnGroup(g_name);
header.addColumnGroup(g_lang);
JScrollPane scroll = new JScrollPane( table );
getContentPane().add( scroll );
setSize( 400, 120 );
// allow sorting
RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(dm);
table.setRowSorter(sorter);
}
public static void main(String[] args) {
GroupableHeaderExample frame = new GroupableHeaderExample();
frame.setSize(1024,768);
frame.addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
System.exit(0);
}
});
frame.setVisible(true);
}
}
And a screenshot:
Thank you very much for the help!
Solved it by combining various sources.
One source was this post on StackOverflow, but that solution put the filter only outside of the table.
Another source was the open source version of a TableFilter on coderazzi. That's very awesome, but also very heavy weight for my needs. And doesn't support grouped columns. So all in all what I needed was this piece of code:
JViewport headerViewport = new JViewport() {
#Override
public void setView(Component view) {
if (view instanceof JTableHeader) {
filterHeader.add(view, BorderLayout.NORTH);
super.setView(filterHeader);
}
}
};
scroll.setColumnHeader(headerViewport);
and
private class TableFilterHeader extends JPanel {
public TableFilterHeader(JTableHeader th) {
setLayout(new BorderLayout());
add(new TableFilterRow(th.getTable()), BorderLayout.SOUTH);
}
}
A screenshot:
Interested ones can get the full code from this gist. What's missing is the filter itself, but that's just straightforward: Add a document listener and apply the filter.

JTable update ColumnNames

I am using AbstractTableModel.
I have used the code provided from this website(http://www.javalobby.org/articles/jtable/?source=archives) to auto generate new rows for the JTable when JTable reaches on last row.
Its working fine but I am not able to update the column names anymore when user clicks a button. So I have to remove auto generate row feature to update column names.
As far as I understand the code from the website, Its using a hidden column as a trigger to create new row. InteractiveTableModelListener is disabling something which I don't understand how to change/edit it?
How could I solve this?
Thanks for your time!!
This is a simple example of how it might be possible to provide "auto add at end" functionality
Basically, what this does is maps custom key bindings to provide customised key board navigation, in this example, this includes Tab, Shift+Tab, Enter, Left, Right, Up, Down which allows the ability to determine what should happen when these keys are pressed.
Mostly, Tab, Enter, Left, Up, Down are most relevant, I just added the others in as an example and to maintain consistency.
A feature that might be slightly more useful would be to provide a "row factory" which would be capable of producing the required data for a new blank row, instead of just inserting a bunch of nulls, or at least I would find it useful.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;
public class TestTable {
public static void main(String[] args) {
new TestTable();
}
public TestTable() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MutableDefaultTableModel model = new MutableDefaultTableModel(4, 4);
JTable table = new JTable(model);
KeyStroke tabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
KeyStroke shiftTabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK);
KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
KeyStroke arrowLeftKey = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0);
KeyStroke arrowRightKey = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0);
KeyStroke arrowDownKey = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
KeyStroke arrowUpKey = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
InputMap im = table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(tabKey, "nextCell");
im.put(enterKey, "nextCell");
im.put(arrowRightKey, "nextCell");
im.put(shiftTabKey, "previousCell");
im.put(arrowLeftKey, "previousCell");
im.put(arrowDownKey, "nextRow");
im.put(arrowUpKey, "previousRow");
ActionMap am = table.getActionMap();
am.put("nextCell", new NextCellAction(table, model));
am.put("previousCell", new PreviousCellAction(table, model));
am.put("nextRow", new NextRowAction(table, model));
am.put("previousRow", new PreviousRowAction(table, model));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MutableDefaultTableModel extends DefaultTableModel implements MutableTableModel {
public MutableDefaultTableModel(int rows, int cols) {
super(rows, cols);
}
#Override
public void insertNewRow(int row) {
Object[] rowData = new Object[getColumnCount()];
if (row < getRowCount()) {
insertRow(row, rowData);
} else {
addRow(rowData);
}
}
}
public interface MutableTableModel extends TableModel {
public void insertNewRow(int row);
}
public abstract class AbstractTableAction extends AbstractAction {
private JTable table;
private MutableTableModel model;
private boolean forceStopEditing;
public AbstractTableAction(JTable table, MutableTableModel model) {
this.table = table;
this.model = model;
}
public MutableTableModel getModel() {
return model;
}
public JTable getTable() {
return table;
}
public boolean isForceStopEditing() {
return forceStopEditing;
}
public void setForceStopEditing(boolean forceStopEditing) {
this.forceStopEditing = forceStopEditing;
}
public void stopCellEditing() {
TableCellEditor editor = getTable().getCellEditor();
if (editor != null) {
if (!editor.stopCellEditing() && isForceStopEditing()) {
editor.cancelCellEditing();
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
JTable table = getTable();
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
int rowCount = table.getRowCount();
int colCount = table.getColumnCount();
stopCellEditing();
int cell[] = updateCellCoordinates(row, col);
row = cell[0];
col = cell[1];
if (col < 0) {
col = colCount - 1;
row--;
} else if (col >= colCount) {
col = 0;
row++;
}
if (row < 0) {
row = 0;
} else if (row >= rowCount) {
getModel().insertNewRow(row);
}
table.setRowSelectionInterval(row, row);
table.setColumnSelectionInterval(col, col);
}
protected abstract int[] updateCellCoordinates(int row, int col);
}
public class NextCellAction extends AbstractTableAction {
public NextCellAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{row, ++col};
}
}
public class PreviousCellAction extends AbstractTableAction {
public PreviousCellAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{row, --col};
}
}
public class NextRowAction extends AbstractTableAction {
public NextRowAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{++row, col};
}
}
public class PreviousRowAction extends AbstractTableAction {
public PreviousRowAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{--row, col};
}
}
}

row selection not moving with row moves in JTable

I have following code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
import javax.swing.table.DefaultTableModel;
public class NewClass1 extends JFrame {
private JTable table;
private JScrollPane scrollPane;
private DefaultTableModel defaultTableModel;
public NewClass1() {
setLocationByPlatform(true);
setLayout(new BorderLayout());
setPreferredSize(new Dimension(600, 400));
setTitle("Table Issues");
setDefaultCloseOperation(EXIT_ON_CLOSE);
createTableModel();
table = new JTable(defaultTableModel);
scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
pack();
}
private void createTableModel() {
Vector cols = new Vector();
cols.add("A");
Vector rows = new Vector();
for (int i = 0; i < 50; i++) {
Vector row = new Vector();
row.add((i + 1) + "");
rows.add(row);
}
defaultTableModel = new DefaultTableModel(rows, cols) {
Class[] types = new Class[]{
String.class
};
#Override
public Class getColumnClass(int columnIndex) {
return types[columnIndex];
}
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(new NimbusLookAndFeel());
} catch (Exception e) {
}
final NewClass1 nc = new NewClass1();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
nc.setVisible(true);
}
});
while (true) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int row = (int) (Math.random() * 50);
int move = (int) (Math.random() * 50);
nc.defaultTableModel.moveRow(row, row, move);
}
});
try{
Thread.sleep(1000);
}catch(Exception e){
}
}
}
}
Please run the above code and select row.
My problem is with row movement, row selection is not moving. It is staying at fixed position. Suppose I selected row with column value 25, selected row must be of column value 25 after row movements.
Please help me on this.
My real problem is, user will select row and clicks menu to perform action, meanwhile other threads may have moved rows, and performed action will be on other row than actual one.
The easiest way is to remember the selected row somewhere outside of the ListSelectionModel and adjust the selection whenever the TableModel changes. For example you could do this:
public class NewClass1 extends JFrame {
private JTable table;
private DefaultTableModel defaultTableModel;
private JScrollPane scrollPane;
private class SelectionHelper implements ListSelectionListener, TableModelListener {
private Object selectedRow;
#Override
public void valueChanged(ListSelectionEvent event) {
if (!event.getValueIsAdjusting()) return;
int selectedIndex = table.getSelectedRow();
if (selectedIndex >= 0) {
selectedRow = defaultTableModel.getDataVector().get(selectedIndex);
} else {
selectedRow = null;
}
}
#Override
public void tableChanged(TableModelEvent event) {
if (selectedRow == null) return;
int selectedIndex = defaultTableModel.getDataVector().indexOf(selectedRow);
table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex);
}
}
public NewClass1() {
// ...
createTableModel();
table = new JTable(defaultTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
SelectionHelper helper = new SelectionHelper();
table.getModel().addTableModelListener(helper);
table.getSelectionModel().addListSelectionListener(helper);
// ...
}
// ...
}
Note however, that you should adjust this code for production use, for example in regards to thread safety or portability (using the table and defaultTableModel attributes in the inner class is bad style).

JTable horizontal scrollbar based on width of one column

I am running into a problem that has been discussed on here before: getting a JScrollPane containing a JTable to display the horizontal scrollbar as I desire. HERE is a post I tried following, but for some reason it does not seem to work with my situation (I think it is because I've set one of my columns to have a fixed width.
Here is a SSCCE:
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
#SuppressWarnings("serial")
public class TableScrollTest extends JFrame
{
public TableScrollTest() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"key", "value"},0);
model.addRow(new Object[]{"short", "blah"});
model.addRow(new Object[]{"long", "blah blah blah blah blah blah blah"});
JTable table = new JTable(model) {
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().width < getParent().getWidth();
}
};
table.getColumn("key").setPreferredWidth(60);
table.getColumn("key").setMinWidth(60);
table.getColumn("key").setMaxWidth(60);
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args) {
TableScrollTest frame = new TableScrollTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setSize(200, 200);
frame.setResizable(false);
frame.setVisible(true);
}
}
In short, I have a two column table for displaying key/value pairs. The container holding the table is of fixed width, and the first column of the table is also of fixed width (since I know how long all the key names will be). The second column will contain values of varying string length. A horizontal scrollbar should only appear when there are values present which are too long to fit in the width allotted for the column.
Because the second value above has such a long length, the scrollbar should be visible. However, it's not, and everything I have tried has only succeeded in getting it visible always, which is not what I want... I only want it visible if "long" values are present. It seems like the getScrollableTracksViewportWidth() method being overridden in the table constructor checks what the preferred width of the table is... so somehow I need to direct the table to prefer a larger width based on the contents of that second column only... but I'm stumped.
Any ideas?
This is a hackey solution
Basically, what it does is calculates the "preferred" width of all the columns based on the values from all the rows.
It takes into consideration changes to the model as well as changes to the parent container.
Once it's done, it checks to see if the "preferred" width is greater or less than the available space and sets the trackViewportWidth variable accordingly.
You can add in checks for fixed columns (I've not bothered) which would make the process "slightly" faster, but this is going to suffer as you add more columns and rows to the table, as each update is going to require a walk of the entire model.
You could put some kind of cache in, but right about then, I'd be considering fixed width columns ;)
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class TableScrollTest extends JFrame {
public TableScrollTest() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"key", "value"}, 0);
model.addRow(new Object[]{"short", "blah"});
model.addRow(new Object[]{"long", "blah blah blah blah blah blah blah"});
JTable table = new JTable(model) {
private boolean trackViewportWidth = false;
private boolean inited = false;
private boolean ignoreUpdates = false;
#Override
protected void initializeLocalVars() {
super.initializeLocalVars();
inited = true;
updateColumnWidth();
}
#Override
public void addNotify() {
super.addNotify();
updateColumnWidth();
getParent().addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
invalidate();
}
});
}
#Override
public void doLayout() {
super.doLayout();
if (!ignoreUpdates) {
updateColumnWidth();
}
ignoreUpdates = false;
}
protected void updateColumnWidth() {
if (getParent() != null) {
int width = 0;
for (int col = 0; col < getColumnCount(); col++) {
int colWidth = 0;
for (int row = 0; row < getRowCount(); row++) {
int prefWidth = getCellRenderer(row, col).
getTableCellRendererComponent(this, getValueAt(row, col), false, false, row, col).
getPreferredSize().width;
colWidth = Math.max(colWidth, prefWidth + getIntercellSpacing().width);
}
TableColumn tc = getColumnModel().getColumn(convertColumnIndexToModel(col));
tc.setPreferredWidth(colWidth);
width += colWidth;
}
Container parent = getParent();
if (parent instanceof JViewport) {
parent = parent.getParent();
}
trackViewportWidth = width < parent.getWidth();
}
}
#Override
public void tableChanged(TableModelEvent e) {
super.tableChanged(e);
if (inited) {
updateColumnWidth();
}
}
public boolean getScrollableTracksViewportWidth() {
return trackViewportWidth;
}
#Override
protected TableColumnModel createDefaultColumnModel() {
TableColumnModel model = super.createDefaultColumnModel();
model.addColumnModelListener(new TableColumnModelListener() {
#Override
public void columnAdded(TableColumnModelEvent e) {
}
#Override
public void columnRemoved(TableColumnModelEvent e) {
}
#Override
public void columnMoved(TableColumnModelEvent e) {
if (!ignoreUpdates) {
ignoreUpdates = true;
updateColumnWidth();
}
}
#Override
public void columnMarginChanged(ChangeEvent e) {
if (!ignoreUpdates) {
ignoreUpdates = true;
updateColumnWidth();
}
}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
return model;
}
};
table.getColumn("key").setPreferredWidth(60);
// table.getColumn("key").setMinWidth(60);
// table.getColumn("key").setMaxWidth(60);
// table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
TableScrollTest frame = new TableScrollTest();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setSize(200, 200);
frame.setResizable(true);
frame.setVisible(true);
}
});
}
}
It seems then that my goal is to force the cell renderer to automatically make cells fit long strings
This isn't the job of the renderer. You must manually set the width of the columns.
See Table Column Adjuster for one way to do this.
I won't necessarily accept this answer if something smarter is posted, but here is a solution I figured out on my own based on comments posted so far and THIS post. The only catch is that the width that I was coming up with was always ~1 pixel too short (in some look-and-feel's it was ok), so I added the line near the end width += 5 which seems to make it work ok, but it feels hacky to me. Would welcome any comments on this approach.
import java.awt.Component;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
#SuppressWarnings("serial")
public class TableScrollTest extends JFrame {
public TableScrollTest() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"key", "value"},0);
model.addRow(new Object[]{"short", "blah"});
model.addRow(new Object[]{"long", "blah blah blah blah blah blah blah"});
JTable table = new JTable(model);
table.getColumn("key").setPreferredWidth(60);
table.getColumn("key").setMinWidth(60);
table.getColumn("key").setMaxWidth(60);
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
int width = 0;
for (int row = 0; row < table.getRowCount(); row++) {
TableCellRenderer renderer = table.getCellRenderer(row, 1);
Component comp = table.prepareRenderer(renderer, row, 1);
width = Math.max (comp.getPreferredSize().width, width);
}
width += 5;
table.getColumn("value").setPreferredWidth(width);
table.getColumn("value").setMinWidth(width);
table.getColumn("value").setMaxWidth(width);
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args) {
TableScrollTest frame = new TableScrollTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setSize(200, 200);
frame.setResizable(false);
frame.setVisible(true);
}
}

Widgets in JTable cells

Widgets in JTable columns are expected to be not distinguishable from normal ones, right? There seems to be behavioral difference, take Swing documentation example and move mouse over checkboxes in the Vegetarian column... They don't react at all. I understand that those are just widget surrogates, so highlighting has to be done manually, so how would I fix this? I tried widget.requestFocusInWindow();
in mouseMoved() for the surrogate widget event handler without success. Any other workaround?
You can create your own cell renderer that applies a rollover effect. Then, add a mouse listener that tracks the mouse movement and repaints the relevant cells. You need to apply the effect to the current cell under the cursor and clear the previous cell.
Below is a short example that demonstrates this approach on a checkbox renderer. The example extends default BooleanRenderer. The only change is getModel().setRollover(...) in getTableCellRendererComponent().
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class TableRolloverDemo {
private static void createAndShowGUI() {
JFrame frame = new JFrame("TableRolloverDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTable table = new JTable();
final DefaultTableModel model = new DefaultTableModel(new Object[][] {
{ false }, { false }, { true }, { true } },
new Object[] { "Column" }) {
public Class<?> getColumnClass(int columnIndex) {
return Boolean.class;
}
};
RolloverMouseAdapter rolloverAdapter = new RolloverMouseAdapter(table);
RolloverBooleanRenderer renderer = new RolloverBooleanRenderer(rolloverAdapter);
table.addMouseListener(rolloverAdapter);
table.addMouseMotionListener(rolloverAdapter);
table.setDefaultRenderer(Boolean.class, renderer);
table.setModel(model);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
static class RolloverMouseAdapter extends MouseAdapter {
private int row = -1;
private int column = -1;
private JTable table;
public RolloverMouseAdapter(JTable table) {
this.table = table;
}
public boolean isRolloverCell(int row, int column) {
return this.row == row && this.column == column;
}
#Override
public void mouseMoved(MouseEvent e) {
int lastRow = row;
int lastColumn = column;
row = table.rowAtPoint(e.getPoint());
column = table.columnAtPoint(e.getPoint());
if (row == lastRow && column == lastColumn)
return;
if (row >= 0 && column >= 0) {
table.repaint(table.getCellRect(row, column, false));
}
if (lastRow >= 0 && lastColumn >= 0) {
table.repaint(table.getCellRect(lastRow, lastColumn, false));
}
}
#Override
public void mouseExited(MouseEvent e) {
if (row >= 0 && column >= 0) {
table.repaint(table.getCellRect(row, column, false));
}
row = column = -1;
}
}
static class RolloverBooleanRenderer extends JCheckBox implements
TableCellRenderer, UIResource {
private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
private RolloverMouseAdapter adapter;
public RolloverBooleanRenderer(RolloverMouseAdapter adapter) {
super();
this.adapter = adapter;
setHorizontalAlignment(JLabel.CENTER);
setBorderPainted(true);
}
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
getModel().setRollover(adapter.isRolloverCell(row, column));
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setSelected((value != null && ((Boolean) value).booleanValue()));
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
} else {
setBorder(noFocusBorder);
}
return this;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
It's rollover (or lack of thereof) which makes widgets in the table to appear dead. If JPanel is genuine container of widgets, why JTable and JTree are not?
Both JTable and JTree use the flyweight pattern for rendering. The default renderer & editor have no intrinsic mouse-over behavior. You have to supply the desired behavior yourself, as #Max shows.
Jtable dont put real components into cells. They only use the component's paint methods to render the cell content.

Categories