I have defined cell editors for the two columns in my table in the following manner:
Java Code:
JComboBox combo = new JComboBox();
//code to add items to the combo box goes here.
JTextField textField = new JTextField();
textField.setHorizontalAlignment(JTextField.RIGHT);
TableColumn column = myJTable.getColumnModel().getColumn(0);
column.setCellEditor(new DefaultCellEditor(combo));
column = myJTable.getColumnModel().getColumn(1);
column.setCellEditor(new DefaultCellEditor(textField));
The problem I am facing is that when a focus is moved to a table cell, the cell doesn't become automatically editable. So, when the focus is moved to column 2 (that has a text field as an editor), the caret sign doesn't not appear unless the cell is double-clicked or the user starts typing. Similar is the case for column 1 (that has a combo box as an editor) as here the combo box doesn't appear unless the cell is clicked. These behaviors are counter-intuitive and undesirable for a user operating with the keyboard.:(
Please suggest pointers on how this could be resolved.
Thanks in advance.
This example overrides editCellAt() in a JTable having a DefaultCellEditor using JTextField.
You can bind the Space key to the startEditing action defined for JTable:
table.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "startEditing");
As commented above, you can use JComboBox both as renderer and editor. Below is a very basic example. It also shows DefaultCellEditor.setClickCountToStart() usage.
import java.awt.Component;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
public class ComboBoxEditorDemo {
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("ComboBoxEditorDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTable table = new JTable(
new Object[][] { { "1", "2" }, { "1", "2" } },
new Object[] {"Col1", "Col2" });
table.setRowHeight(24);
TableColumn column = table.getColumnModel().getColumn(1);
column.setCellRenderer(new MyComboBoxRenderer(new String[] { "1", "2", "3" }));
column.setCellEditor(new MyComboBoxEditor(new String[] { "1", "2", "3" }));
DefaultCellEditor editor = new DefaultCellEditor(new JTextField());
editor.setClickCountToStart(1);
column = table.getColumnModel().getColumn(0);
column.setCellEditor(editor);
JScrollPane scrollPane = new JScrollPane(table);
frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
static class MyComboBoxRenderer extends JComboBox implements
TableCellRenderer {
public MyComboBoxRenderer(String[] items) {
super(items);
}
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());
}
setSelectedItem(value);
return this;
}
}
static class MyComboBoxEditor extends DefaultCellEditor {
public MyComboBoxEditor(String[] items) {
super(new JComboBox(items));
}
}
}
Related
I customized a Jtable headers with a TableCellRenderer so filter icon would appear, and as the user clicks on the filter icon, a pop-up filter like excel's appears (picture 1). However, if the user clicks on the text of the header, a row sorter is appliead. When the row sorter is applied (picture 2), it override the customization and filter icon disappears. Is there a way to avoid this behavior while keeping the row sorter on the table header?
You can do this by decorating the original renderer of the table header with a custom one which will add the desired icon of filtering.
For example, follows some code to preserve the sort icons while adding your own label next to the original column header:
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.util.Objects;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
public class Main {
private static class MyTableCellRenderer extends DefaultTableCellRenderer {
private final TableCellRenderer originalRenderer;
public MyTableCellRenderer(final TableCellRenderer originalRenderer) {
this.originalRenderer = Objects.requireNonNull(originalRenderer);
}
#Override
public Component getTableCellRendererComponent(final JTable table,
final Object value,
final boolean isSelected,
final boolean hasFocus,
final int row,
final int column) {
final Component original = originalRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (row >= 0) //The header will have a row equal to -1.
return original;
final JPanel container = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
final JLabel filtering = new JLabel("Filter", JLabel.CENTER); //Your icon should go in this label...
//Put some fancy background to make the positioning of the label clear:
filtering.setOpaque(true);
filtering.setBackground(Color.CYAN);
//The default renderer comes with a border... Let's apply the border to the whole new container:
if (original instanceof JComponent) {
container.setBorder(((JComponent) original).getBorder());
((JComponent) original).setBorder(null);
}
container.add(filtering);
container.add(original);
return container;
}
}
private static void createAndShowGUI() {
final JTable table = new JTable(new Object[][] {
new Object[]{"Data001", "Data002", "Data003"},
new Object[]{"Data011", "Data012", "Data013"},
new Object[]{"Data021", "Data022", "Data023"},
new Object[]{"Data031", "Data032", "Data033"},
new Object[]{"Data041", "Data042", "Data043"}
}, new Object[] {"Column1", "Column2", "Column3"});
table.setAutoCreateRowSorter(true);
final JTableHeader header = table.getTableHeader();
header.setDefaultRenderer(new MyTableCellRenderer(header.getDefaultRenderer()));
final JFrame frame = new JFrame("Table header");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(Main::createAndShowGUI);
}
}
Instead of the word Filter (which appears on each column header) you can use your icons on the created label instead.
References:
Source (see the section: 3. Keeping sort icons).
How can I put a control in the JTableHeader of a JTable?
How can I set the margin of a specific row in a JTable by its row number?
Please refer to the illustration below I made using excel.
Also, I want a thick border and an invisible border
JTable has a method setRowHeight(...) which will allow you to increase the height of specific rows.
Regarding the border you can check out Table Row Rendering. It demonstrates how to override the prepareRenderer(...) method of the JTable to use a custom Border.
This is an example of a JTable with custom row borders - for specific rows. These specific rows are determined by the data in the table rows.
The example uses a JTable with a custom renderer - CustomBorderRenderer class which extends javax.swing.table.DefaultTableCellRenderer. There are two classes in this example application: TableCellRendererTester.java and CustomBorderRenderer.java.
The following is the image of the table with custom border for a specific row - where the Year value is "2012". The source code for the two classes follow.
The Example Code:
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class TableCellRendererTester {
private static final Object[] TABLE_COLUMNS = {"Book", "Author", "Year"};
private static final Object [][] TABLE_DATA = {
{"Book 1", "author 1", "1972"}, {"Book 2", "author 1", "1945"},
{"Book 3", "author 2", "2012"}, {"Book 4", "author 3", "1999"}
};
public static void main(String [] args) {
new TableCellRendererTester().createAndShowGUI();
}
public void createAndShowGUI() {
JFrame frame = new JFrame("Table Custom Row Border Tester");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getTablePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private JPanel getTablePanel() {
DefaultTableModel model = new DefaultTableModel(TABLE_DATA, TABLE_COLUMNS);
JTable table = new JTable(model);
table.setRowHeight(30);
CustomBorderRenderer renderer = new CustomBorderRenderer();
table.setDefaultRenderer(Object.class, renderer);
JScrollPane scrollpane = new JScrollPane(table);
scrollpane.setPreferredSize(new Dimension(400, 150));
scrollpane.setViewportView(table);
JPanel panel = new JPanel();
panel.add(scrollpane);
return panel;
}
}
The Renderer Class:
import java.awt.Color;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.JComponent;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
import javax.swing.table.TableModel;
import javax.swing.table.DefaultTableCellRenderer;
public class CustomBorderRenderer extends DefaultTableCellRenderer {
private int prevRow;
private boolean customBorderFlag;
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
JComponent c = (JComponent) super.getTableCellRendererComponent(table,
value, isSelected, hasFocus, row, column);
if (row != prevRow) {
// Row has changed.
// Store it so that all the columns can be rendered
// with the custom border - if condition is met.
customBorderFlag = false;
prevRow = row;
TableModel m = table.getModel();
String s = (String) m.getValueAt(row, 2);
// Check if the row needs to be rendered with a custom border.
if (s.equals("2012")) {
customBorderFlag = true;
}
}
if (customBorderFlag) {
// Set custom border for all column cells - for a row.
c.setBorder(getCustomBorder());
}
return c;
}
/*
* Returns a custom border with:
* 1. Thick border
* 2. A margin between cell content (text) and the border.
*/
private Border getCustomBorder() {
Border insideMargin = BorderFactory.createEmptyBorder(10, 10, 10, 10);
Border thickBorder = BorderFactory.createLineBorder(Color.BLACK, 3);
Border borderWithMargin =
BorderFactory.createCompoundBorder(thickBorder, insideMargin);
return borderWithMargin;
}
}
EDIT: Updated the image and the CustomBorderRenderer.java class to show the custom border with margin.
I am using JComboBox in Jtable cell. When I click on the JComboBox and select a value from it, it calls the ActionPerformed fuction. Till here it is working fine but as soon as I click on the JComboBox again, it calls the ActionPerformed function, which it should not. What I want is, to call the ActionPerformed function when the item is selected in the JComboBox. In other words it should work as it worked for the first time when the item was selected from the JComboBox and then the ActionPerformed function was called. I cannot figure out why this problem is occurring. Here are the links that I have looked into and I did some other searches also but still could not find any relative answer to the above mentioned problem.
Adding JComboBox to a JTable cell
How to use ActionListener on a ComboBox to give a variable a value
https://coderanch.com/t/339842/java/ComboBox-ItemListener-calling
Here is the code, you can copy paste it and check it.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
public class TableExample implements ActionListener{
JFrame frame;
JComboBox skuNameComboBoxTable;
TableExample() {
frame = new JFrame();
String data[][] = {{"101", "Amit", "Choose"},
{"102", "Jai", "Choose"},
{"101", "Sachin", "Choose"}};
String column[] = {"ID", "Name", "Degree"};
JTable table = new JTable(data, column);
table.setBounds(30, 40, 200, 300);
JScrollPane scrollPane = new JScrollPane(table);
frame.add(scrollPane);
frame.setSize(300, 400);
frame.setVisible(true);
String[] array = {"BS(SE)", "BS(CS)", "BS(IT)"};
skuNameComboBoxTable = new JComboBox(array);
skuNameComboBoxTable.addActionListener(this);
TableColumn col = table.getColumnModel().getColumn(2);
col.setCellEditor(new DefaultCellEditor(skuNameComboBoxTable));
}
public static void main(String[] args) {
new TableExample();
}
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "actionPerformed called");
}
}
Kindly tell me why this problem is occurring and how should I solve it.
Unfortunately, you can't do much when using the DefaultCellEditor - that is how it behaves. Within your code you can add a check to ensure that a change in value occured before handling the event. Something like below:
public void actionPerformed(ActionEvent e) {
if (skuNameSelected == null || skuNameComboBoxTable.getSelectedItem() != skuNameSelected)
JOptionPane.showMessageDialog(null, "actionPerformed called: ");
skuNameSelected = (String) skuNameComboBoxTable.getSelectedItem();
}
You can try using ItemListener and filter your action according to the ItemEvent.
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
public class JComboBoxTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String[] items = {"One", "Two", "Three"};
JComboBox cb = new JComboBox(items);
cb.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
System.out.println("Selected " + e.getItem());
} else {
System.out.println("Deselected " + e.getItem());
}
}
});
frame.add(cb);
frame.pack();
frame.setVisible(true);
}
}
This is happening because you are using the same JComboBox as the DefaultCellEditor for column 2.
Whenever you click a cell from column 2 the ComboBox will change to the value that is on the cell at the moment and that triggers the DESELECT (from the old value) and the SELECT (for the new value). This will only happen if the old value and the new value are not the same.
One way to avoid this is to add a CellEditorListener on the DefaultCellEditor that you are using, like below:
TableColumn col = table.getColumnModel().getColumn(2);
DefaultCellEditor cellEditor = new DefaultCellEditor(skuNameComboBoxTable);
col.setCellEditor(cellEditor);
cellEditor.addCellEditorListener(new CellEditorListener() {
#Override
public void editingStopped(ChangeEvent e) {
System.out.println("Value of combo box defined!");
}
#Override
public void editingCanceled(ChangeEvent e) {
System.out.println("Edition canceled, set the old value");
}
});
This way you will be able to only act when the value has been defined by the ComboBox.
I hope this helps.
You should not be using an ActionListener for this. The combo box uses its own ActionListener to update the TableModel when an item is selected.
So instead you should be listening for changes in the TableModel in order to do your custom code. So you should be using a TableModelListener to listen for changes in the data. However, a TableModelListener can fire an event even if just start and stop editing of the cell which might be a problem for you.
In this case you can use the Table Cell Listener. It will only generate an event when the value in the TableModel has changed.
I have been trying to find a solution for this for the past few days and its driving me crazy. I have a table in which I set the selection colour to yellow. I also set the background of the cell editor component to yellow so that the colour remains the same when the cell is being edited. I do that by overriding the prepareEditor method as such:
#Override
public Component prepareEditor(TableCellEditor editor, int row, int col) {
Component c = super.prepareEditor(editor, row, col);
c.setBackground(Color.YELLOW);
c.setFont(myFont);
return c;
}
This is working fine for all columns except for a column in which I assign a combo box as the cell editor. Once I start editing a cell in that column the background becomes white. The background colour in the popup menu is Yellow but the background colour in the selected value box remains white. I tried adding a focus listener to the combo box but all that I was able to do was change the background of the popup items and not the background of the selected item. I tried adding the focus listener to the combo box itself as such:
myComboBox.addFocusListener(new FocusListener() {//code here});
and to the editor component as such:
myComboBox.getEditor().getEditorComponent().addFocusListener(new FocusListener() {//code here});
and none of these worked. Can someone please point out what Im doing wrong? Thanks.
You probably need to override the cell renderer. Use this in your UI manager and change the paintComponent method to your liking.
public class MyComboBoxUI extends MetalComboBoxUI {
public MyComboBoxUI() {
}
public static ComponentUI createUI(JComponent c) {
return new MyComboBoxUI();
}
#Override
public void installUI(JComponent c) {
ListCellRenderer rend = new ListCellRenderer() {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final JLabel renderer = new JLabel(value.toString()) {
protected void paintComponent(java.awt.Graphics g) {
UIDefaults uid = UIManager.getDefaults();
Graphics2D g2d = (Graphics2D)g;
Dimension d = this.getSize();
g2d.setPaint(new GradientPaint(0, 0, Color.red, 0, d.height, Color.orange, true));
g2d.fillRect(0, 0, d.width, d.height);
super.paintComponent(g);
}
};
renderer.setOpaque(false);
return renderer;
}
};
super.installUI(c);
((JComboBox)c).setRenderer(rend);
}
}
I managed to find a solution to the problem and Im posting it here in case anyone faces the same problem. the following is the code:
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.jdesktop.swingx.autocomplete.ComboBoxCellEditor;
public class ComboTest {
JTable table;
JComboBox comboBox;
ComboTest(){
String[] headings = {"Type", "Reference Number", "Amount"};
Object[][] data = {
{"Cash", "123", "2000"},
{"Online", "333", "1200"},
{"Bank Transfer", "667", "800"}
};
comboBox = new JComboBox(new String[] {"Cash", "Cheque", "Bank Transfer", "Credit Card", "Online"});
AutoCompleteDecorator.decorate(comboBox);
JFrame jfrm = new JFrame("Example");
jfrm.getContentPane().setLayout(new FlowLayout());
jfrm.setSize(500, 160);
table = new JTable(data, headings);
table.setSelectionBackground(Color.GREEN);
TableColumn ledgerColumn = table.getColumnModel().getColumn(0);
ledgerColumn.setCellEditor(new ComboBoxCellEditor(comboBox));
//This is the code that changes the colour of the combo Box when it is selected.
comboBox.getEditor().getEditorComponent().addFocusListener(new FocusListener() {
public void focusGained(FocusEvent arg0) {
comboBox.getEditor().getEditorComponent().setBackground(Color.GREEN);
}
public void focusLost(FocusEvent arg0) {
comboBox.getEditor().getEditorComponent().setBackground(Color.WHITE);
}
});
JScrollPane jscrlp = new JScrollPane(table);
jfrm.getContentPane().add(jscrlp);
jfrm.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable () {
public void run() {
new ComboTest();
}
});
}
}
I have read through the majority of the JTable/JComboBox responses to other questions of this ilk, but haven't yet found a solution to my problem.
I have created a table that has JComboBox TableHeader elements. None of the JComboBox elements will open to display a list of items. How do I get the item lists for the individual JComboBox elements to display?
Please note that a distinguishing element of this question is that the JComboBox is in the TableHeader, not embedded within a JTable cell.
Any help is appreciated.
SSCE
import java.awt.Component;
import java.awt.Dimension;
import java.util.Enumeration;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
public class ComboHeaderTest extends JScrollPane {
private static final Dimension DEFAULT_SIZE = new Dimension(200, 200);
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ComboHeaderTest().initComponents();
}
});
}
private ComboHeaderTest() {
final String[][] data = { {"Header 1", "Header 2", "Header 3"},
{"A", "B", "C"},
{"D", "E", "F"},
{"G", "H", "I"}};
setViewportView(getTable(data));
setPreferredSize(DEFAULT_SIZE);
}
private void initComponents() {
JFrame frame = new JFrame("ComboHeaderTest");
frame.add(this);
frame.pack();
frame.setVisible(true);
}
private JTable getTable(final String[][] data) {
final String[] items = data[0];
final ComboHeaderRenderer[] columnHeaders = new ComboHeaderRenderer[items.length];
for(int i = 0; items.length > i; ++i) {
columnHeaders[i] = new ComboHeaderRenderer(items);
}
final JTable table = new JTable(data, columnHeaders);
final Enumeration<TableColumn> tableEnum = table.getColumnModel().getColumns();
for (int columnIndex = 0; tableEnum.hasMoreElements(); ++columnIndex) {
final TableColumn column = tableEnum.nextElement();
final ComboHeaderRenderer combo = columnHeaders[columnIndex];
column.setHeaderValue(combo.getItemAt(columnIndex));
column.setHeaderRenderer(combo);
}
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowSelectionAllowed(true);
table.setColumnSelectionAllowed(false);
table.setCellSelectionEnabled(false);
table.setFillsViewportHeight(true);
table.setSize(DEFAULT_SIZE);
table.validate();
return table;
}
private static class ComboHeaderRenderer extends JComboBox implements TableCellRenderer{
public ComboHeaderRenderer(final String[] entries) {
for (int i = 0; entries.length > i; ++i) {
addItem(entries[i]);
}
}
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value,
final boolean isSelected, final boolean hasFocus, final int row, final int column) {
setSelectedItem(value);
return this;
}
}
}
This actually works exactly as expected. I think the clue is TableCellRenderer.
Renderer's are typically non-interactive components. They are usually just a "snap-shot" of the component painted on to a surface and there is typically no way for a user to interact with them.
This is the expected behavior.
In order to supply editable functionality to the table header, you're going to need to supply your implementation of a JTableHeader
Have a look at this example for some ideas
Here is an example that uses a JComboBox in a JTable TableHeader.
For other types of components is easier, have a look here.