JTable sorter not working - java

I have a very simple JTable that I want to sort in ascending order by column 0. The code is very simple. But it is not able to sort the rows properly. I can not figure out what is wrong. Following is my code:
package test;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class TableSorter {
public static void main(String[] args) {
new TableSorter();
}
public TableSorter() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException |
IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new String[] {"X", "Y", }, 0);
model.addRow(new Object[]{5, 8});
model.addRow(new Object[]{10, 5});
model.addRow(new Object[]{50, 60});
model.addRow(new Object[]{100, 60});
JTable table = new JTable(model);
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(sorter);
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
sorter.setSortKeys(sortKeys);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Here is the result when I run the program
Results:
X y
10 5
100 60
5 8
50 60
Any help will be appreciated. The result is attached.
Thanks in advance.

From the documentation of DefaultTableModel :
Warning: DefaultTableModel returns a column class of Object. When
DefaultTableModel is used with a TableRowSorter this will result in
extensive use of toString, which for non-String data types is
expensive. If you use DefaultTableModel with a TableRowSorter you are
strongly encouraged to override getColumnClass to return the
appropriate type.
You need to override getColumnClass of your table model like :
DefaultTableModel model = new DefaultTableModel(new String[] {"X", "Y", }, 0)
{
#Override
public Class<?> getColumnClass(int column)
{
Class<?> returnValue;
if ((column >= 0) && (column < getColumnCount()))
{
returnValue = getValueAt(0, column).getClass();
}
else
{
returnValue = Object.class;
}
return returnValue;
};
};
In your case it is comparing Integer.toString() on integers and hence the wrong order that you see.
By overriding getColumnClass() to return Integer type, you will get comparison of integers by their values.

Related

JComboBox in JTable cell. Selection change affects JComboBox in other rows

I have been lurking around here for a couple years or so, never needed to ask a question before because I have always found my answer in someone else's question. Thank you!
I guess the lurking has come to an end. I have seen similar questions but not exactly this situation on here:
I have a 2 column JTable with a JComboBox in the first column, and an integer in the second column.
JComboBox has ItemListener set up so that when the selection in the JComboBox is changed the value in the Integer column is set to the comboBox selected index. Right click on the table renders JPopupMenu with addRow() as MouseEvent.
It works fine as long as I add all the rows I want when setting up the DefaultTableModel. But, if I start the model with only one row and use the MouseEvent (or any other method of adding rows other than adding them in parameters of DefaultTableModel) to add rows, when we start changing the selections in the combo boxes it will change the integer values in other rows.
For example: If i run the program and immediately add two more rows via MouseEvent, I then select Zero from combo in row 0, One from combo in row 1, and Two from combo in row 2. All fine so far...
Then I go back to row 0 and as soon as I activate the combobox (I haven't selected an item yet...) it changes the integer in row 2 to 0.
Can anyone tell me how to stop the integers from changing in this manner?
here is a trial code:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
public class JComboBoxInJTable {
public static void main(String[] args) {
new JComboBoxInJTable();
}
public JComboBoxInJTable() {
EventQueue.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new Object[]{"ComboBox", "Index"}, 1);
JTable table = new JTable(model);
//popup menu to add row
JPopupMenu popup = new JPopupMenu();
JMenuItem newRow;
newRow = new JMenuItem("New Row");
newRow.setToolTipText("Add new row.");
newRow.addActionListener((ActionEvent nr) -> {
model.addRow(new Object[]{"", ""});
});
popup.add(newRow);
//set up right-click to open popup menu
table.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent rc) {
if (SwingUtilities.isRightMouseButton(rc)) {
if (table.getSelectedRow() >= 0) {
popup.show(table, rc.getX(), rc.getY());
}
}
}
});
JComboBox combo = new JComboBox(new Object[]{"Zero", "One", "Two", "Three"});
combo.addItemListener((ItemEvent e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
//sets value of cell to left of combobox to comboboxe's selected index
table.setValueAt(combo.getSelectedIndex(), table.getSelectedRow(), 1);
} else {
//do nothing...
}
});
DefaultCellEditor comboEditor = new DefaultCellEditor(combo);
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setCellEditor(comboEditor);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
As usual I am sure this is something simple that I am missing. Thanks Again for the help in the past and in the future!
Don't use MouseListeners to show popup menus, different OSs have different triggers for the popups, and not all are triggered by mousePressed. Instead use JComponent#setComponentPopupMenu and let the API deal with it
Don't modify the state of the model from an editor. This can place the model into an invalidate state and cause other side effects, like you have now. Once seems to be happening is when a new row is selected, the editor is been updated with the rows cell value, but the row selection hasn't been set, so the table still thinks the previous row is still selected. Instead, use the models setValueAt method to make decisions about what to do once the first columns value is changed.
For example...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
public class JComboBoxInJTable {
public static void main(String[] args) {
new JComboBoxInJTable();
}
private List<String> comboData;
public JComboBoxInJTable() {
EventQueue.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new Object[]{"ComboBox", "Index"}, 1) {
#Override
public void setValueAt(Object aValue, int row, int column) {
super.setValueAt(aValue, row, column);
if (column == 0) {
String value = aValue == null ? null : aValue.toString();
if (aValue == null) {
super.setValueAt(null, row, 1);
} else {
super.setValueAt(comboData.indexOf(aValue), row, 1);
}
}
}
};
JTable table = new JTable(model);
table.setFillsViewportHeight(true);
table.setGridColor(Color.GRAY);
//popup menu to add row
JPopupMenu popup = new JPopupMenu();
JMenuItem newRow;
newRow = new JMenuItem("New Row");
newRow.setToolTipText("Add new row.");
newRow.addActionListener((ActionEvent nr) -> {
model.addRow(new Object[]{"", ""});
});
popup.add(newRow);
table.setComponentPopupMenu(popup);
comboData = new ArrayList<>(Arrays.asList(new String[]{"Zero", "One", "Two", "Three"}));
JComboBox combo = new JComboBox(comboData.toArray(new String[comboData.size()]));
// combo.addItemListener((ItemEvent e) -> {
// if (e.getStateChange() == ItemEvent.SELECTED) {
// //sets value of cell to left of combobox to comboboxe's selected index
// System.out.println("Selected row = " + table.getSelectedRow());
// table.setValueAt(combo.getSelectedIndex(), table.getSelectedRow(), 1);
// } else {
// //do nothing...
// }
// });
DefaultCellEditor comboEditor = new DefaultCellEditor(combo);
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setCellEditor(comboEditor);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}

