Cannot display JComboBox in JTable with TableModel - java

Below code to display a JTable with 3 columns, which respectively contain a JComboBox, a String and a double, and which should display yellow. The problem is that I cannot get the JComboBox in the first column to display as ... a combo box; instead I get a String saying "javax.swing.JComboBox...". What am I doing wrong?
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;
public class BadDialog extends JDialog {
//Instantiate the data for the table, which is 2 rows x 3 cols
private final JComboBox col0ComboBox = new JComboBox(new String[]{"aaa", "bbb"}); //Goes in all rows of Col 0
private final String[] col1Data = {"Mickey", "Mouse"};
private final double[] col2Data = {111, 222};
public BadDialog() {
//Instantiate table
JTable badTable = new JTable();
//Assign a tableModel to the table, put the table in a scroller, add it to this dialog, and sort out the renderer
TableModel badTableModel = new BadTableModel();
badTable.setModel(badTableModel);
JScrollPane scroller = new JScrollPane(badTable);
add(scroller);
BadTableCellRenderer badTableCellRenderer = new BadTableCellRenderer();
badTable.setDefaultRenderer(JComboBox.class, badTableCellRenderer); //Col 0
badTable.setDefaultRenderer(String.class, badTableCellRenderer); //Col 1
badTable.setDefaultRenderer(Double.class, badTableCellRenderer); //Col 2
//Assign col0ComboBox to Col 0
badTable.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(col0ComboBox));
//Show the dialog
setPreferredSize(new Dimension(300, 470));
pack();
setModal(true);
setLocation(10, 10);
setVisible(true);
}
private final class BadTableModel extends AbstractTableModel {
#Override
public int getRowCount() {
return 2;
}
#Override
public int getColumnCount() {
return 3;
}
#Override
public Object getValueAt(int rowIndex, int colIndex) {
if (colIndex == 0) return col0ComboBox;
if (colIndex == 1) return col1Data[rowIndex];
return col2Data[rowIndex];
}
#Override
public Class<?> getColumnClass(int colIndex) {
if (colIndex == 0) return JComboBox.class;
if (colIndex == 1) return String.class;
return Double.class;
}
}
private static class BadTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
//Make all columns yellow
c.setBackground(Color.YELLOW);
c.setForeground(Color.RED);
c.setFont(new Font("Dialog", Font.PLAIN, 12));
return c;
}
}
public static void main(String[] args) {
new BadDialog();
}
}

Never return a component in a TableModel. The whole point of having a separate model and view is that the model contains only data, not components. The model’s job is to provide data; the view’s job is to determine how to display that data.
Your TableModel’s getColumnClass method should look like this:
public Class<?> getColumnClass(int colIndex) {
if (colIndex == 0) return String.class; // String, not JComboBox
if (colIndex == 1) return String.class;
return Double.class;
}
and your getValueAt method needs to return the actual data value at that row:
public Object getValueAt(int rowIndex, int colIndex) {
if (colIndex == 0) return (rowIndex % 1 == 0 ? "aaa" : "bbb");
if (colIndex == 1) return col1Data[rowIndex];
return col2Data[rowIndex];
}
The cell renderer is part of the view, not the model, so it can make use of a JComboBox. Your render needs to use the value argument to modify your JComboBox:
private static class BadTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
if (row != 0) {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
}
JComboBox c = col0ComboBox;
c.setSelectedItem(value);
//Make all columns yellow
c.setBackground(Color.YELLOW);
c.setForeground(Color.RED);
c.setFont(new Font("Dialog", Font.PLAIN, 12));
return c;
}
}

