JComboBox shared between multiple table cells automatically selecting currently selected item - java

I have added a combobox as a cell editor using the code provided by camickr below as reference:
How to add unique JComboBoxes to a column in a JTable (Java)
Except in my case, I only need one combobox to be used by all of the cells within a column. The problem I'm running into is that the combobox automatically selects the last selected item (or current selected item, not sure), and since different rows share the same combobox, if you click on one of the cells, it'll automatically change to the last selected item.
As a quick demonstration I simply modified the code from above to show the issue. I would like the combo box to automatically select an item on the list equal to the item that is set in selected cell (as opposed to selecting a cell, and then having the contents of that cell automatically change)
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class Test extends JFrame
{
List<TableCellEditor> editors = new ArrayList<TableCellEditor>(3);
public Test()
{
// Create the editors to be used for each row
String[] items1 = { "Red", "Blue", "Green" };
JComboBox comboBox1 = new JComboBox( items1 );
DefaultCellEditor dce1 = new DefaultCellEditor( comboBox1 );
editors.add( dce1 );
// Create the table with default data
Object[][] data =
{
{"Color", "Red"},
{"Shape", "Square"},
{"Fruit", "Banana"},
{"Plain", "Text"}
};
String[] columnNames = {"Type","Value"};
DefaultTableModel model = new DefaultTableModel(data, columnNames);
JTable table = new JTable(model)
{
// Determine editor to be used by row
public TableCellEditor getCellEditor(int row, int column)
{
int modelColumn = convertColumnIndexToModel( column );
if (modelColumn == 1 && row < 3)
return editors.get(0);
else
return super.getCellEditor(row, column);
}
};
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args)
{
Test frame = new Test();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setVisible(true);
}
}

By default the first item of a combo box is selected, which in your example is "Red".
When you edit a cell the value from the TableModel is selected in the comboBox editor. Since the data in your table does not match any of the entries in the comboBox, the selection doesn't change so "Red" is displayed as the value in the editor.
When you make a selection from the editor that value is then saved in the model and will be displayed properly the next time you edit the cell.
The solution to your problem is to make sure the TableModel contains valid data when it is created. Only that way can the proper item in the comboBox be selected.
I figured I would have to attach an action listener to the combobox to determine which item to selected
No, you don't play with listeners on a comboBox when it is used as an editor. The comboBox editor does the selection of the item automatically for you.

Related

When using JTable with cell selection, selecting a cell removes whole selection

