JTable - Problems with Boolean.class Cell Renderer and Nimbus Look and Feel - java

I am using a JTable to visualize some data. One column ist destined to show boolean data through a checkbox. I achieved this by returning Boolean.class from my overriden getColumnClass() function in my table model.
Unfortunately this results in a cell with a checkbox but without a background color appropriate for the current row.
I fixed this by using the answer from this post: JTable - Boolean Cell Type - Background
Now I was trying to increase the contrast of the alternate rows. I achieved this by setting the appropriate property of the Nimbus LAF, which I am using.
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
defaults.put("Table.alternateRowColor", new Color(217, 217, 217));
As you see, the background of the Boolean cells is still the old Nimbus Table.alternateRowColor color.
Is there a way to change this? Am I doing this completely wrong? Is there a better way to achieve alternating background color and more contrast?
EDIT
caused on
java version "1.7.0_17" Java(TM) SE Runtime Environment (build
1.7.0_17-b02) Java HotSpot(TM) Server VM (build 23.7-b01, mixed mode), OS is Ubuntu 12.04

I was (finally) able to get it to work. The secret was to change the defaults BEFORE you create anything.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class TestTable10 {
public static void main(String[] args) {
new TestTable10();
}
public TestTable10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
UIManager.getLookAndFeelDefaults().put("Table.alternateRowColor", Color.RED);
JTable table = new JTable(new MyModel());
((JComponent) table.getDefaultRenderer(Boolean.class)).setOpaque(true);
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 MyModel extends AbstractTableModel {
#Override
public int getRowCount() {
return 10;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch (columnIndex) {
case 0:
return "Hello";
case 1:
return true;
}
return "?";
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? String.class : Boolean.class;
}
}
}

I'd be to use standard Renderer concept for this job, instead of playing with Nimbus Constants
Renderer works for Nimbus, override all Colors, excluding JTableHeader
code based on #camickrs Table Row Rendering
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableRowRenderingTip extends JPanel {
private static final long serialVersionUID = 1L;
public TableRowRenderingTip() {
Object[] columnNames = {"Type", "Company", "Shares", "Price", "Boolean"};
Object[][] data = {
{"Buy", "IBM", new Integer(1000), new Double(80.5), Boolean.TRUE},
{"Sell", "Dell", new Integer(2000), new Double(6.25), Boolean.FALSE},
{"Short Sell", "Apple", new Integer(3000), new Double(7.35), Boolean.TRUE},
{"Buy", "MicroSoft", new Integer(4000), new Double(27.50), Boolean.FALSE},
{"Short Sell", "Cisco", new Integer(5000), new Double(20), Boolean.TRUE}
};
DefaultTableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
#Override
public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Alternating", createAlternating(model));
add(tabbedPane);
}
private JComponent createAlternating(DefaultTableModel model) {
JTable table = new JTable(model) {
private static final long serialVersionUID = 1L;
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if (!isRowSelected(row)) { // Alternate row color
c.setBackground(row % 2 == 0 ? getBackground() : Color.orange);
}
return c;
}
};
table.setPreferredScrollableViewportSize(table.getPreferredSize());
((JComponent) table.getDefaultRenderer(Boolean.class)).setOpaque(true);
return new JScrollPane(table);
}
public static void main(String[] args) {
try {
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame.setDefaultLookAndFeelDecorated(false);
JFrame frame = new JFrame("Table Row Rendering");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TableRowRenderingTip());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

to solve this problem I used jxtable() instead jtable() and I used own prepareRenderer for row colors (you can use mKorbel's one, to put it to table in netbeans just Customize code for jxtable() component), because this solution: JTable - Boolean Cell Type - Background does not work for multi color rows for me. My platform: Windows 7 32bit, java version "1.7.0_21",
Java(TM) SE Runtime Environment (build 1.7.0_21-b11),
Java HotSpot(TM) Client VM (build 23.21-b01, mixed mode, sharing), netbeans IDE 7.3
Here is png (dont have enought reputation :D): jxtable().

Immediately after setting Nimbus L&F, add this lines:
UIManager.getLookAndFeelDefaults().put("Table:\"Table.cellRenderer\".background", Color.DARK_GRAY);
UIManager.getLookAndFeelDefaults().put("Table.background",new ColorUIResource(Color.DARK_GRAY));
UIManager.getLookAndFeelDefaults().put("Table.alternateRowColor",Color.DARK_GRAY.brighter());
Note usage of ColorUIResource for Table.background.
This fixed the checkbox background issue for me.

Related

JTable: send key event to all selected components to check all selected checkboxes

I have JTable with checkboxes (because getColumnClass is Boolean).
User selects several rows, and clicks space but only "lead selection" checkbox gets KeyEvent and becomes checked.
How can I check all selected checkboxes?
It looks like a very simple task, but I can't find "official" way to do that, and I do not want to use hacks.
I can override processKeyEvent in my table and physically set true to all values in model, but it doesn't look good.
I followed #camickr and #Jean Willian S. J 's suggestion, and did:
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.lang.reflect.InvocationTargetException;
public class TableTest {
private static final int CHECKBOX_COL = 0;
public static void main(String[] args) throws InvocationTargetException, InterruptedException {
SwingUtilities.invokeAndWait(() -> {
var frame = new JFrame("TableTest");
Object[][] data = {new Object[]{true, "Foo"}, new Object[]{false, "Buz"}};
MyModel dm = new MyModel(data, new Object[]{"Enabled", "Name"});
var table = new JTable(dm);
var invertSelected = "InvertSelected";
table.getInputMap().remove(KeyStroke.getKeyStroke("SPACE"));
table.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), invertSelected);
table.getActionMap().put(invertSelected, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
TableModel model = table.getModel();
for (int selectedRow : table.getSelectedRows()) {
var row = table.convertRowIndexToModel(selectedRow);
var value = !(Boolean) model.getValueAt(row, CHECKBOX_COL);
model.setValueAt(value, row, CHECKBOX_COL);
}
}
});
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
frame.getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
});
}
private static class MyModel extends DefaultTableModel {
public MyModel(Object[][] data, Object[] columnNames) {
super(data, columnNames);
}
#Override
public boolean isCellEditable(int row, int column) {
return column == CHECKBOX_COL;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return (columnIndex == CHECKBOX_COL ? Boolean.class : String.class);
}
}
}
It now works. Thank you, everyone