Your TableModel is completely wrong.
The data of the TableModel must be stored in the TableModel, not as an Array external to the model.
Don't extend AbstractTableModel. Instead extend DefaultTableModel. The DefaultTableModel already provides data storage and methods to update the data of each cell. Then the only method you need to override is the getColumnClass(...) method, to make sure the appropriate renderer/editor is used for each column.
See: Sorting JTable programmatically for a basic example showing how to add initial data to a JTable. The getColumnClass(...) method in that example is more generic.
You can simplify the getColumnClass(...) method as follows:
#Override
public Class getColumnClass(int column)
{
switch (column)
{
case 2: return Double.class;
default: return super.getColumnClass(column);
}
}
Then if you want the editor of the first column to be a combobox you would use:
badTable.getColumnModel().getColumn(0).setCellEditor( new DefaultCellEditor(col0ComboBox));
You should NOT use the same renderer for all 3 columns because typically numbers are displayed right aligned in the column.
There should be no need for custom renderers. Instead you change properties of the table:
table.setBackground(...);
table.setForeground(...);
table.setFont(...);
These values will then be used by each of the default column renderers.

Related

How to set a JComboBox only in specific cell in a JTable

I want to add a JComboBox only inside a cell who suppose to have a list of values. Below is my code but it adds the combo box in all the cells in the column. Let me know what's missing in my code to set the combo box only on the selected cell.
public class PropertiesTableModel extends AbstractTableModel{
//this method is called to set the value of each cell
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Field field= (Field) fieldList.get(rowIndex);
if(columnIndex==0){
String dataType=field.getFieldDef().getDataType();
return PropertiesPanel.getPpIns().getDataTypeIcon(dataType);
}
if(columnIndex==1){
return field.getFieldDef().getfName();
}
else if (columnIndex==2){
if(field.getFieldDef().getListValue().size()>0){
return createValueListCombo(field.getFieldDef().getListValue());
}
return field.getDefaultValue();
}
else{
return null;
}
}
public JComboBox createValueListCombo(List<Value> valueList){
TableColumn valColumn = table.getColumnModel().getColumn(2);
JComboBox comboBox=new JComboBox();
for (Value val: valueList) {
comboBox.addItem(val.getDescription());
}
comboBox.setSelectedIndex(0);
valColumn.setCellEditor(new DefaultCellEditor(comboBox));
return comboBox;
}
}
It's really simple and can be done using two ways
First of all your model should notify editor/table that the current cell has list of values.
public class PropertiesTableModel extends AbstractTableModel {
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
// previous stuff
if (columnIndex == 2) {
// return the actually selected value,
// not the first value of the list!
// also the values changed by setValueAt() must be considered.
return null; // implement it!
}
}
public List<Object> getPossibleValues(int row, int column) {
// this method should return possible values to select.
// if cell has no possible values and should be editeb
// by a text field this methos should return null
if (column == 2) {
Field field= (Field) fieldList.get(rowIndex);
if (field.getFieldDef().getListValue().size() > 0) {
return field.getFieldDef().getListValue();
}
return null; // probably something else for non-list values
}
}
public void setValueAt(int row, int column) {
// you need to store the value chosen by the user
}
}
1) Override the method public TableCellEditor getCellEditor(int row, int column) in JTable
public TableCellEditor getCellEditor(int row, int column) {
PropertiesTableModel model = (PropertiesTableModel) getModel();
List<Object> values = model.getPossibleValues(row, column);
if (values != null) {
return new DefaultCellEditor(new JComboBox(values.toArray()));
} else {
return super.getCellEditor(row, column);
}
}
2) You can create a custom editor which delegates all calls to one of two (or more) editors depended on the currently edited cell.
public class CellEditorMulticaster implements TableCellEditor {
private DefaultTableCellEditor textEditor;
private DefaultTableCellEditor currentEditor;
public CellEditorMulticaster() {
firstEditor = new DefaultTableCellEditor(new JTextField());
}
Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected,
int row, int column) {
PropertiesTableModel model = (PropertiesTableModel) table.getModel();
List<Object> values = model.getPossibleValues(row, column);
if (values != null) {
currentEditor = new DefaultCellEditor(new JComboBox(values.toArray()));
} else {
currentEditor = textEditor;
}
return currentEditor.getTableCellEditorComponent(table, value,
isSelected, row, column);
}
public Object getCellEditorValue() {
return currentEditor.getCellEditorValue();
}
public boolean isCellEditable(EventObject anEvent) {
JTable table = (JTable) anEvent.getSource;
int row, col;
if (anEvent instanceof MouseEvent) {
MouseEvent evt = (MouseEvent) anEvent;
row = table.rowAtPoint(evt.getPoint());
col = table.columnAtPoint(evt.getPoint());
} else {
row = table.getSelectedRow();
col = table.getSelectedColumn();
}
PropertiesTableModel model = (PropertiesTableModel) table.getModel();
List<Object> values = model.getPossibleValues(row, column);
if (values != null) {
return true;
} else {
return textEditor.isCellEditable(anEvent);
}
}
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
public boolean stopCellEditing() {
return currentEditor.stopCellEditing();
}
public void cancelCellEditing() {
currentEditor.cancelCellEditing();
}
// same pattern for another methods (delegate to currentEditor)
}
i am sorry that i not valid to add comment , but i have to report a problem for sergiy's answer .
it's for the step 1 : 1) Override the method public TableCellEditor getCellEditor(int row, int column) in JTable
i found each time i click the comboBox , this code will create a new combobox as a new cell editor . and my code will be crash after i clicked 2-3 times as the Index out of bounds for length.
i am fully confuse about this , hope can get the answer here .
my code is :
#Override
public TableCellEditor getCellEditor(int row, int column) {
TableModel model = (TableModel) getModel();
String[] values = model.getPossibleValues(row, column);
if (values != null) {
JComboBox<String> comboBox = new JComboBox<String>(values);
comboBox.addActionListener((e)->{
model.setValueAt(row, column, comboBox.getSelectedItem());
});
return new DefaultCellEditor(comboBox);
} else {
return super.getCellEditor(row, column);
}
}