I have a table where you can select specific cells.
When you select row 1 and 2 for first column by using Ctrl-click, and then you remove selection on one of the rows, the other row also get de-selected:
public static void main(String[] args)
{
JFrame jf = new JFrame("Test");
JTable table = new JTable();
table.setCellSelectionEnabled(true);
DefaultTableModel tm = new DefaultTableModel(new Object[][]{new Object[]{1,"A"},new Object[]{2, "B"},new Object[]{3, "C"}}, new Object[]{"Test", "Test2"});
table.setModel(tm);
jf.setSize(300, 400);
jf.getContentPane().add(table);
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
Ctrl + Click on Test/2:
I expected:
From what i understand of the Swing code, the problem is that the column selection model has a code that does something like: if col = 0 selected, remove selection altogether. So when the table re-rendered, there's no column selection and whole selection gets cleared.
Is there a way to prevent whole deselection without recoding the whole DefaultSelectionModel?
I solved it by keeping track of the last selected column and having a selection listener that checks if selected row count is > 0, then select the lastly selected column, not perfect mayhaps but it seems to work fine

Repaint Table issue with Swing JTable

I am working with swing JTable and have a trouble with repainting table. I draw a JTable with thr following code
Object[] column = new Object[]{"Entity", "Attribute"};
Object[][] rowData = new Object[][]{{"E1", "A1"},{"E2", "A2"}};
TableCellRenderer cellRenderer = new TableCellRenderer();
JTable table = new JTable(new DefaultTableModel(rowData, column));
table.setCellSelectionEnabled(true);
table.getColumnModel().getColumn(0).setCellRenderer(cellRenderer);
Below is my table renderer code ..
public class TableCellRenderer implements javax.swing.table.TableCellRenderer {
//private JPanel panel;
JTextField field;
private JTable table;
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value,
boolean isSelected, boolean hasFocus, final int row, final int column) {
this.table = table;
//JTextField field = null;
System.out.println("Rendere : Row : " + row + "Column : " + column);
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JButton button = new JButton("?");
button.setPreferredSize(new Dimension(20, panel.getHeight()));
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
new SelectionDialog(panel, table, value, row, column);
}
});
if(table.getValueAt(row, column) != null){
field = new JTextField(table.getValueAt(row, column).toString());
}else{
field = new JTextField(table.getValueAt(row, column).toString());
}
field.setPreferredSize(new Dimension(30, panel.getHeight()));
panel.add(field, BorderLayout.WEST);
panel.add(button, BorderLayout.EAST);
return panel;
}
this is how i am updating contents of a cell in table..
SelectionDialog.this.table.getModel().setValueAt("E7", 0, 0);
I am updating the table model data via the SelectionDialog for e.g change data at row 0, colum 0 to E7 etc. After changing data in table model i have tried the following options but none of them has refreshed the table data in view however the data in model of JTable was updated correctly. If I add a new row on the fly and then call the below methods then every thing work fine but if I modify data in the model of an existing row then none of the solution mentioned below is working
//((DefaultTableModel)SelectionDialog.this.table.getModel()).addRow(new Object[]{"E3", "A3"});
//((DefaultTableModel)SelectionDialog.this.table.getModel()).fireTableCellUpdated(SelectionDialog.this.row, SelectionDialog.this.column);
//((DefaultTableModel)SelectionDialog.this.table.getModel()).fireTableChanged(new TableModelEvent(SelectionDialog.this.table.getModel()));
//((DefaultTableModel)SelectionDialog.this.table.getModel()).fireTableStructureChanged();
//SelectionDialog.this.table.repaint();
// SelectionDialog.this.table.revalidate();
Please provide any insights about the problem as I am to swing and may have missed some very prominent things.
Edit 1: Just adding one more note which i wanted to place earlier but don't know how i missed. Table is not updated in general but if i focus out from the cell in which change was made or i change the size of table then it immediately change the contents of that particular cell to fresh selected value.
Problem Solved:
I am placing my findings for someone who is facing similar problem.
I rendered a button and a text box inside each cell in my table. When button was clicked (Editor code is not provided as it looks irrelevant to me to place here) a dialog appear which inputs value from user and update the specific column and row.
The lines i mentioned as not working at the end of my post (before edit 1) were working correctly however renderer was not executing unless i manually focus out from the selected cell (whose button was clicked) or manually change the size of jtable which make sense as button was inside editor and button click shows that cell is edited and off-course renderer will not execute unless editing is finished which requires focus out or enter key etc.
I applied the following code as
table.editCellAt(-1, -1);
It focus out from the edited cell (edited with the button) and hence renderer executes and work as expected.
When you are using a DefaultTableModel and you want to update a certain value, you need to use the DefaultTableModel#setValueAt method.
Calling this method (on the Event Dispatch Thread of course) will update your model and fire the necessary events. Those events will cause your JTable to update itself.
A few additional remarks about your renderer:
Creating new components in each call of your renderer is not the way to go. This becomes incredibly slow for large tables. Create all components once in the constructor of your renderer, and just update their state
Adding a JButton to your table in the renderer has no effect, unless you have an editor as well. The button will not be clickable, and the action listener you attach to it will never be called. See the renderers and editors section in the JTable tutorial for more information.
There should be no need to call getValueAt in your renderer. The value is passed in as one of the arguments

JTable JComboBox is defaulting to display 1st item when list is expanded