JComboBox on cell of JTable

I have a JTable object and i would add 5 different JComboBox on a single column.
I've so tried:
table.getColumnModel().getColumn(3).setCellEditor(new DefaultCellEditor(jcombo));
but this add the same JComboBox to all cells of that column. How can i do to add different ones on the same column?
Thank you!
Basically, you need to modify the model which the combobox is using depending on the row.
The following example allows you to specify a ComboBoxModel for a given row and provide a default ComboBoxModel to be used when one is not specified for the row.
Generally speaking though, each column should be of the same type...
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
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.DefaultTableModel;
public class TestCombo {
public static void main(String[] args) {
new TestCombo();
}
public TestCombo() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new Object[]{"Stuff"}, 5);
JTable table = new JTable(model);
table.setGridColor(Color.LIGHT_GRAY);
MyComboBoxCellEditor editor = new MyComboBoxCellEditor();
editor.setModelForRow(0, new DefaultComboBoxModel(new Object[]{"Banana", "Peach", "Pear"}));
editor.setModelForRow(1, new DefaultComboBoxModel(new Object[]{"Dog", "Cat", "T-Rex"}));
editor.setModelForRow(2, new DefaultComboBoxModel(new Object[]{"Car", "Truck", "Hovercraft"}));
editor.setModelForRow(3, new DefaultComboBoxModel(new Object[]{"Helicopter", "Plane", "Rocket"}));
editor.setModelForRow(4, new DefaultComboBoxModel(new Object[]{"PC", "Mac", "Linux"}));
table.getColumnModel().getColumn(0).setCellEditor(editor);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MyComboBoxCellEditor extends DefaultCellEditor {
private ComboBoxModel defaultModel;
private Map<Integer, ComboBoxModel> mapModels;
public MyComboBoxCellEditor() {
super(new JComboBox());
mapModels = new HashMap<>(25);
defaultModel = new DefaultComboBoxModel();
}
public void setDefaultModel(ComboBoxModel model) {
defaultModel = model;
}
public void setModelForRow(int row, ComboBoxModel model) {
mapModels.put(row, model);
}
public ComboBoxModel getDefaultModel() {
return defaultModel;
}
public ComboBoxModel getModelForRow(int row) {
return mapModels.get(row);
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JComboBox comboBox = (JComboBox) getComponent();
ComboBoxModel model = getModelForRow(row);
if (model == null) {
model = getDefaultModel();
}
comboBox.setModel(model);
return super.getTableCellEditorComponent(table, value, isSelected, row, column); //To change body of generated methods, choose Tools | Templates.
}
}
}

Display JCheckBox in JTable