JTable changing cells colour's from a given ArrayList

So I have the following class which defines basic parameters of an error
public class Error {
public String desc;
public int rowNumber;
public int colNumber;
public int fixNumber;
public Error(String desc,int row, int col, int fix){
this.desc = desc;
rowNumber = row++;
colNumber = col++;
fixNumber = fix;
}
...
My gui Class
public class Gui extends JFrame {
AbstractTableModel model;
JTable table;
public void start(AbstractTableModel model) {
this.model = model;
table=new JTable(model){
#Override
public boolean isCellEditable(int arg0, int arg1) {
return false;
}
};
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
TableColumn column = null;
for (int i = 0; i < model.getColumnCount(); i++) {
column = table.getColumnModel().getColumn(i);
column.setPreferredWidth(120);
column.setMaxWidth(300);
column.setMinWidth(50);
column.setCellRenderer(new customCellRender());
}
JScrollPane pane = new JScrollPane(table);
pane.setPreferredSize(new Dimension(900,900));
add(pane);
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
I have attempted to make a cellRenderer but at the moment it does not have the intended effect. Because it colors all the cells.
#SuppressWarnings("serial")
public class customCellRender extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column){
Component c = null;
c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
c.setBackground(new java.awt.Color(255, 72, 72));
return c;
}
I then have a List which stores instances of the error class.What I cant figure out is how to ONLY change the color of the cells in my table based on the Error(rowNumber,colNumber). Considering that the errors are in a list structure, so I would have to iterate over and somehow pass each error column and row to the renderer. Is that possible ?
Your code colors all the cells because the component is reused, not recreated every time. Just add a
if (error) {
c.setBackground(errorBackground);
} else {
c.setBackground(table.getBackground);
}
A couple of points to make:
Don't create a new Color every time. It's expensive and you're using the same one anyway.
Use a Set for your errors to make contains quick, otherwise your rendering may become very slow with a large number of those.

How to put JComboBox into JTable to show List<String> per row

I need to put JComboBox into JTable. The JComboBox should contain a list of entries corresponding to particular row. For instance:
Position | Skills
Programmer | List<String{Java,C,C++}
Web Programmer | List<String{javaScript,PHP,MySQL}
I populate the table with Object[][] data. One of columns of data contains List<String>.
I wrote the following renderer. However, the problem is that it outputs the content of JComboBox in each row is the same.
DefaultTableCellRenderer cellRenderer = new DefaultTableCellRenderer()
{
public Component getTableCellRendererComponent( JTable table, Object value, boolean
isSelected, boolean hasFocus, int row, int column)
{
super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
if ( value instanceof List<?>)
{
int vColIndex = 5;
TableColumn col = table.getColumnModel().getColumn(vColIndex);
col.setCellEditor(new ComboBoxEditor(((ArrayList<?>) value).toArray()));
col.setCellRenderer(new ComboBoxRenderer(((ArrayList<?>) value).toArray()));
}
return this;
}
};
table = new JTable(model);
for (int i = 0; i < table.getColumnCount(); ++i)
{
table.getColumnModel().getColumn(i).setCellRenderer(cellRenderer);
}
ComboBox renderers:
class ComboBoxRenderer extends JComboBox implements TableCellRenderer {
public ComboBoxRenderer(Object[] objects) {
super(objects);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
// Select the current value
setSelectedItem(value);
return this;
}
}
class ComboBoxEditor extends DefaultCellEditor {
public ComboBoxEditor(Object[] objects) {
super(new JComboBox(objects));
}
}
JComboBox box=new JComboBox(values);
TableColumn col = table.getColumnModel().getColumn(0);
col.setCellEditor(new DefaultCellEditor(box));

JTable Renderer not working with RowSorter?

I have a Simple JTable holding Integers in the first column and Strings in the Second. I would like to be able to sort each Column.
My Renderer:
package gui.table;
import gui.DaimlerColor;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
#SuppressWarnings("serial")
public class StandardCellRenderer extends 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.setBackground((isSelected ? DaimlerColor.LIGHT_BLUE : (row % 2 == 1 ? DaimlerColor.DARK_WHITE : DaimlerColor.WHITE)));
if(isSelected){
c.setBackground(DaimlerColor.LIGHT_BLUE);
}
return c;
}
}
My TableModel:
package gui.table;
import java.util.List;
import java.util.Vector;
import javax.swing.table.DefaultTableModel;
import org.jdom2.Element;
import reporting.FailureClassificationCatalogue;
#SuppressWarnings("serial")
public class ReportTableModel extends DefaultTableModel {
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
public ReportTableModel(List<Element> reports) {
super(createDataVector(reports), createColumnNames());
}
private static Vector<Vector<Object>> createDataVector(List<Element> reports) {
Vector<Vector<Object>> data = new Vector<Vector<Object>>();
for (int i = 0; i < reports.size(); i++) {
Vector<Object> row = new Vector<Object>();
if(reports.get(i).getName().equals("Default")){
row.add("-");
}
else{
row.add(Integer.valueOf(i+1));
}
String title = reports.get(i).getAttributeValue("type");
try{
title = FailureClassificationCatalogue.valueOf(title).getName();
}catch(IllegalArgumentException iae){
}catch(NullPointerException npe){
}
row.add(title);
data.add(row);
}
return data;
}
#Override
public Class<?> getColumnClass(int colNum) {
switch (colNum) {
case 0:
return Integer.class;
case 1:
return String.class;
default:
return String.class;
}
}
private static Vector<String> createColumnNames() {
Vector<String> columns = new Vector<String>();
columns.add("Number");
columns.add("Error Type");
return columns;
}
/**
* overridden to ignore null values
*/
#Override
public void setValueAt(Object value, int row, int col) {
if (value == null) {
return;
}
// delegate to the parent method
super.setValueAt(value, row, col);
}
}
If I don´t implement the getColumnClass then the renderer works fine but the Integers are sorted as Strings. As soon as I implement the method the renderer doesn´t work properly (background is only set in one column) but the Integers are sorted correctly. How can I resolve this?
My Code to set the renderer and sorter:
reporttable = new JTable();
reporttable.setName("report");
reporttable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
reporttable.setRowHeight(40);
reporttable.addMouseListener(tgc.mouseListener);
reporttable.setDefaultRenderer( Object.class, new StandardCellRenderer());
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>();
reporttable.setRowSorter(sorter);
sorter.setModel(reportModel);
Any ideas? Thanks.
Absent an sscce, two several things stand out:
The call to setDefaultRenderer() should specify the data type to which it applies, e.g. String.class or Integer.class. Alternatively, override the table's prepareRenderer() to consider all cells, as shown here.
reporttable.setDefaultRenderer(Integer.class, new StandardCellRenderer());
Your TableRowSorter constructor "Creates a TableRowSorter with an empty model." Instead, pass your TableModel to the constructor or use setAutoCreateRowSorter(true).
reporttable.setAutoCreateRowSorter(true);
"When using a sorter, always remember to translate cell coordinates," as discussed here.