I have been trying to determine why my JComboBox is displaying the 1st item in the list through numerous Google searches, but I'm struggling to find relevant help. It could be that I don't know the correct terminology (hence the overly specific title of this question) and thus not finding the information that would explain my issue. I checked out the JComboBox API, and few of the listeners and models that it uses, but they did not seem likely candidates.
The JComboBox in question is inside a JTable, so I am not aware if that changes the default behaviour of it. The code I am using is as below:
//row and col are final due to usage inside anonymous inner class
public TableCellEditor getCellEditor(final int row, final int col)
{
String[] listItems = new String[arrayList.getSize()];
int i = -1;
for(String s : arrayList)
{
i++;
listItems[i] = s;
}
JComboBox<String> box = new JComboBox<>(listItems);
box.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
if(e.getStateChange() == ItemEvent.SELECTED)
{
if(e.getItem().equals("Add/Edit Projectile"))
{
//Where Editor is a JFrame that will be opened
new Editor();
}
}
}
});
DefaultCellEditor list = new DefaultCellEditor(box);
}
Please note that the Arraylist in my program does not contain Strings, but instead a more complicated set of custom objects that I believe would distract from the main issue.
I haven't included a Renderer for JComboBox's in the JTable as I was happy enough with the way it appeared, and figured that my problem was more going to be something I have neglected to implement in the model/implemented wrong.
I've also provided a couple of screenshots to better portray my problem. The first image is when the JComboBox is not selected, and simply displaying the currently selected item.
The second image is when I have just clicked the JComboBox to bring up the list. As depicted, it will immediately bring up that first item, no matter what it is.
If anyone has any suggestions as to where to look/solutions, I would be very grateful.
EDIT
My particular table has two columns, where the left column is a variable name, and the right column is the value associated with the variable. The tables role is to display the properties of a selected object, where each value for different variable for different objects are likely to not be the same.
In this particular case, the cell displays a JComboBox with all the available Projectiles in the game we are making. Each enemy has a different type of projectile it defaults to. So when I click on a different enemy in our game area, the table will display all of their current properties (defaults if they have not been changed).
Enemies do have a getter for the Projectile, so I could determine what the currently selected enemy is, get it's projectile, do a toString() to find how it is to be represented in the list, and do a setValueAt().
The only problem is at the moment it is always selecting the first item in the list when the list is expanded.
Unless the values for the JComboBox are dynamically generated for each row, you should be able to just prepare the CellEditor ahead of time, for example...
JComboBox cb = new JComboBox(new String[]{"1", "2", "3", "4"});
DefaultCellEditor editor = new DefaultCellEditor(cb);
JTable table = new JTable(new DefaultTableModel(5, 1));
table.getColumnModel().getColumn(0).setCellEditor(editor);
This will set the selected value of the editor to the value of the cell when the editing process starts
Updated
In the case where the combobox values are dynamically generate per row, you could do something more like...
JComboBox cb = new JComboBox();
DefaultCellEditor editor = new DefaultCellEditor(cb) {
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JComboBox editor = (JComboBox) getComponent();
String[] listItems = new String[arrayList.getSize()];
int i = -1;
for (String s : arrayList) {
i++;
listItems[i] = s;
}
DefaultComboBoxModel model = new DefaultComboBoxModel(listItems);
editor.setModel(model);
editor.setSelectedItem(value);
return editor;
}
};
JTable table = new JTable(new DefaultTableModel(5, 1));
table.getColumnModel().getColumn(0).setCellEditor(editor);
Note the use of editor.setSelectedItem(value);, this will set the selected value to the cells current value...
You could also re-use the model, clearing it each time and re-filling it with new values. You might find this more efficient if you have a large number of rows as you won't need to constantly create a new model each time a cell is edited
Thow this is an oldie...
Your problem is most likely you don't implement "equals" in the class used in the combo.
The Combo needs to select the current item when it is being prepared and does so by iterating through the elements of the model and selects the first one that is equal to the value in the cell. If none is encountered then it leaves the combo as is (either first element or the last used element in a previous cell edit)
This is how you should default to the previously selected element:
//...
Object selectedItem = box.getSelectedItem();
//Add some elements to the jComboBox
box.setSelectedItem(selectedItem);

How to add row of data to Jtable from values received from jtextfield and comboboxes

I have a JFrame Form which has JTextFields, JCombobox etc. and I am able to receive those values to variables and now I want to add the received data to JTable in new row when user clicks Add or something like that.
I have created JTable using net-beans the problem is what would be the code to add data from those variable to the rows of table. A basic example would be appreciated. I have tried numerous example and have added the code to ActionListener of the JButton but nothing Happens.
The Examples I tried are. How to add row in JTable? and How to add rows to JTable with AbstractTableModel method?
Any Help would be appreciated.
Peeskillet's lame tutorial for working with JTables in Netbeans GUI Builder
Set the table column headers
Highglight the table in the design view then go to properties pane on the very right. Should be a tab that says "Properties". Make sure to highlight the table and not the scroll pane surrounding it, or the next step wont work
Click on the ... button to the right of the property model. A dialog should appear.
Set rows to 0, set the number of columns you want, and their names.
Add a button to the frame somwhere,. This button will be clicked when the user is ready to submit a row
Right-click on the button and select Events -> Action -> actionPerformed
You should see code like the following auto-generated
private void jButton1ActionPerformed(java.awt.event.ActionEvent) {}
The jTable1 will have a DefaultTableModel. You can add rows to the model with your data
private void jButton1ActionPerformed(java.awt.event.ActionEvent) {
String data1 = something1.getSomething();
String data2 = something2.getSomething();
String data3 = something3.getSomething();
String data4 = something4.getSomething();
Object[] row = { data1, data2, data3, data4 };
DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
model.addRow(row);
// clear the entries.
}
So for every set of data like from a couple text fields, a combo box, and a check box, you can gather that data each time the button is pressed and add it as a row to the model.
you can use this code as template please customize it as per your requirement.
DefaultTableModel model = new DefaultTableModel();
List<String> list = new ArrayList<String>();
list.add(textField.getText());
list.add(comboBox.getSelectedItem());
model.addRow(list.toArray());
table.setModel(model);
here DefaultTableModel is used to add rows in JTable,
you can get more info here.
String[] tblHead={"Item Name","Price","Qty","Discount"};
DefaultTableModel dtm=new DefaultTableModel(tblHead,0);
JTable tbl=new JTable(dtm);
String[] item={"A","B","C","D"};
dtm.addRow(item);
Here;this is the solution.

