I´m rather new to Java and programming itself, so excuse me for the question. What I´m trying to do is the following:
I´m making a bookkeeping program. On the column where the income/outcome is displayed, I want it so when the user enters a negative number (eg. -1.150€),
the number turns red( or any color really, but red is what most bookkeeping programs use). only that specific cell on that column only. I have not started with a code yet so I cannot input one here. I also do not need it to be right-aligned since I have already done that.
PS. Sorry if this post/question already exists, I searched but I found nothing that could help me much.
A small example with double values in a single column. This version uses JTable.setDefaultRenderer for Double.class.
You can also set colors
From an override of JTable.prepareRenderer
From a renderer set individually for columns by calling TableColumn.setCellRenderer; TableColumn instances can be retrieved from the TableColumnModel
import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
#SuppressWarnings("serial")
public class TableWithColors {
protected static JTable createTable() {
Object[][] rows = new Object[][] {{1.23d},{-20.5d},{5.87d},{2.23d},{-7.8d},{-8.99d},{9d},{16.25d},{4.23d},{-26.22d},{-14.14d}};
Object[] cols = new Object[]{"Balance"};
JTable t = new JTable(rows,cols) {
#Override
public Class<?> getColumnClass(int column) {
if(convertColumnIndexToModel(column)==0) return Double.class;
return super.getColumnClass(column);
}
};
t.setDefaultRenderer(Double.class, new DefaultTableCellRenderer(){
#Override
public Component getTableCellRendererComponent(JTable table,Object value,boolean isSelected,boolean hasFocus,int row,int column) {
Component c = super.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);
c.setForeground(((Double) value)>0 ? Color.BLUE : Color.RED);
return c;
}
});
return t;
}
private static JFrame createFrame() {
JFrame f = new JFrame("Table with colors");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
f.add(new JScrollPane(createTable()),BorderLayout.CENTER);
f.setSize(new Dimension(60,255));
return f;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createFrame().setVisible(true);
}
});
}
}
Goes like:
Related
How can I obtain a multiline JTable header where the header column correctly enlarges to fit some text and then wraps to a new line?
Something like shown below:
Currently searching for the above requirements returns a lot of solutions of which none really solves the problem:
http://www.javarichclient.com/multiline-column-header/
Creating multi-line header for JTable
Java JTable header word wrap
The above solutions all propose using HTML code, for instance:
String[] columnNames = {
"<html><center>Closing<br>Date</html>",
"<html><center>Open<br>Price</html>",
"<html>Third<br>column</html>"
};
That solution is not elegant for a couple of reasons, mainly because in the case of variable columns names I need to pass the string to a function which strips spaces and subtitutes them with <br> symbols, however if the column text contains very short text that appears in a line of its own.
I would need to decide a minimum and a maximum length of a column and then be able to make text centering possible, the above solution quickly becomes overengineered and unmanageable.
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderTable.htm
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderExample.htm
Above solutions require manually creating a header array with words already correctly split up as in:
public static Object[][] tableHeaders = new Object[][] {
new String[] { "Currency" },
new String[] { "Yesterday's", "Rate" },
new String[] { "Today's", "Rate" },
new String[] { "Rate", "Change" } };
-or-
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(
new Object[][] { { "a", "b", "c" }, { "A", "B", "C" } },
new Object[] { "1st\nalpha", "2nd\nbeta", "3rd\ngamma" });
Still not elegant because variable text in the column names would not be feasible.
How to change JTable header height?
Manually setting the header height as in the above solutions is only half of what I want to do, because then text would still not correctly wrap and deciding the height is still not feasible.
Currently all I was able was to create a custom TableCellRenderer but yet no solution:
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.table.*;
/**
* #version 1.0 11/09/98
*/
public class MultiLineHeaderExample extends JFrame
{
MultiLineHeaderExample()
{
super("Multi-Line Header Example");
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(new Object[][]
{
{
"a", "b", "c"
},
{
"A", "B", "C"
}
},
new Object[]
{
"My First Column, Very Long But Space Separated", "short col", "VeryLongNoSpaceSoShouldSomeHowWrap"
});
JTable table = new JTable(dm);
MultiLineHeaderRenderer renderer = new MultiLineHeaderRenderer();
Enumeration enumK = table.getColumnModel().getColumns();
while (enumK.hasMoreElements())
{
((TableColumn) enumK.nextElement()).setHeaderRenderer(renderer);
}
JScrollPane scroll = new JScrollPane(table);
getContentPane().add(scroll);
setSize(400, 110);
setVisible(true);
}
public static void main(String[] args)
{
MultiLineHeaderExample frame = new MultiLineHeaderExample();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
class MultiLineHeaderRenderer extends JList implements TableCellRenderer
{
public MultiLineHeaderRenderer()
{
ListCellRenderer renderer = getCellRenderer();
((JLabel) renderer).setHorizontalAlignment(JLabel.CENTER);
setCellRenderer(renderer);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
BufferedReader br = new BufferedReader(new StringReader(str));
String line;
Vector v = new Vector();
try
{
while ((line = br.readLine()) != null)
{
v.addElement(line);
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
setListData(v);
return this;
}
}
This here also uses JTextArea and also resizes the header height when the table is resized. The key to the correct calculation of the table header height is setSize(width, getPreferredSize().height);
class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
public MultiLineTableHeaderRenderer() {
setEditable(false);
setLineWrap(true);
setOpaque(false);
setFocusable(false);
setWrapStyleWord(true);
LookAndFeel.installBorder(this, "TableHeader.cellBorder");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int width = table.getColumnModel().getColumn(column).getWidth();
setText((String)value);
setSize(width, getPreferredSize().height);
return this;
}
}
you need a Conponent that is able to wordwrap its content like JTextArea.
I changed the cell renderer from your SSCCE so that is works initially, but it has a nasty resize behavior.
class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
public MultiLineHeaderRenderer()
{
setAlignmentY(JLabel.CENTER);
setLineWrap(true);
setWrapStyleWord(true);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLACK),
BorderFactory.createEmptyBorder(3,3,3,3)
));
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
setText(str);
int columnWidth= getColumnWidth();
setRows(str.length()/columnWidth);
return this;
}
}
Here is another approach. This solution has the following advantages:
You need not manually break the column names.
The columns dynamically word-wrap as you resize the columns and/or window.
The header appearance will automatically be consistent with the installed look-and-feel.
Unlike other solutions I have seen, this works even if the first column doesn't wrap (as in the example below).
It has the following disadvantage, however: It creates an unused JTableHeader object for each column, so it's a bit inelegant and probably not suitable if you have many columns.
The basic idea is that you wrap the column names in an <html> tags, and, crucially, every TableColumn gets its own TableCellRenderer object.
I came to this solution after debugging deep into the guts of the Swing table header layout plumbing. Without getting too much into the weeds, the problem is that if the TableColumns don't have a headerRenderer defined, the same default renderer is used for every column header cell. The layout code used for JTableHeader only bothers to ask the renderer of the first column header for its preferred size (see feature 4. above), and because the renderer is re-used, the call to its setText() method triggers the creation of a new View for the label, which, for reasons I'm too tired to even think about explaining, causes the header renderer to always report its preferred unwrapped height.
Here is a quick-and-dirty proof-of-concept:
package scratch;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
#SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {
class DemoTableModel extends AbstractTableModel {
private ArrayList<String> wrappedColumnNames = new ArrayList<String>();
private int numRows;
DemoTableModel(List<String> columnNames, int numRows) {
for (String name: columnNames)
wrappedColumnNames.add("<html>" + name + "</html>");
this.numRows = numRows;
}
public int getRowCount() {
return numRows;
}
public int getColumnCount() {
return wrappedColumnNames.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
}
public String getColumnName(int column) {
return wrappedColumnNames.get(column);
}
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
}
public WordWrappingTableHeaderDemo() {
DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
public void addColumn(TableColumn column) {
// This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
super.addColumn(column);
}
};
JTable table = new JTable();
table.setFillsViewportHeight(true);;
table.setColumnModel(tableColumnModel);
table.setModel(
new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
getContentPane().add(new JScrollPane(table));
}
public static void createAndShowGUI() {
WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setLocationByPlatform(true);
app.pack();
app.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {createAndShowGUI();});
}
}
How can I obtain a multiline JTable header where the header column correctly enlarges to fit some text and then wraps to a new line?
Something like shown below:
Currently searching for the above requirements returns a lot of solutions of which none really solves the problem:
http://www.javarichclient.com/multiline-column-header/
Creating multi-line header for JTable
Java JTable header word wrap
The above solutions all propose using HTML code, for instance:
String[] columnNames = {
"<html><center>Closing<br>Date</html>",
"<html><center>Open<br>Price</html>",
"<html>Third<br>column</html>"
};
That solution is not elegant for a couple of reasons, mainly because in the case of variable columns names I need to pass the string to a function which strips spaces and subtitutes them with <br> symbols, however if the column text contains very short text that appears in a line of its own.
I would need to decide a minimum and a maximum length of a column and then be able to make text centering possible, the above solution quickly becomes overengineered and unmanageable.
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderTable.htm
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderExample.htm
Above solutions require manually creating a header array with words already correctly split up as in:
public static Object[][] tableHeaders = new Object[][] {
new String[] { "Currency" },
new String[] { "Yesterday's", "Rate" },
new String[] { "Today's", "Rate" },
new String[] { "Rate", "Change" } };
-or-
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(
new Object[][] { { "a", "b", "c" }, { "A", "B", "C" } },
new Object[] { "1st\nalpha", "2nd\nbeta", "3rd\ngamma" });
Still not elegant because variable text in the column names would not be feasible.
How to change JTable header height?
Manually setting the header height as in the above solutions is only half of what I want to do, because then text would still not correctly wrap and deciding the height is still not feasible.
Currently all I was able was to create a custom TableCellRenderer but yet no solution:
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.table.*;
/**
* #version 1.0 11/09/98
*/
public class MultiLineHeaderExample extends JFrame
{
MultiLineHeaderExample()
{
super("Multi-Line Header Example");
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(new Object[][]
{
{
"a", "b", "c"
},
{
"A", "B", "C"
}
},
new Object[]
{
"My First Column, Very Long But Space Separated", "short col", "VeryLongNoSpaceSoShouldSomeHowWrap"
});
JTable table = new JTable(dm);
MultiLineHeaderRenderer renderer = new MultiLineHeaderRenderer();
Enumeration enumK = table.getColumnModel().getColumns();
while (enumK.hasMoreElements())
{
((TableColumn) enumK.nextElement()).setHeaderRenderer(renderer);
}
JScrollPane scroll = new JScrollPane(table);
getContentPane().add(scroll);
setSize(400, 110);
setVisible(true);
}
public static void main(String[] args)
{
MultiLineHeaderExample frame = new MultiLineHeaderExample();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
class MultiLineHeaderRenderer extends JList implements TableCellRenderer
{
public MultiLineHeaderRenderer()
{
ListCellRenderer renderer = getCellRenderer();
((JLabel) renderer).setHorizontalAlignment(JLabel.CENTER);
setCellRenderer(renderer);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
BufferedReader br = new BufferedReader(new StringReader(str));
String line;
Vector v = new Vector();
try
{
while ((line = br.readLine()) != null)
{
v.addElement(line);
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
setListData(v);
return this;
}
}
This here also uses JTextArea and also resizes the header height when the table is resized. The key to the correct calculation of the table header height is setSize(width, getPreferredSize().height);
class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
public MultiLineTableHeaderRenderer() {
setEditable(false);
setLineWrap(true);
setOpaque(false);
setFocusable(false);
setWrapStyleWord(true);
LookAndFeel.installBorder(this, "TableHeader.cellBorder");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int width = table.getColumnModel().getColumn(column).getWidth();
setText((String)value);
setSize(width, getPreferredSize().height);
return this;
}
}
you need a Conponent that is able to wordwrap its content like JTextArea.
I changed the cell renderer from your SSCCE so that is works initially, but it has a nasty resize behavior.
class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
public MultiLineHeaderRenderer()
{
setAlignmentY(JLabel.CENTER);
setLineWrap(true);
setWrapStyleWord(true);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLACK),
BorderFactory.createEmptyBorder(3,3,3,3)
));
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
setText(str);
int columnWidth= getColumnWidth();
setRows(str.length()/columnWidth);
return this;
}
}
Here is another approach. This solution has the following advantages:
You need not manually break the column names.
The columns dynamically word-wrap as you resize the columns and/or window.
The header appearance will automatically be consistent with the installed look-and-feel.
Unlike other solutions I have seen, this works even if the first column doesn't wrap (as in the example below).
It has the following disadvantage, however: It creates an unused JTableHeader object for each column, so it's a bit inelegant and probably not suitable if you have many columns.
The basic idea is that you wrap the column names in an <html> tags, and, crucially, every TableColumn gets its own TableCellRenderer object.
I came to this solution after debugging deep into the guts of the Swing table header layout plumbing. Without getting too much into the weeds, the problem is that if the TableColumns don't have a headerRenderer defined, the same default renderer is used for every column header cell. The layout code used for JTableHeader only bothers to ask the renderer of the first column header for its preferred size (see feature 4. above), and because the renderer is re-used, the call to its setText() method triggers the creation of a new View for the label, which, for reasons I'm too tired to even think about explaining, causes the header renderer to always report its preferred unwrapped height.
Here is a quick-and-dirty proof-of-concept:
package scratch;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
#SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {
class DemoTableModel extends AbstractTableModel {
private ArrayList<String> wrappedColumnNames = new ArrayList<String>();
private int numRows;
DemoTableModel(List<String> columnNames, int numRows) {
for (String name: columnNames)
wrappedColumnNames.add("<html>" + name + "</html>");
this.numRows = numRows;
}
public int getRowCount() {
return numRows;
}
public int getColumnCount() {
return wrappedColumnNames.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
}
public String getColumnName(int column) {
return wrappedColumnNames.get(column);
}
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
}
public WordWrappingTableHeaderDemo() {
DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
public void addColumn(TableColumn column) {
// This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
super.addColumn(column);
}
};
JTable table = new JTable();
table.setFillsViewportHeight(true);;
table.setColumnModel(tableColumnModel);
table.setModel(
new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
getContentPane().add(new JScrollPane(table));
}
public static void createAndShowGUI() {
WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setLocationByPlatform(true);
app.pack();
app.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {createAndShowGUI();});
}
}
I am trying to make a Checkbox change value on click in a JTable. Here is the code I use for that in the MouseListener
public void mouseClicked(MouseEvent e) {
Point mouse = e.getPoint();
int row = table.rowAtPoint(mouse);
int col = table.columnAtPoint(mouse);
if (col == 0) tableModel.setValueAt(new Boolean(!(Boolean) tableModel.getValueAt(row, col)), row, col);
}
The problem is, that when I sort the table, this happens
Here is an SSCCE
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
#SuppressWarnings("serial")
public class SSCCE extends JFrame {
JTable table;
public SSCCE() {
setSize(300, 200);
Object[][] data = { {false, "This is false"}, {true, "This is true"}};
table = new JTable(new CustomTableModel(data));
add(table);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
private class CustomTableModel extends AbstractTableModel {
Object[][] data;
public CustomTableModel(Object[][] data) {
this.data = data;
}
public Class<?> getColumnClass(int columnIndex) {
return data[0][columnIndex].getClass();
}
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new SSCCE();
}
});
}
}
Is there a way around this? Or a better method (not ListListener) to detect clicks on cells?
There is no need to use a MouseListener. You just need to use a proper editor for the column and the table will handle it for you.
Read the section from the Swing tutorial on How to Use Tables for more information and working examples.
Basically you need to do two things:
Add Boolean data to the TableModel
Override the getColumnClass(...) method of the TableModel to return Boolean.class for that column and the table will choose the appropriate editor.
Above is the answer for your question, but for future information the MouseEvent is relative to the table, so you want to use table methods to access the data. That is you would use table.getValueAt(...) and table.setValueAt(...). These reference the data as it is currently displayed in the view of the table. That is the view could be sorted or the column could have been moved.
I have implemented a JProgressBar in a JTable.
I used renderer for the ProgressBar NOT EDITOR.
Now I tried to implement a ProgressBar set value but due to EDT its not working so I used SwingUtilties but it did not work as well.
EXPECTED BEHAVIOUR - The JProgressBar must be setting value to 80 ,
currently it is showing only 0%
public class SkillSetTableProgressBarRenderer extends JProgressBar implements
TableCellRenderer {
public SkillSetTableProgressBarRenderer() {
super(0, 100);
super.setPreferredSize(new Dimension(100, 80));
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
final JProgressBar bar = (JProgressBar) value;
if (bar.getString().equals("JAVA") || bar.getString().equals("SWING"))
super.setString("Mellow");
else
super.setString("GOOD");
setOpaque(true);
table.setOpaque(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
System.err.println("MAIN ANDER");
setValue(80);
bar.setValue(80);
}
});
super.setStringPainted(true);
super.setIndeterminate(true);
super.setPreferredSize(new Dimension(140, 16));
if (isSelected) {
super.setBackground(table.getSelectionBackground());
super.setForeground(table.getSelectionForeground());
// this.setBackground(table.getSelectionBackground());
} else {
super.setForeground(table.getForeground());
super.setBackground(Color.WHITE);
}
return this;
}
}
Looks like you're trying to use a ProgressBar as a CellRenderer. One thing you should know, is that CellRenders are only called when a cell is being drawn, and setting values at other moments has no effect: this is because Swing uses a flyweight pattern for renderers, and thus reuses the renderer.
To get the effect you want, you should
notify the table that the cell is to be updated, for instance by updating its underlying model (that will fire the necessary events), and
set all values you need before the getTableCellRendererComponent return, so, remove the invokeLater (the getter is called on the EDT, so you don't need to worry about threading there).
1) even is possible, don't create JComponents inside TableCellRenderer, nor re_create Object, Renderer is only for formatting cell contents
2) use SwingWorker for moving with progress in JProgressBar (if you have real important reasons, then use Runnable#Tread)
example about Runnable#Tread
import java.awt.Component;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class TableWithProgressBars {
public static class ProgressRenderer extends JProgressBar implements TableCellRenderer {
private static final long serialVersionUID = 1L;
public ProgressRenderer(int min, int max) {
super(min, max);
this.setStringPainted(true);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
this.setValue((Integer) value);
return this;
}
}
private static final int maximum = 100;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TableWithProgressBars().createGUI();
}
});
}
public void createGUI() {
final JFrame frame = new JFrame("Progressing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Integer[] oneRow = {0, 0, 0, 0};
String[] headers = {"One", "Two", "Three", "Four"};
Integer[][] data = {oneRow, oneRow, oneRow, oneRow, oneRow,};
final DefaultTableModel model = new DefaultTableModel(data, headers);
final JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new ProgressRenderer(0, maximum));
table.setPreferredScrollableViewportSize(table.getPreferredSize());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(new Runnable() {
#Override
public void run() {
Object waiter = new Object();
synchronized (waiter) {
int rows = model.getRowCount();
int columns = model.getColumnCount();
Random random = new Random(System.currentTimeMillis());
boolean done = false;
while (!done) {
int row = random.nextInt(rows);
int column = random.nextInt(columns);
Integer value = (Integer) model.getValueAt(row, column);
value++;
if (value <= maximum) {
model.setValueAt(value, row, column);
try {
waiter.wait(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
done = true;
for (row = 0; row < rows; row++) {
for (column = 0; column < columns; column++) {
if (!model.getValueAt(row, column).equals(maximum)) {
done = false;
break;
}
}
if (!done) {
break;
}
}
}
frame.setTitle("All work done");
}
}
}).start();
}
}
Take a look at the Swing table tutorial: renderers and editors. The renderer uses the component to make a sort of 'stamp'. Changing the component afterwards has no effect since the component is not actually contained in your UI. That is one of the reasons you can return the same component over and over again, which would not be possible if the component was actually contained in the table. This is all explained in the linked tutorial.
To solve your problem, just remove the SwingUtilities.invokeLater method call, and return a JProgressBar where the correct progress is set.
And if I see this correctly, you have a TableModel containing a JProgressBar. I hope there is a good reason for this, since this is a UI component which should normally not be included on the model side. This is based on your
final JProgressBar bar = (JProgressBar) value;
call in the renderer. I would strongly suggest to take a look at your TableModel to see whether this can be avoided
I'm trying to use an empty column as a divider between pairs of columns in a JTable. Here's a picture and code for what I have so far. I know I can change the look using a custom TableCellRenderer. Before I go down that road, is there a better way to do this? Any ideas appreciated.
import javax.swing.*;
import javax.swing.table.*;
public class TablePanel extends JPanel {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("TablePanel");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.add(new TablePanel());
f.pack();
f.setVisible(true);
}
});
}
public TablePanel() {
TableModel dataModel = new MyModel();
JTable table = new JTable(dataModel);
table.getColumnModel().getColumn(MyModel.DIVIDER).setMaxWidth(0);
JScrollPane jsp = new JScrollPane(table);
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
this.add(jsp);
}
private static class MyModel extends AbstractTableModel {
private static final int DIVIDER = 2;
private final String[] names = { "A1", "A2", "", "B1", "B2" };
#Override
public int getRowCount() {
return 32;
}
#Override
public int getColumnCount() {
return names.length;
}
#Override
public String getColumnName(int col) {
if (col == DIVIDER) return "";
return names[col];
}
#Override
public Object getValueAt(int row, int col) {
if (col == DIVIDER) return "";
return (row + 1) / 10.0;
}
#Override
public Class<?> getColumnClass(int col) {
if (col == DIVIDER) return String.class;
return Number.class;
}
}
}
On problem with this approach it that the user will need to "tab over" the divider column. You could use the Table Tabbing suggestion to make it more user friendly.
Or if tabbing between the two tables isn't important, then maybe you can use use two tables and put whatever divider you want betweeen the two. The selection model can shared if required.
Edit:
As I suggested above sharing models is easier than writing custom listeners. To keep the scrolling in sync the code would be:
jspa.getVerticalScrollBar().setModel( jspb.getVerticalScrollBar().getModel() );
You can also do the same with the selection model so that highlighting of rows is in sync.
I kind of combined both answers: I used two tables that share one's scrollbar. This works with sorting, and it actually makes the model simpler. Tabbing doesn't matter, but comparing "A" and "B" does. I think I was trying to solve a "view" problem in the "model". I made this a separate answer, because I'd appreciate any critical comments.
public TablePanel() {
this.setLayout(new GridLayout(1, 0, 8, 0));
JTable tableA = new JTable(new MyModel("A"));
tableA.setPreferredScrollableViewportSize(new Dimension(200, 400));
final JScrollPane jspA = new JScrollPane(tableA);
jspA.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
this.add(jspA);
JTable tableB = new JTable(new MyModel("B"));
tableB.setPreferredScrollableViewportSize(new Dimension(200, 400));
final JScrollPane jspB = new JScrollPane(tableB);
jspB.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
this.add(jspB);
tableA.setSelectionModel(tableB.getSelectionModel());
jspA.getVerticalScrollBar().setModel(jspB.getVerticalScrollBar().getModel());
}
Without knowing what do you want to show in this table it's hard to tell whether you've selected good solution or not.
Regarding this solution. This column does not seem like a divider. Paint it with gray/another color, or paint divider header cell in white.
But anyway I'd prefer JScrollPane + two tables inside it instead of this solution.
Please have a look at the answer of this question, some nice new suggestion was given there: Column dividers in JTable or JXTable