JTable horizontal scrollbar based on width of one column - java

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);
}
}

Related

disable multi-column selection

Is there any way to disable selection of multiple columns for a Swing JTable? I've disabled selection all together in the "Tid" column by overriding the selection intervals of the selection model:
myTable.getColumnModel().setSelectionModel(new DefaultListSelectionModel() {
private boolean isSelectable(int index0, int index1) {
return index1 != 0;
}
#Override
public void setSelectionInterval(int index0, int index1) {
if(isSelectable(index0, index1)) {
super.setSelectionInterval(index0, index1);
}
}
#Override
public void addSelectionInterval(int index0, int index1) {
if(isSelectable(index0, index1)) {
super.addSelectionInterval(index0, index1);
}
}
});
And my guess is that one can also disallow the selection of multiple columns by overriding methods in the selection model. But I can't really figure out how to accomplish that.
Allowed selection
Disallowed selection
First get the TableColumnModel from the JTable
TableColumnModel columnModel = table.getColumnModel();
Next, get the LstSeletionModel for the TableColumnModel
ListSelectionModel selectionModel = columnModel.getSelectionModel();
With this, you could set the selectionMode that the model will use, for example
selectionModel.setSelectionModel(ListSelectionModel.SINGLE_SELECTION)
See the JavaDocs for ListSelectionModel and TableColumnModel for more details
Runnable example....
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(0, 10);
for (int row = 0; row < 10; row++) {
String[] data = new String[10];
for (int col = 0; col < 10; col++) {
data[col] = row + "x" + col;
}
model.addRow(data);
}
JTable table = new JTable(model);
table.setColumnSelectionAllowed(true);
table.setRowSelectionAllowed(true);
table.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(table));
}
}
}
Actually, it was a simple enough addition to my already existing overrides that was needed.
#Override
public void setSelectionInterval(int index0, int index1) {
if (isSelectable(index0, index1)) {
if (index0==index1) { //The if condition needed.
super.setSelectionInterval(index0, index1);
}
}
}
I realised upon reviewing the JavaDoc and the DefaultListSelectionModel that the index0 and index1 were just what I was looking for - the column span. So by doing the call to the superclass if and only if the two column indices are equal, selection of multiple columns is not possible.

How to Display Row Header on JTable Instead of Column Header

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...

Why showing path instead of icon in table cell

I am newbie on java-swing. I am trying to add icon in table cell. but when i add ImageIcon in cell then it's showing only path instead of icon.
Here is my code.
public void createGUI(ArrayList<String> params, String type) {
try {
DefaultTableModel model = new DefaultTableModel();
model.addColumn("ParameterName");
model.addColumn("ParameterType");
model.addColumn("Operation");
for (int i = 0; i < params.size() - 4; i++) {
String param_name = params.get(i).toString().substring(0, params.get(i).toString().indexOf("["));
String param_type = params.get(i).toString().substring(params.get(i).toString().indexOf("[") + 1, params.get(i).toString().indexOf("]"));
//URL url = ClassLoader.getSystemClassLoader().getResource("");
ImageIcon image = new ImageIcon("/com/soastreamer/resources/delete_idle.png");
// JLabel label = new JLabel(image);
model.addRow(new Object[]{param_name, param_type.toUpperCase(),image});
}
Action delete = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JTable table = (JTable) e.getSource();
int modelRow = Integer.valueOf(e.getActionCommand());
((DefaultTableModel) table.getModel()).removeRow(modelRow);
}
};
Here is image for clear understanding.
Please give me hint or any reference.
Thank you.
The problem lies with your TableModel, you have to tell the table that it has to render an image in that column overriding the getColumnClass(int column) method of the model.
Look at this answer by camickr.
UPDATE
Minimal example of a JTable with an ImageIcon using DefaultTableModel's renderer to paint it. I borrowed the updateRowHeights() code from this answer again by camickr, without it the icon was bigger than the row and wasn't fully displayed.
The important thing here is that now when the renderer calls getColumnClass(1), it gets ImageIcon.class so the code to render icons will be executed. By default this method would return Object.class and the renderer would ignore the fact that it's an icon.
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public class ImageIconTable
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ImageIconTable().initGUI();
}
});
}
public void initGUI()
{
JFrame frame = new JFrame();
DefaultTableModel tableModel = new DefaultTableModel()
{
#Override
public Class getColumnClass(int column)
{
if (column == 1) return ImageIcon.class;
return Object.class;
}
};
tableModel.addColumn("Row 1");
tableModel.addColumn("Icons Row");
tableModel.addRow(new Object[]{"This cell is an Object", new ImageIcon("icon.jpg")});
_table = new JTable(tableModel);
updateRowHeights();
frame.add(new JScrollPane(_table), BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private void updateRowHeights()
{
try
{
for (int row = 0; row < _table.getRowCount(); row++)
{
int rowHeight = _table.getRowHeight();
for (int column = 0; column < _table.getColumnCount(); column++)
{
Component comp = _table.prepareRenderer(_table.getCellRenderer(row, column), row, column);
rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
}
_table.setRowHeight(row, rowHeight);
}
}
catch(ClassCastException e) {}
}
private JTable _table;
}
It looks like this:
First, I suggest you to use ImageIo.read() and use the BufferedImage returned as argument for your ImageIcon object.
Second, use the Class.getResource() facility
YourClass.class.getResource("/com/soastreamer/resources/delete_idle.png");
Then, everything should work.

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).