How do you add a border to a row in a Jtable?

I have a Jtable and I want to highlight a row by adding a border to the row. I have extended a DefaultTableCellRenderer and I figure the work needs to be done in the getTableCellRendererComponent method.
I'm guessing that since there doesn't seem to be a concept of a row that I need to create a custom border for the individual cells in the row. Something like a left side, top, and bottom for the first cell, a top and bottom for all the inner cells, and a top, bottom, and right side for the last cell in the row. I'm having problems finding out how to go about actually executing the thought process. I'm not sure how to use the setBorder() method or if that's even the direction I need to take.
I would not create a custom renderer for this. Yes it will work if all your data is of the same type. But what happens when you start to mix Strings, with Dates and Integers and Booleans which all use different renderers? Then you would need to create 4 custom renderers.
The better approach is to override the prepareRenderer(...) method JTable so you can add the code in one place. Here is an example to get you started. In reality you would want to use a CompoundBorder that contains a MatteBorder for the top/bottom and and EmptyBorder for the left/right and you would create a single instance of the Border.
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.text.*;
import javax.swing.border.*;
public class TablePrepareRenderer extends JFrame
{
JTable table;
public TablePrepareRenderer()
{
Object[] columnNames = {"Type", "Company", "Shares", "Price", "Boolean"};
Object[][] data =
{
{"Buy", "IBM", new Double(1000), new Double(80.5), Boolean.TRUE},
{"Sell", "MicroSoft", new Double(2000), new Double(6.25), Boolean.TRUE},
{"RSell", "Apple", new Double(3000), new Double(7.35), Boolean.TRUE},
{"Buy", "Nortel", new Double(4000), new Double(20), Boolean.TRUE}
};
DefaultTableModel model = new DefaultTableModel(data, columnNames);
table = new JTable( model )
{
// Returning the Class of each column will allow different
// renderers to be used based on Class
public Class getColumnClass(int column)
{
return getValueAt(0, column).getClass();
}
public Component prepareRenderer(
TableCellRenderer renderer, int row, int column)
{
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent)c;
// Color row based on a cell value
// Alternate row color
if (!isRowSelected(row))
c.setBackground(row % 2 == 0 ? getBackground() : Color.LIGHT_GRAY);
else
jc.setBorder(new MatteBorder(1, 0, 1, 0, Color.RED) );
// Use bold font on selected row
return c;
}
};
table.setPreferredScrollableViewportSize(table.getPreferredSize());
table.changeSelection(0, 0, false, false);
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args)
{
TablePrepareRenderer frame = new TablePrepareRenderer();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
You have the correct idea in mind, you will need to set the border on the label in the cellrenderer depending on where it is in the table(i.e. edge, center etc).
Take a look at matteborder. You can specify which areas to draw a border along w/ width and color.
I agree with > camickr
the best way to go is to override the prepareRendere method. The following code will create a border for a row with a selected cell:
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent)c;
if (isRowSelected(row)){
int top = (row > 0 && isRowSelected(row-1))?1:2;
int left = column == 0?2:0;
int bottom = (row < getRowCount()-1 && isRowSelected(row + 1))?1:2;
int right = column == getColumnCount()-1?2:0;
jc.setBorder(BorderFactory.createMatteBorder(top, left, bottom, right, this.getSelectionBackground()));
}
else
jc.setBorder(null);
return c;
}

Categories

Resources