Change the background color of a row in a JTable

I have a JTable with 3 columns. I've set the TableCellRenderer for all the 3 columns like this (maybe not very effective?).
for (int i = 0; i < 3; i++) {
myJTable.getColumnModel().getColumn(i).setCellRenderer(renderer);
}
The getTableCellRendererComponent() returns a Component with a random background color for each row.
How could I change the background to an other random color while the program is running?
Resumee of Richard Fearn's answer , to make each second line gray:
jTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
{
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
final Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
c.setBackground(row % 2 == 0 ? Color.LIGHT_GRAY : Color.WHITE);
return c;
}
});
One way would be store the current colour for each row within the model. Here's a simple model that is fixed at 3 columns and 3 rows:
static class MyTableModel extends DefaultTableModel {
List<Color> rowColours = Arrays.asList(
Color.RED,
Color.GREEN,
Color.CYAN
);
public void setRowColour(int row, Color c) {
rowColours.set(row, c);
fireTableRowsUpdated(row, row);
}
public Color getRowColour(int row) {
return rowColours.get(row);
}
#Override
public int getRowCount() {
return 3;
}
#Override
public int getColumnCount() {
return 3;
}
#Override
public Object getValueAt(int row, int column) {
return String.format("%d %d", row, column);
}
}
Note that setRowColour calls fireTableRowsUpdated; this will cause just that row of the table to be updated.
The renderer can get the model from the table:
static class MyTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
MyTableModel model = (MyTableModel) table.getModel();
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
c.setBackground(model.getRowColour(row));
return c;
}
}
Changing a row's colour would be as simple as:
model.setRowColour(1, Color.YELLOW);
The other answers given here work well since you use the same renderer in every column.
However, I tend to believe that generally when using a JTable you will have different types of data in each columm and therefore you won't be using the same renderer for each column. In these cases you may find the Table Row Rendering approach helpfull.
This is basically as simple as repainting the table. I haven't found a way to selectively repaint just one row/column/cell however.
In this example, clicking on the button changes the background color for a row and then calls repaint.
public class TableTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Color[] rowColors = new Color[] {
randomColor(), randomColor(), randomColor()
};
final JTable table = new JTable(3, 3);
table.setDefaultRenderer(Object.class, new TableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
JPanel pane = new JPanel();
pane.setBackground(rowColors[row]);
return pane;
}
});
frame.setLayout(new BorderLayout());
JButton btn = new JButton("Change row2's color");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
rowColors[1] = randomColor();
table.repaint();
}
});
frame.add(table, BorderLayout.NORTH);
frame.add(btn, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private static Color randomColor() {
Random rnd = new Random();
return new Color(rnd.nextInt(256),
rnd.nextInt(256), rnd.nextInt(256));
}
}
The call to getTableCellRendererComponent(...) includes the value of the cell for which a renderer is sought.
You can use that value to compute a color. If you're also using an AbstractTableModel, you can provide a value of arbitrary type to your renderer.
Once you have a color, you can setBackground() on the component that you're returning.

Categories