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

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

Related

Cannot display JComboBox in JTable with TableModel

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.

How to get the selected node of a Tree Table in swing?

I have implemented a tree table in swing refrring the tutorial available here
.I have two columns in which Message Object or Fields objects under the Message will be displayed. If the node is a Field then it's Value will be displayed in the second column.I could view the required data successfully add a cell renderer to the second column where the value is a collection then addd a combo box to the cell. The issue comes when I add the cell editor. Below is my cell editor.
private class ValueCellEditor extends AbstractCellEditor implements TableCellEditor {
JTextField textEditor = new JTextField();
JComboBox comboBox = new JComboBox();
Component comp;
Field field = null;
private ValueCellEditor(){
int row=myTreeTable.getSelectedRow();
Object object= myTreeTable.getValueAt(row, 3);
TreePath path= myTreeTable.getPathForRow(row);
Object o= path.getLastPathComponent();
MyDataNode node=(MyDataNode)o;
Field f=(Field)node.getNodeDataObject();
comboBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if(field!=null){
if(e.getStateChange() == ItemEvent.SELECTED) {
field.setSelectedValue(comboBox.getSelectedItem());
field.setDefaultValue(comboBox.getSelectedItem());
}
}
}
});
textEditor.addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if(field!=null){
field.setDefaultValue(textEditor.getText());
}
}
});
}
#Override
public Component getTableCellEditorComponent
(JTable table, Object value, boolean isSelected, int row, int column) {
if(value instanceof List<?>) {
populateComboBox((ArrayList)value);
comp = comboBox;
} else {
textEditor.setText((String)value);
comp = textEditor;
}
return comp;
}
#Override
public Object getCellEditorValue() {
if(comp != null && comp instanceof JTextField ) {
return textEditor.getText();
} else if( comp != null && comp instanceof JCheckBox) {
return comboBox.getSelectedItem();
}
return null;
}
private void populateComboBox(List<Value> valueList){
comboBox.removeAll();
comboBox.setSelectedItem(null);
for (Value val: valueList) {
comboBox.addItem(val.getDescription());
}
if(field.getSelectedValue() != null) {
comboBox.setSelectedItem(field.getSelectedValue());
}
}
}
In my cell editor's constructor I have added a code as below through which I tried to get the current selected node of the selected row of the tree table. but the code I have entered does't work because of the 'getPathForRow' function does't support the tree table.
int row=myTreeTable.getSelectedRow();
Object object= myTreeTable.getValueAt(row, 3);
TreePath path= myTreeTable.getPathForRow(row);
Object o= path.getLastPathComponent();
MyDataNode node=(MyDataNode)o;
Field f=(Field)node.getNodeDataObject();
this is the table model I am using.
import com.milleniumit.walle.frontend.frontEndDto.Field;
public class MyDataModel extends MyAbstractTreeTableModel {
static protected String[] columnNames = { "Field", "Value" };
static protected Class<?>[] columnTypes =
{ MyTreeTableModel.class, Object.class};
public MyDataModel(MyDataNode rootNode) {
super(rootNode);
root = rootNode;
}
public Object getChild(Object parent, int index) {
return ((MyDataNode) parent).getChildren().get(index);
}
public int getChildCount(Object parent) {
return ((MyDataNode) parent).getChildren().size();
}
public int getColumnCount() {
return columnNames.length;
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class<?> getColumnClass(int column) {
return columnTypes[column];
}
public Object getValueAt(Object node, int column) {
MyDataNode mNode=(MyDataNode)node;
Object obj =mNode.getNodeDataObject();
if(column==0){
return mNode.getName();
}
else if (column==1){
if(obj instanceof Field){
Field field=(Field)mNode.getNodeDataObject();
if(field.getFieldDef().getListValue().size()>0){
return field.getFieldDef().getListValue();
}
else
return mNode.getDefaultValue();
}
else
return mNode.getDefaultValue();
}
return null;
}
public boolean isCellEditable(Object node, int column) {
return true; // Important to activate TreeExpandListener
}
public void setValueAt(Object aValue, Object node, int column) {
MyDataNode mNode=(MyDataNode)node;
if (mNode.getNodeDataObject() instanceof Field && column == 1) {
Field field = (Field) mNode.getNodeDataObject();
field.setDefaultValue(aValue);
field.setSelectedValue(aValue);
}
}
}
I need to get the selected node of the tree table so that I can get the current selected field to edit it's changed value through the editor. Can someone please let me know where I am going wrong in my code. What's the correct approach to get the current selected node of this tree table.

Why does the JTable change color while clicking?

I have a simple JTable which shows data and different background colors. Displaying the data and coloring the rows works great with the DefaultTableModel. But if I add a MouseListener to the rows to load something when I click on a row, then the background color disappears. Same if I mark a few rows. I don't need the rows to be marked at all, I just need the color too stay and the MouseEvent to work. Here the relevant code:
testcase_table = new JTable() {
public Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
List<Element> cases = element.getChildren();
int passed = 0;
Component c = super.prepareRenderer(renderer, row, column);
if (!isRowSelected(row)) {
c.setBackground(getBackground());
passed = 0;
if (row <= running_testcase) {
List<Element> teststeps = cases.get(row).getChildren();
for (Element teststep : teststeps) {
if (teststep.getAttribute("status") != null
&& teststep.getAttributeValue("status")
.equals("failed")) {
passed++;
}
}
if (passed > 0) {
c.setBackground(Color.RED);
}
if (passed == 0) {
c.setBackground(Color.GREEN);
}
}
}
return c;
}
};
testcase_table.setFocusable(false);
testcase_table.setRowSelectionAllowed(false);
testcase_table.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
JTable target = (JTable)e.getSource();
int row = target.getSelectedRow();
//set_values(doc,25);
set_teststeps(doc, row);
}
}
});
private void DisplayData(List<String> Testcases) {
aModel = new DefaultTableModel() {
// setting the jtable read only
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
// setting the column name
Object[] tableColumnNames = new Object[1];
tableColumnNames[0] = "TestCases";
aModel.setColumnIdentifiers(tableColumnNames);
if (Testcases == null) {
testcase_table.setModel(aModel);
return;
}
Object[] objects = new Object[1];
ListIterator<String> lstrg = Testcases.listIterator();
// populating the tablemodel
while (lstrg.hasNext()) {
String newcus = lstrg.next();
objects[0] = newcus;
aModel.addRow(objects);
}
// binding the jtable to the model
testcase_table.setModel(aModel);
}
Any ideas? Thank you.

Java JTable create row of checkboxes to create subtable

I have a JTable that uses an AbstractTableModel. I'm trying to make the first row of the table a row of JCheckboxes.
EDIT: The goal is to take the columns with checked checkboxes and create a new table. This is my first time trying something like this, so I'm open to suggestions.
Here is the code I'm trying in NetBeans 7.1.1 :
private void selectSourceCBActionPerformed(java.awt.event.ActionEvent evt) {
int sourceNum = selectSourceCB.getSelectedIndex();
DataSource currentDS = datSourceArrList.get(sourceNum);
final ArrayList<Object[]> workArrLst1 = currentDS.getSampSet();
sourceDetailTable.setAutoResizeMode(sourceDetailTable.AUTO_RESIZE_OFF);
sourceDetailTable.setColumnSelectionAllowed(true);
JTableHeader header = sourceDetailTable.getTableHeader();
AbstractTableModel mytable1 = new AbstractTableModel() {
Object colNames[] = workArrLst1.get(0);
#Override
public int getRowCount() {
return workArrLst1.size();
}
#Override
public int getColumnCount() {
return workArrLst1.get(1).length;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return workArrLst1.get(rowIndex+1)[columnIndex];
}
#Override
public void setValueAt(Object value, int row, int col) {
if(row == 1){
workArrLst1.get(row)[col] = Boolean(false);
fireTableCellUpdated(row, col);
}
workArrLst1.get(row)[col] = (String) value;
fireTableCellUpdated(row, col);
}
#Override
public String getColumnName(int column) {
return (String) colNames[column];
}
};
}
Is there anything obvious I'm missing here?

How can I select a table row with a single click using a custom TableCellEditor?

I have a JTable with a custom TableCellRenderer and a custom TableCellEditor. By default, the first click on a table row switch from renderer to editor and the second click select the row.
Is there any way I can make the row selected on a single click (and swith to the editor)?
I have tried to use:
table.getSelectionModel().setSelectionInterval(row, row);
in my getTableCellEditorComponent but it doesn't work, and if I add it to my getTableCellRendererComponent it works, but only sometimes.
Here is a full example:
public class SelectRowDemo extends JFrame {
public SelectRowDemo() {
CellRendererAndEditor rendererAndEditor = new CellRendererAndEditor();
StringTableModel model = new StringTableModel();
JTable table = new JTable(model);
table.setDefaultEditor(String.class, rendererAndEditor);
table.setDefaultRenderer(String.class, rendererAndEditor);
model.addElement("");
model.addElement("");
model.addElement("");
add(new JScrollPane(table));
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
new SelectRowDemo();
}
});
}
class CellRendererAndEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private final JLabel renderer = new JLabel();
private final JLabel editor = new JLabel();
#Override
public Object getCellEditorValue() {
return editor.getText();
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String str = "renderer ";
str += (isSelected) ? "selected" : "not selected";
renderer.setText(str);
return renderer;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
table.getSelectionModel().setSelectionInterval(row, row);
String str = "editor ";
str += (isSelected) ? "selected" : "not selected";
editor.setText(str);
return editor;
}
}
class StringTableModel extends AbstractTableModel {
private final List<String> data = new ArrayList<String>();
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int column) {
return data.get(row);
}
#Override
public Class<?> getColumnClass(int column) {
return String.class;
}
#Override
public boolean isCellEditable(int row, int column) {
return true;
}
#Override
public void setValueAt(Object aValue, int row, int column) {
if(aValue instanceof String) {
data.set(row, (String)aValue);
fireTableRowsUpdated(row, column);
} else throw new IllegalStateException("aValue is not a String");
}
public void addElement(String s) {
data.add(s);
}
}
}
What's happening is that the table UI is starting to edit the cell before changing selection. If you put a println in getTableCellEditor() and shouldSelectCell(), the editor gets called first, with isSelected == false, then it calls shouldSelectCell and changes the selection.
You can see exactly where it happens in BasicTableUI.adjustSelection(MouseEvent).
boolean dragEnabled = table.getDragEnabled();
if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) {
setDispatchComponent(e);
repostEvent(e);
}
CellEditor editor = table.getCellEditor();
if (dragEnabled || editor == null || editor.shouldSelectCell(e)) {
table.changeSelection(pressedRow, pressedCol,
BasicGraphicsUtils.isMenuShortcutKeyDown(e),
e.isShiftDown());
}
As for rendering purposes, I'd just render it as if selected == true, since it will before that event is finished processing.

Categories