How to obtain JTable header text on two line [duplicate]

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

JTable header text wrapping for multiline header (custom TableCellRenderer)

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

Bug in popping up JComboBox and JPopup

I am having a bug I'm having trouble tracking down and was wondering if anyone had seen anything like this. I am building my code in Java 8. I run/test this code on both Macintosh & Windows. This bug only happens on a machine running Windows OS. (Haven't tried linux yet) I am testing on Windows 8.
I have a JComboBox that is in the same window as a JTable and a JTree which I have modified to add checkboxes. The JComboBox initially works perfectly. Popups up and works correctly. But once I interact with the JTable or JTree it no longer functions. It does not popup anymore.
When this pane is further added to a JTabbedPane, I can get it functioning again when tab out of this pane & back into the one with the JComboBox. In an attempt to go around this bug (and understand it better) I tried using a JPopupMenu instead of the JComboBox. The bug happens with the JPopup as well and it's easier to tell where it fails. When I debug I find that I do enter the mousePressed event and everything looks good in the mousePressed event. But the JPopupMenu.show() function doesn't get executed correctly.
#Override
public void mousePressed(MouseEvent e) {
topDirPopup.show(e.getComponent(), e.getX(), e.getY());
}
I could show more code but I'm not sure what code is interacting with the popup.
Any ideas? Any work arounds?
Isolated it down to it's going wrong in the checkboxes in the JTable (or the JTree) but I'm using standard Swing Table so it's easier to isolate. Very basic code.. doesn't read anything from the file system. Doesn't do any modifications to swing objects. Still see the bug.
I stripped most everything that is useless out of my code.
Main Test Class.
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Test extends JPanel {
Test() {
super(new BorderLayout());
FileSelectionPane fpane = new FileSelectionPane();
JLabel label = new JLabel("File Selection Pane Test");
this.add(fpane, BorderLayout.CENTER);
this.add(label, BorderLayout.NORTH);
}
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("File Selection Test Tool");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add content to the window.
frame.add(new Test());
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event dispatch thread:
//creating and showing this application's GUI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//Turn off metal's use of bold fonts
UIManager.put("swing.boldMetal", Boolean.FALSE);
createAndShowGUI();
}
});
}
}
My FileTool Pane. Yes I have a BorderLayout within a BorderLayout. It's silly in this example but my actual code is more complex and I have a Tabbed Pane in between these two.
import java.awt.BorderLayout;
import java.awt.Panel;
import javax.swing.JComboBox;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
public class FileSelectionPane extends Panel {
/**
*
*/
private static final long serialVersionUID = 1784004323821479260L;
/**
*
*/
JTable fileTable;
JComboBox<String> topDirComboBox;
FileSelectionPane() {
super(new BorderLayout());
createTopDirectoryComboBox();
FileSelectionTableModel tModel = new FileSelectionTableModel();
fileTable = new JTable(tModel);
JScrollPane jScrollPane2 = new JScrollPane(fileTable);
this.add(jScrollPane2, BorderLayout.CENTER);
this.add(topDirComboBox, BorderLayout.NORTH);
}
void createTopDirectoryComboBox() {
topDirComboBox = new JComboBox<String>();
topDirComboBox.addItem("C:\\Test\\Fake");
topDirComboBox.addItem("C:\\Test\\More");
topDirComboBox.addItem("C:\\Test\\View");
}
public class FileSelectionTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
static final int numCols = 4;
private String[] columnNames = {"..", "Name", "Type", "Size", "Creation Date", "Modification Date"};
private final static int checkCol = 0;
private final static int nameCol = 1;
private final static int typeCol = 2;
private final static int sizeCol = 3;
public int getColumnCount() { return numCols; }
public int getRowCount() { return 5; }
public String getColumnName(int col) {
return columnNames[col];
}
// Only the check item is editable.
public boolean isCellEditable(int row, int col)
{
return (col == checkCol) ? true : false;
}
Boolean tBool[] = new Boolean[10];
public void setValueAt(Object value, int row, int col) {
if (col == checkCol)
tBool[row] = (Boolean) value;
}
public Object getValueAt(int row, int col) {
Object rtnObject = null;
switch (col) {
case checkCol:
rtnObject = (Object) tBool[row];
break;
case nameCol:
rtnObject = (Object) "Testing";
break;
case sizeCol:
rtnObject = (Object) "5";
break;
}
return rtnObject;
}
/* Set up the types of each column */
public Class<?> getColumnClass(int col) {
// getValueAt(0, c).getClass();
Class<?> rtnValue = null;
switch (col)
{
case checkCol:
rtnValue = Boolean.class;
break;
case nameCol:
rtnValue = String.class;
break;
case sizeCol:
rtnValue = String.class;
break;
case typeCol:
rtnValue = String.class;
break;
}
return rtnValue;
}
}
}

