Disable JTableHeader update after row sorting - java

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?

Related

Set margin and custom border of a specific row for 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.

How to display differnt values in JCombo in each row

For my project i just want to show a dialogue box with JTable. On that i want to show a JCombobox with available staff based on row index. I tried the following coding,
for(int i=0;i<n;i++)
{
Object obj[] = new Object[4];
obj[0]=2,
obj[1]=3;
obj[2]=""; //Here combo appear.
obj[3]=3;
JComboBox aa = new JComboBox();
for(int j=0;j<m;j++)
{
aa.addItem(rs.getString(1));
aa.addItem(rs.getString(2));
}
table.getcolumnModel.getcolumn(2).setcellEditor(new DefaultCellEditor(aa));
model.addRow(obj);
}
if i use this output generated. But last rows combo value is present in all previous rows combo. Those different values are not in that. its totally same. But all other text fields are correctly displayed. What should i do here. thanking you...
Note: Here
aa.addItem(rs.getString(1));
aa.addItem(rs.getString(2));
is just for example. Actually it will return many number of values based on id.
You try to set editor to each row, but it's wrong, editor can be set to whole column. Read Concepts: Editors and Renderers. Instead of that implement your logic in getTableCellEditorComponent() method of TableCellEditor.
Simple example with different values for each row:
import java.awt.Component;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;
public class TestFrame extends JFrame {
private DefaultComboBoxModel<String> model;
private Map<String, List<String>> keyVal;
public TestFrame() {
init();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void init() {
keyVal = new HashMap<>();
keyVal.put("1", Arrays.asList(new String[]{"a","b"}));
keyVal.put("0", Arrays.asList(new String[]{"c","d"}));
keyVal.put("2", Arrays.asList(new String[]{"e","f","g"}));
JTable t = new JTable(3,3);
t.getColumnModel().getColumn(0).setCellEditor(getEditor());
add(new JScrollPane(t));
}
private TableCellEditor getEditor() {
return new DefaultCellEditor(new JComboBox<String>(model = new DefaultComboBoxModel<String>())){
#Override
public Component getTableCellEditorComponent(JTable table,Object value, boolean isSelected, int row, int column) {
model.removeAllElements();
if(keyVal.containsKey(row+"")){
List<String> list = keyVal.get(row+"");
for(String s : list)
model.addElement(s);
}
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
};
}
public static void main(String args[]) {
new TestFrame();
}
}

How to set the title of a JComboBox when nothing is selected?

I want to have a JCombobox in my Swing application, which shows the title when nothing is selected. Something like this:
COUNTRY ▼
Spain
Germany
Ireland
I want "COUNTRY" to show when the selected index is -1 and thus, the user wouldn't be able to select it. I tried to put it on the first slot and then overriding the ListCellRenderer so the first element appears greyed out, and handling the events so when trying to select the "title", it selects the first actual element, but I think this is a dirty approach.
Could you lend me a hand?
Overriding the ListCellRenderer is a good approach, but you tried something overly complicated. Just display a certain string if you are rendering the cell -1 and there is no selection (value is null). You are not limited to display elements on the list.
The following is an example program that demonstrates it:
Full Code:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
public class ComboBoxTitleTest
{
public static final void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
new ComboBoxTitleTest().createAndShowGUI();
}
});
}
public void createAndShowGUI()
{
JFrame frame = new JFrame();
JPanel mainPanel = new JPanel();
JPanel buttonsPanel = new JPanel();
frame.add(mainPanel);
frame.add(buttonsPanel, BorderLayout.SOUTH);
String[] options = { "Spain", "Germany", "Ireland", "The kingdom of far far away" };
final JComboBox comboBox = new JComboBox(options);
comboBox.setRenderer(new MyComboBoxRenderer("COUNTRY"));
comboBox.setSelectedIndex(-1); //By default it selects first item, we don't want any selection
mainPanel.add(comboBox);
JButton clearSelectionButton = new JButton("Clear selection");
clearSelectionButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
comboBox.setSelectedIndex(-1);
}
});
buttonsPanel.add(clearSelectionButton);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class MyComboBoxRenderer extends JLabel implements ListCellRenderer
{
private String _title;
public MyComboBoxRenderer(String title)
{
_title = title;
}
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean hasFocus)
{
if (index == -1 && value == null) setText(_title);
else setText(value.toString());
return this;
}
}
}
index == -1 in the renderer is the head component that, by default, displays the selected item and where we want to put our title when there's no selection.
The renderer knows that there's nothing selected because the value passed to it is null, which is usually the case. However if for some weird reasons you had selectable null values in your list, you can just let the renderer consult which is the explicit current selected index by passing it a reference to the comboBox, but that is totally unrealistic.