How to show a message over leading selection cell in JTable?

I have a JTable with a custom ListSelectionModel, which contains a custom selection mode. This selection mode ensures that a selection is only made in one row, but with multiple cells. When a user clicks selects some cells in the table, the selected cells are all in the same row as the first selected cell.
Now I want to create a small message above the upper-right corner of the leading selection cell, which displays some data, i.e. the count of the selected cells. The message should be moved when the leading selection is changed.
But how do I do that? It is not a Tooltip as it is intended to be shown when user clicks into the table and selects cells and not by hovering over it.
Any suggestions?
best regards,
htz
Alternatively (not very pretty but works quite well), you can take advantage of the fact that JTable is a JComponent like all others and therefore you can add child components to it. All you have to do, is make sure to size and locate properly your component (this is only because JTable uses a null-layout).
Here is a small demo with a JLabel that displays the number of selected items. The label is automatically positioned on the first visible row, unless it is the current lead selection, in which case, the label is moved to the second visible row:
import java.awt.BorderLayout;
import java.awt.Point;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class TestTable {
protected void initUI() {
Vector<Vector<Object>> data = new Vector<Vector<Object>>();
Vector<String> colNames = new Vector<String>();
for (int i = 0; i < 5; i++) {
colNames.add("Col-" + (i + 1));
}
for (int i = 0; i < 200; i++) {
Vector<Object> row = new Vector<Object>();
for (int j = 0; j < 5; j++) {
row.add("Cell " + (i + 1) + "-" + (j + 1));
}
data.add(row);
}
table = new JTable(data, colNames);
someText = new JLabel();
someText.setOpaque(true);
table.add(someText);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
int count = table.getSelectedRowCount();
someText.setText("You currently have selected " + count + " item" + (count > 1 ? "s" : ""));
layoutLabel();
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
scrollpane = new JScrollPane(table);
scrollpane.getViewport().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
layoutLabel();
}
});
frame.add(scrollpane, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
private JLabel someText;
private JTable table;
private JScrollPane scrollpane;
private void layoutLabel() {
someText.setSize(someText.getPreferredSize());
Point location = scrollpane.getViewport().getViewRect().getLocation();
int leadSelectionIndex = table.getSelectionModel().getLeadSelectionIndex();
if (leadSelectionIndex > -1) {
if (table.rowAtPoint(location) == leadSelectionIndex) {
location.y += table.getRowHeight(leadSelectionIndex);
}
}
someText.setLocation(location);
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestTable().initUI();
}
});
}
}
you can to set Locations for ToolTip with the similair effect like as in MsExcell,
it is not a Tooltip as it is intended to be shown when user clicks into the table and selects cells and not by hovering over it.
you can to use JPopup / JWindow instead of ToolTip, for non_editable contens
JPopup / JWindow by default can't contains editable JComponent (JTextComponents)
for user input you can to use undecorated JDialod only
Yet another option is to implement a per-component glassPane (one of the many roles of JLayer for 1.7, JXLayer for 1.6) like shown below. Note that I didn't try to prettify the location (as #Guillaume did). Also, you'll have to modify the Rob's DragLayout a bit to guarantee that the box is shown inside the table area.
public static class ToolTipUI extends LayerUI<JTable> {
private JLayer<JTable> layer;
private JToolTip toolTip;
#Override
public void installUI(JComponent c) {
super.installUI(c);
this.layer = (JLayer) c;
installGlassPane();
installListeners();
}
private void installListeners() {
ListSelectionListener l = new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) return;
updateToolTip();
}
};
getTable().getColumnModel().getSelectionModel().addListSelectionListener(l);
getTable().getSelectionModel().addListSelectionListener(l);
}
private void updateToolTip() {
int[] selectedColumns = getTable().getColumnModel().getSelectedColumns();
int selectedRow = getTable().getSelectedRow();
if (selectedRow < 0 || selectedColumns.length == 0) {
setToolTipText("");
} else {
String text = "selected cells: ";
for (int i = 0; i < selectedColumns.length; i++) {
text += " " + selectedColumns[i];
}
setToolTipText(text);
}
}
private void setToolTipText(String string) {
toolTip.setTipText(string);
Rectangle cellBounds = getTable().getCellRect(getTable().getSelectedRow(), 0, false);
toolTip.setLocation(cellBounds.getLocation());
doLayout(layer);
}
#Override
public void doLayout(JLayer<? extends JTable> l) {
super.doLayout(l);
l.getGlassPane().doLayout();
}
private JTable getTable() {
return layer.getView();
}
private void installGlassPane() {
toolTip = ((JComponent) layer.getView()).createToolTip();
layer.getGlassPane().setBorder(BorderFactory.createLineBorder(Color.RED));
// DragLayout by Rob Camick http://tips4java.wordpress.com/2011/10/23/drag-layout/
layer.getGlassPane().setLayout(new DragLayout());
layer.getGlassPane().add(toolTip);
layer.getGlassPane().setVisible(true);
}
}
// usage:
JTable table = new JTable(new AncientSwingTeam());
JLayer layer = new JLayer<JTable>(table, new ToolTipUI());

Categories