I have a Jtable in which I want to add a JCheckbox in one column. However, when I create a JCheckbox object, javax.swing.JCheckBox is being displayed in the column.Please refer to the image. Can you tell me how to amend that please? I have searched everywhere but cannot seem to find any solution for it. Thank you.
don't add components to your TableModel, that's not the responsibility of the TableModel
You will need to specify the class type of your column. Assuming you're using a DefaultTableModel, you can simply fill the column with a bunch of booleans and this should work - After testing, you will need to override the getColumnClass method of the DefaultTableModel (or what ever TableModel implementation) and make sure that for the "check box" column, it returns Boolean.class
See How to use tables for more details
For example...
import java.awt.EventQueue;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class TestCardLayout {
public static void main(String[] args) {
new TestCardLayout();
}
public TestCardLayout() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
Random rnd = new Random();
DefaultTableModel model = new DefaultTableModel(new Object[]{"Check boxes"}, 0) {
#Override
public Class<?> getColumnClass(int columnIndex) {
return Boolean.class;
}
};
for (int index = 0; index < 10; index++) {
model.addRow(new Object[]{rnd.nextBoolean()});
}
JTable table = new JTable(model);
final JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Enable and Disable Sorting of JTable dynamically

I'm new to Java Swing and I'm working with JTable.
Actually, I have difficulty in sorting of this component.
I want to enable and disable this function programatically.
I use JTable.setAutoCreateSorter(true) to initialize it, I change to false, nothing happen.
Also, I had use JTable.setRowSorter(null), it works, but I do not know how to enable it again.
Sorry for my poor English!
You can use setSortable(int index, boolean sortable) method of DefaultRowSorter.
for (int i=0 ; i<table.getColumnCount() ; i++) {
sorter.setSortable(i, false);
}
This worked for me. See also JTable sorting programatically only
Inspecting at JTable.setAutoCreateRowSorter(boolean autoCreateRowSorter) source code:
public void setAutoCreateRowSorter(boolean autoCreateRowSorter) {
boolean oldValue = this.autoCreateRowSorter;
this.autoCreateRowSorter = autoCreateRowSorter;
if (autoCreateRowSorter) {
setRowSorter(new TableRowSorter<TableModel>(getModel()));
}
firePropertyChange("autoCreateRowSorter", oldValue,
autoCreateRowSorter);
}
So, you have two options:
Keep a reference to the row sorter to restore it later, as #nachokk suggested.
Set a new TableRowSorter instance:
table.setRowSorter(new TableRowSorter(table.getModel()));
Edit
But now the table's rows are not selectable
That's weird, I didn't have any problem with row selection. Here is the code I've used to test my answer:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
public class Demo {
private void createAndShowGUI() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"Column # 1", "Column # 2"}, 0){
#Override
public Class<?> getColumnClass(int columnIndex) {
switch(columnIndex) {
case 0: return String.class;
case 1: return Integer.class;
default: return super.getColumnClass(columnIndex);
}
}
};
for (int i = 0; i < 20; i++) {
model.addRow(new Object[]{"Property # " + i, Integer.valueOf(i)});
}
final JTable table = new JTable(model);
table.setAutoCreateRowSorter(true);
JToggleButton toggleButton = new JToggleButton("Disable", true);
toggleButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JToggleButton toggleButton = (JToggleButton)e.getSource();
String text = toggleButton.isSelected() ? "Disable" : "Enable";
TableRowSorter sorter = toggleButton.isSelected() ? new TableRowSorter(table.getModel()) : null;
toggleButton.setText(text);
table.setRowSorter(sorter);
}
});
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(new JScrollPane(table), BorderLayout.CENTER);
frame.add(toggleButton, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
}

How can I get the filtered model?

I'm working with JTables to display information that users can filter, and if the user saves after filtering I want to save the filtered table to a textfile for persistence (meaning anything that got filtered out will not be saved to the textfile).
For filtering I just followed the filtering part of this tutorial: http://download.oracle.com/javase/tutorial/uiswing/components/table.html#sorting and it works fine, but I'm not sure of any way that I can get a model of the current display as opposed to the underlying model that contains everything that hasn't been filtered out.
Is there any way to do this with this with the way that I'm filtering?
Thanks!
Ask the table its number of rows (using getRowCount()), which will return the number of filtered (visible) rows. Iterate from 0 to the rowCount, convert each row index to the model index using convertRowIndexToModel(), and ask your model the data at each model index to build the list of filtered (visible) data.
This code shows how to do this. Please note that B row is not printed to the input after the button is pressed.
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowFilter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.JButton;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class JTableFilterDemo {
public static void main(String[] args) {
Object[][] data = { { "A", 5 }, { "B", 2 }, { "C", 4 }, { "D", 8 } };
String columnNames[] = { "Item", "Value" };
TableModel model = new DefaultTableModel(data, columnNames) {
public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
JTable table = new JTable(model);
RowFilter<Object, Object> filter = new RowFilter<Object, Object>() {
public boolean include(Entry entry) {
Integer population = (Integer) entry.getValue(1);
return population.intValue() > 3;
}
};
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(
model);
sorter.setRowFilter(filter);
table.setRowSorter(sorter);
JScrollPane scrollPane = new JScrollPane(table);
JFrame frame = new JFrame("Filtering Table");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton btnNewButton = new JButton("Print values");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for(int row = 0;row < table.getRowCount();row++) {
System.out.println(table.getModel().getValueAt(table.convertRowIndexToModel(row), 0));
}
}
});
frame.getContentPane().add(btnNewButton, BorderLayout.SOUTH);
frame.getContentPane().add(scrollPane);
frame.setSize(300, 200);
frame.setVisible(true);
}
}

Categories

Resources