TableHeaderRenderer with Nimbus

I'm have a custom table header renderer that will have the standard label and a button inside a JComponent.
The issue I'm having is with the label returned by the default renderer. The call to the default renderer provides the standard label. If I return that as is, it looks as expected. If I try to modify the background or border nothing changes. Modifying the foreground does have the intended effect, however. I do not want to view the sort icons, so I'm attempting to construct a JLabel that looks the same, minus the icons. That is not working correctly either. My JLabel is opaque.
JLabel l = (JLabel)table.getTableHeader().getDefaultRenderer().getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
this.lbl.setBackground(l.getBackground());
return this.lbl;
I looked at the source for DefaultTableHeaderRenderer and I can't find anything special that the default class is doing.
I've also tried the following with no effect.
this.lbl.setOpaque(true);
this.lbl.setFont(UIManager.getFont("TableHeader.font"));
this.lbl.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
this.lbl.setBackground(UIManager.getColor("TableHeader.background"));
this.lbl.setForeground(UIManager.getColor("TableHeader.foreground"));
return this.lbl;
EDIT: Clarification. Both code snippets above are inside getTableCellRenderComponent() of my custom renderer. I've tried both ways and neither has worked.
Try using the UIManagers values directly.
TableHeader.background = DerivedColor(color=214,217,223 parent=control offsets=0.0,0.0,0.0,0 pColor=214,217,223
TableHeader.font = javax.swing.plaf.FontUIResource[family=SansSerif,name=sansserif,style=plain,size=12]
TableHeader.foreground = DerivedColor(color=0,0,0 parent=text offsets=0.0,0.0,0.0,0 pColor=0,0,0
TableHeader.opaque = true
Something like UIManager.getColor("TableHeader.background") for example
The border I think you'll find is actually painted by the UI delegate directly.
Updated with example
From the included image, it's obvious that using UIManager does provide some of the basic information need to get the values used by the header, but it does highlight that the renderer is doing some special painting to get the shading.
The second column is the default renderer and the third is cheeky. It is basically stealing the cell renderer directly from the table header...
package testcellrenderer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
public class TestCellRenderer {
public static void main(String[] args) {
new TestCellRenderer();
}
public TestCellRenderer() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
DefaultTableModel model = new DefaultTableModel(
new Object[][]{{"Testing", "Testing", "Testing"}},
new Object[]{"Test A", "Test B", "Test C"}
);
JTable table = new JTable(model);
table.getColumn("Test A").setCellRenderer(new TestTableCellRenderer());
table.getColumn("Test C").setCellRenderer(table.getTableHeader().getDefaultRenderer());
setLayout(new BorderLayout());
add(new JScrollPane(table));
}
}
protected class TestTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Color background = UIManager.getColor("TableHeader.background");
Color foreground = UIManager.getColor("TableHeader.foreground");
Font font = UIManager.getFont("TableHeader.font");
boolean opaque = UIManager.getBoolean("TableHeader.opaque");
setBackground(background);
setForeground(foreground);
setFont(font);
setOpaque(opaque);
return this;
}
}
}

Start editing in a cell in JTable on gaining focus

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

Categories