Changing Dropdown content in a JTable Column in Swing

I have a JTable in which the first column contains combobox with same items in each cell.If i select an item in a cell combobox i need to remove the selected item from all the other combobox in that column and also add the previous selected item to all the other combobox.How should i do that?Please help me with an example.
public class Save extends JFrame {
String[] items1 = new String[] { "Cash", "Bank1", "Bank2" ,"Bank3"};
TableCellEditor editors;
DefaultTableModel dtmFunds;
private JComboBox comboBox1;
private JTable jtblFunds;
private void loadTable(){
comboBox1=new JComboBox(items1);
comboBox1.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
int x=comboBox1.getSelectedIndex();
comboItem= e.getItem().toString();
}
}
});
editors=new DefaultCellEditor(comboBox1);
dtmFunds = new DefaultTableModel(new Object[][] {{"", " ","delete"}}, new Object[] {"Fund Store", "Amount","Action"});
jtblFunds=new JTable(dtmFunds){
public TableCellEditor getCellEditor(int row,int column){
int modelColumn=convertColumnIndexToModel(column);
if(modelColumn==0 && row<jtblFunds.getRowCount()-1)
return editors;
else
return super.getCellEditor(row,column);
}
};
jtblFunds.setModel(dtmFunds);
jtblFunds.getModel().addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
int row=e.getFirstRow();
int column=e.getColumn();
if((column==0)&&(row<jtblFunds.getRowCount()-1)){
System.out.println("Dropdown changed "+row);
}
}
});
}
}
These are are codes i have used to add combobox to JTable column named "Fund Store".
Really, focus your efforts within the CellEditor itself, that's it's job. There is no need to extend from JTable or screw around with it.
import java.awt.Component;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
public class TestCellEditor {
public static void main(String[] args) {
new TestCellEditor();
}
public TestCellEditor() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<String> values = new ArrayList<>(5);
values.add("Bananas");
values.add("Apples");
values.add("Oranages");
values.add("Grapes");
values.add("Pears");
ComboBoxTableCellEditor editor = new ComboBoxTableCellEditor(values);
DefaultTableModel model = new DefaultTableModel(new Object[]{"Fruit"}, 5);
JTable table = new JTable(model);
table.getColumnModel().getColumn(0).setCellEditor(editor);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ComboBoxTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private JComboBox editor;
private List<String> masterValues;
public ComboBoxTableCellEditor(List<String> masterValues) {
this.editor = new JComboBox();
this.masterValues = masterValues;
}
#Override
public Object getCellEditorValue() {
return editor.getSelectedItem();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
DefaultComboBoxModel model = new DefaultComboBoxModel(masterValues.toArray(new String[masterValues.size()]));
for (int index = 0; index < table.getRowCount(); index++) {
if (index != row) {
String cellValue = (String) table.getValueAt(index, 0);
model.removeElement(cellValue);
}
}
editor.setModel(model);
editor.setSelectedItem(value);
return editor;
}
}
}
I'd prefer to have to two pools of values, one which is the master list and one which is the selected values, it would be easier and faster to prepare the editor each time it's invoked, but this is the basic idea...
All you need in fact is to update model of the comboBox you use for the editor.
comboBox1.setModel(new DefaultComboBoxModel(theArrayWithRemovedValue));
You have base array of combobox items (source) and result array of items where the selected one should be removed.
Just recreate the array removing value of the cell.
BTW. It's better to define cell editor standard way rather than overriding getCellEditor() method of JTable. Use e.g. public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor). You can define a custom class for the column 0.

Categories