Issue after filtering JTable - java
I have an issue with a program I am working on. To briefly explain, I have a JTable with multiple columns and rows. Particular columns have editable fields when upon changing the value other column values change according to the inputted data. Everything works well however when I've added a filter option to the JTable, changes made to an editable column won't change the values of other columns as intended after applying the filter. I've attached a couple of images to show the problem.
The first image shows the unfiltered table working correctly. Changing a Discount column value will reduce the corresponding price stored in the GPL column by the percent the inputted discount and displayed in the corresponding row in the SP column. Changing a Quantity column value will multiply the corresponding SP column price with the inputted quantity and displayed in the corresponding row in the Total column.
The second image shows the filtered table not working as intended. Changing a value in either Discount or Quantity columns will not change the intended columns.
I've added the SSCCE code below which contains 2 classes. First is the table itself and the second is the listener for the table.
EDIT I've changed the code of the class according to camickr's answer and now fully works.
TableCellChange class
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.DecimalFormat;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public final class TableCellChange extends JPanel {
private static JFrame frameTableCellChange;
private JPanel panelTable, panelButtons;
private JButton buttonResetDiscounts, buttonResetQuantities, buttonExit;
private JTextField textFilterBox, quantityField, discountField;
private JLabel labelFilter;
private DefaultTableModel tableModel;
private JTable table;
private TableRowSorter<DefaultTableModel> sorter;
private TableColumn columnDiscount, columnTotal, columnQuantity;
private TableCellListener tableCellListener;
private String checkForNull;
private DecimalFormat decimalFormatUS;
private Locale localeUSFormat;
private BigDecimal valueDiscount, valueGPL, resultDiscount, resultSP, resultTotal,
backupDiscount = new BigDecimal("0");
private int selectedColumnIndex, selectedRowIndex, valueQuantity, backupQuantity = 1;
public TableCellChange() {
super();
panelTable = new JPanel();
panelButtons = new JPanel();
setLayout(new BorderLayout());
createTable();
createButtons();
add(panelTable, BorderLayout.NORTH);
add(panelButtons, BorderLayout.CENTER);
// Always focus on the JTextField when opening the window
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
textFilterBox.requestFocusInWindow();
}
});
} // -> TableCellChange()
// Create the buttons for the query result window
public void createButtons() {
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints gridcons = new GridBagConstraints();
gridcons.fill = GridBagConstraints.HORIZONTAL;
panelButtons.setLayout(gridbag);
labelFilter = new JLabel("Quick search:");
gridcons.insets = new Insets(5,0,0,0);
gridcons.gridx = 0;
gridcons.gridy = 0;
gridcons.gridwidth = 2;
gridbag.setConstraints(labelFilter, gridcons);
labelFilter.setHorizontalAlignment(JLabel.CENTER);
panelButtons.add(labelFilter);
// Create text field for filtering
textFilterBox = new JTextField();
gridcons.insets = new Insets(5,0,0,0);
gridcons.gridx = 0;
gridcons.gridy = 1;
gridcons.gridwidth = 2;
gridbag.setConstraints(textFilterBox, gridcons);
textFilterBox.getDocument().addDocumentListener(
new DocumentListener() {
#Override
public void changedUpdate(DocumentEvent e) {
tableFilter();
}
#Override
public void insertUpdate(DocumentEvent e) {
tableFilter();
}
#Override
public void removeUpdate(DocumentEvent e) {
tableFilter();
}
}); // -> DocumentListener()
panelButtons.add(textFilterBox);
// Create the button to reset the discount column to 0%
buttonResetDiscounts = new JButton("Reset all discounts");
buttonResetDiscounts.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
BigDecimal valueGPL, valueTotal;
int valueQuantity;
for (int i = 0; i < table.getModel().getRowCount(); i++) {
valueGPL = new BigDecimal( table.getModel().
getValueAt(i, 2).toString().replaceAll("[$,]", "") );
table.getModel().setValueAt("0%", i, 3);
table.getModel().setValueAt(DecimalFormat
.getCurrencyInstance(localeUSFormat).format(valueGPL), i, 4);
valueQuantity = Integer.parseInt( table.getModel().
getValueAt(i, 5).toString() );
valueTotal = valueGPL.multiply(new BigDecimal(valueQuantity),
new MathContext(BigDecimal.ROUND_HALF_EVEN));
table.getModel().setValueAt(DecimalFormat
.getCurrencyInstance(localeUSFormat).format(valueTotal), i, 6);
}
}
});
gridcons.insets = new Insets(10,0,0,0);
gridcons.gridx = 0;
gridcons.gridy = 3;
gridcons.gridwidth = 1;
gridbag.setConstraints(buttonResetDiscounts, gridcons);
panelButtons.add(buttonResetDiscounts);
// Create button to reset the quantity column to 1
buttonResetQuantities = new JButton("Reset all quantities");
buttonResetQuantities.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
BigDecimal valueSP;
for (int i = 0; i < table.getModel().getRowCount(); i++) {
valueSP = new BigDecimal( table.getModel().
getValueAt(i, 4).toString().replaceAll("[$,]", "") );
table.getModel().setValueAt("1", i, 5);
table.getModel().setValueAt(DecimalFormat.
getCurrencyInstance(localeUSFormat).format(valueSP), i, 6);
}
}
});
gridcons.insets = new Insets(10,0,0,0);
gridcons.gridx = 1;
gridcons.gridy = 3;
gridcons.gridwidth = 1;
gridbag.setConstraints(buttonResetQuantities, gridcons);
panelButtons.add(buttonResetQuantities);
// Create button for closing the window and releasing resources
buttonExit = new JButton("Exit");
buttonExit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
gridcons.insets = new Insets(5,0,0,0);
gridcons.gridx = 0;
gridcons.gridy = 5;
gridcons.gridwidth = 2;
gridbag.setConstraints(buttonExit, gridcons);
panelButtons.add(buttonExit);
} // -> createButtons()
// Filters the JTable based on user input
private void tableFilter() {
RowFilter<DefaultTableModel, Object> tableRowFilter;// = null;
// If current expression doesn't parse, don't update
try {
tableRowFilter = RowFilter.regexFilter("(?i)" + textFilterBox.
getText(), 0, 1, 2);
} catch (java.util.regex.PatternSyntaxException e) {
return;
}
sorter.setRowFilter(tableRowFilter);
} // -> tableFilter
// Method that creates the JTable
public void createTable() {
// Create listener for selecting all text when a text field gains focus
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
#Override
public void propertyChange(final PropertyChangeEvent e) {
if (e.getNewValue() instanceof JTextField) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JTextField textField = (JTextField)e.getNewValue();
textField.selectAll();
}
});
}
}
});
String[] columnNames = {"Model", "Description", "GPL", "Discount", "SP",
"Quantity", "Total"};
Object[][] data = {
{"MR16", "desc1", "$649.00", "0%", "$649.00", new Integer(1), "$649.00"},
{"MR24", "desc2", "$1,199.00", "0%", "$1,199.00", new Integer(1), "1,199.00"},
{"MR62", "desc3", "$699.00", "0%", "$699.00", new Integer(1), "$699.00"},
{"MR66", "desc4", "$1,299.00", "0%", "$1,299.00", new Integer(1), "$1,299.00"},
{"MX80", "desc5", "$1,995.00", "0%", "$1,995.00", new Integer(1), "$1,995.00"},
{"MX90", "desc6", "$3,995.00", "0%", "$3,995.00", new Integer(1), "$3,995.00"},
{"MX400", "desc7", "$15,995.00", "0%", "$15,995.00", new Integer(1), "$15,995.00"},
{"MX600", "desc8", "$31,995.00", "0%", "$31,995.00", new Integer(1), "$31,995.00"},
{"MS22-HW", "desc9", "$1,999.00", "0%", "$1,999.00", new Integer(1), "$1,999.00"},
{"MS42-HW", "desc10", "$3,499.00", "0%", "$3,499.00", new Integer(1), "$3,499.00"},
};
// Create the TableModel and populate it
tableModel = new DefaultTableModel(data, columnNames) {
Class [] classes = {String.class, String.class, String.class,
String.class, String.class, int.class, String.class, Boolean.class};
#Override
public Class getColumnClass(int column) {
return classes[column];
}
};
// Create a JTable and populate it with the content of the TableModel
table = new JTable(tableModel) {
#Override
public boolean isCellEditable(int row, int column) {
if (column == 0 || column == 1 || column == 2 || column == 4 ||
column == 6) {
return false;
}
return true;
}
};
// This sorter is used for text filtering
sorter = new TableRowSorter<>(tableModel);
for (int column = 3; column < 6; column++) {
sorter.setSortable(column, false);
}
table.setRowSorter(sorter);
columnTotal= table.getColumnModel().getColumn(6);
columnTotal.setPreferredWidth(100);
// Filter user input in the quantity text field to only allow digits
discountField =new JTextField();
discountField.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e)
{
if(!Character.isDigit(e.getKeyChar()) && e.getKeyChar() !=KeyEvent.VK_BACK_SPACE) {
discountField.setEditable(false);
discountField.setBackground(Color.WHITE);
} else {
discountField.setEditable(true);
}
}
});
// Set the text field to the cells of the quantity column
columnQuantity = table.getColumnModel().getColumn(5);
columnQuantity.setCellEditor(new DefaultCellEditor (discountField));
// Filter user input in the discount text field to only allow digits
quantityField =new JTextField();
quantityField.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e)
{
if(!Character.isDigit(e.getKeyChar()) && e.getKeyChar() !=KeyEvent.VK_BACK_SPACE) {
quantityField.setEditable(false);
quantityField.setBackground(Color.WHITE);
//JOptionPane.showMessageDialog(null,"Only digit input is allowed!");
} else {
quantityField.setEditable(true);
}
}
});
// Set the text field to the cells of the quantity column
columnDiscount = table.getColumnModel().getColumn(3);
columnDiscount.setCellEditor(new DefaultCellEditor(discountField));
// Create an US number format
localeUSFormat = Locale.US;
decimalFormatUS = (DecimalFormat) DecimalFormat.getInstance(localeUSFormat);
decimalFormatUS.setMaximumFractionDigits(2);
// Create abstract action which listens for changes made in the JTable
Action actionTableListener = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
TableCellListener tcl = (TableCellListener)e.getSource();
// Get the current row and column index of the table
selectedRowIndex = tcl.getRow();
selectedColumnIndex = tcl.getColumn();
TableModel model = tcl.getTable().getModel();
// Have a string variable check for null cell value
checkForNull = model.getValueAt(selectedRowIndex,selectedColumnIndex).toString();
// Change the discounted and total price values
if (selectedColumnIndex == 3) {
// Check if the discount value is null and replace with
// last used value if true
if (checkForNull.equals("")) {
model.setValueAt(backupDiscount + "%",selectedRowIndex, selectedColumnIndex);
return;
}
// Get the discount value and replace the '%' with nothing
valueDiscount = new BigDecimal(( model
.getValueAt(selectedRowIndex,selectedColumnIndex)
.toString().replaceAll("[%]","") ));
//
model.setValueAt(valueDiscount + "%",selectedRowIndex, selectedColumnIndex);
// Check if the discount value is greater than 100
if ( (valueDiscount.compareTo(new BigDecimal(100)) == 1 ) ) {
model.setValueAt(backupDiscount + "%",selectedRowIndex, selectedColumnIndex);
JOptionPane.showMessageDialog(null,"Discount cannot be more than 100%.");
} else {
backupDiscount = valueDiscount;
valueDiscount = valueDiscount.divide(new BigDecimal(100)
, 2, BigDecimal.ROUND_HALF_EVEN);
// Calculate SP and Total values based on the discount input
valueGPL = new BigDecimal( ( model
.getValueAt(selectedRowIndex,selectedColumnIndex - 1)
.toString().replaceAll("[$,]","") ) );
// Get the quantity value
valueQuantity = Integer.parseInt( ( model
.getValueAt(selectedRowIndex,selectedColumnIndex + 2)
.toString() ) );
// Calculate the new discount value
resultDiscount = valueGPL.multiply(valueDiscount,
new MathContext(BigDecimal.ROUND_HALF_EVEN));
// Calculate the new SP value
resultSP = valueGPL.subtract(resultDiscount,
new MathContext(BigDecimal.ROUND_HALF_EVEN));
// Calculate the new result value
resultTotal = resultSP.multiply(new BigDecimal(valueQuantity),
new MathContext(BigDecimal.ROUND_HALF_EVEN));
// Display the new SP value
model.setValueAt(DecimalFormat.getCurrencyInstance(localeUSFormat)
.format(resultSP),selectedRowIndex, selectedColumnIndex + 1);
// Display the new Total value
model.setValueAt(DecimalFormat.getCurrencyInstance(localeUSFormat)
.format(resultTotal),selectedRowIndex, selectedColumnIndex + 3);
}
}
// Change the total price values based on the quantity column
if (selectedColumnIndex == 5) {
// Check if the quantity value is null and replace with
// last used value if true
if (checkForNull.equals("")) {
model.setValueAt(backupQuantity,selectedRowIndex, selectedColumnIndex);
return;
}
// Change total price value based on the quantity column
resultSP = new BigDecimal( ( model.
getValueAt(selectedRowIndex,
selectedColumnIndex - 1).toString().replaceAll("[$,]","") ) );
valueQuantity = Integer.parseInt( ( model.getValueAt(selectedRowIndex,
selectedColumnIndex).toString() ) );
// Check if the value quantity is over a certain limit
if (valueQuantity <= 0 || valueQuantity >= 999999) {
model.setValueAt(backupQuantity,selectedRowIndex, selectedColumnIndex);
JOptionPane.showMessageDialog(null,"Quantity value is too high or invalid!");
} else {
// If the value is under the limit: backup the new quantity
// value, calculate the new total value and display it
backupQuantity = valueQuantity;
resultTotal = resultSP.multiply(new BigDecimal(valueQuantity),
new MathContext(BigDecimal.ROUND_HALF_EVEN));
model.setValueAt(DecimalFormat.getCurrencyInstance(localeUSFormat)
.format(resultTotal), selectedRowIndex, selectedColumnIndex + 1);
}
}
}
}; // -> AbstractAction()
tableCellListener = new TableCellListener(table, actionTableListener);
table.setPreferredScrollableViewportSize(table.
getPreferredSize());
table.setRowHeight(22);
setVisibleRowCount(table,10);
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
table.setFillsViewportHeight(true);
table.getTableHeader().setReorderingAllowed(false);
table.getTableHeader().setResizingAllowed(false);
panelTable.add(new JScrollPane(table));
} // -> createTable()
// Method to display a fixed number of rows in the JTable viewport
public static void setVisibleRowCount(JTable table, int rows){
int height = 0;
for(int row=0; row<rows; row++) {
height += table.getRowHeight(row);
}
table.setPreferredScrollableViewportSize(new Dimension(
table.getPreferredScrollableViewportSize().width, height ));
}
// Create and display the contents of the frame
public static void showGUI() {
// Disable boldface controls
UIManager.put("swing.boldMetal", Boolean.FALSE);
// Create the frame
frameTableCellChange = new JFrame("Table frame");
frameTableCellChange.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frameTableCellChange.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
// Create and set up the content pane.
TableCellChange newContentPane = new TableCellChange();
newContentPane.setOpaque(true); //content panes must be opaque
frameTableCellChange.setContentPane(newContentPane);
// Arrange and display the window.
frameTableCellChange.pack(); //must be called first
frameTableCellChange.setLocationRelativeTo(null); //center window
frameTableCellChange.setResizable(false);
frameTableCellChange.setVisible(true);
} //-> showQueryResultGUI()
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.
UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException | InstantiationException |
IllegalAccessException |
javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(TableCellChange.class.getName()).
log(java.util.logging.Level.SEVERE, null, ex);
}
// Display the frame and it's contents
TableCellChange.showGUI();
}
});
} //-> main(String[] args)
} //-> TableCellChange class
EDIT This class was created by Rob Camick (a.k.a. camickr), all credits go to him for creating this awesome piece of code. Only comments were removed from the code in order to respect the character limit.
TableCellListener class
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
/*
* This class listens for changes made to the data in the table via the
* TableCellEditor. When editing is started, the value of the cell is saved
* When editing is stopped the new value is saved. When the old and new
* values are different, then the provided Action is invoked.
* The source of the Action is a TableCellListener instance.
*/
public class TableCellListener implements PropertyChangeListener, Runnable {
private JTable table;
private Action action;
private int row;
private int column;
private Object oldValue;
private Object newValue;
public TableCellListener(JTable table, Action action) {
this.table = table;
this.action = action;
this.table.addPropertyChangeListener(this);
}
private TableCellListener(JTable table, int row, int column, Object oldValue, Object newValue) {
this.table = table;
this.row = row;
this.column = column;
this.oldValue = oldValue;
this.newValue = newValue;
}
public int getColumn() {
return column;
}
public Object getNewValue() {
return newValue;
}
public Object getOldValue() {
return oldValue;
}
public int getRow() {
return row;
}
public JTable getTable() {
return table;
}
#Override
public void propertyChange(PropertyChangeEvent e) {
if ("tableCellEditor".equals(e.getPropertyName())) {
if (table.isEditing()) {
processEditingStarted();
} else {
processEditingStopped();
}
}
}
private void processEditingStarted() {
SwingUtilities.invokeLater(this);
}
#Override
public void run() {
row = table.convertRowIndexToView(table.getEditingRow());
row = table.getEditingRow();
column = table.convertColumnIndexToModel(table.getEditingColumn());
oldValue = table.getModel().getValueAt(row, column);
newValue = null;
}
private void processEditingStopped() {
newValue = table.getModel().getValueAt(row, column);
if (!newValue.equals(oldValue)) {
TableCellListener tcl = new TableCellListener(
getTable(), getRow(), getColumn(), getOldValue(), getNewValue());
ActionEvent event = new ActionEvent(
tcl,
ActionEvent.ACTION_PERFORMED,
"");
action.actionPerformed(event);
}
}
}
I understand that when filtering a table the indexes of the table view change and must be synchronized with the indexes of the underlying model. How can that be done in order for the filtered table to work?
You implemented the Action for the TableCellListener incorrectly. You can't use the selected row/colum because those values are in the Table view. The TableCellListener works on the model.
Check out the example Action provided with Table Cell Editor. To get the row/column that was changed you must reference the TableCellListener itself.
Edit:
Here is my simple text example. When you change the "Price", the "Price Change" and "Value" columns are automatically updated.
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableCellListenerTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI()
{
String[] columnNames = {"Stock", "Shares", "Price", "Price Change", "Value"};
Object[][] data =
{
{"IBM", new Integer(100), new Double(85), new Double(0), new Double(8500)},
{"Apple", new Integer(300), new Double(30), new Double(0), new Double(9000)},
{"Sun", new Integer(1500), new Double(5), new Double(0), new Double(7500)},
{"Google", new Integer(100), new Double(100), new Double(0), new Double(10000)}
};
DefaultTableModel model = new DefaultTableModel(data, columnNames)
{
public Class getColumnClass(int column)
{
return getValueAt(0, column).getClass();
}
public boolean isCellEditable(int row, int column)
{
return column == 2;
}
};
JTable table = new JTable(model);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollPane = new JScrollPane(table);
// Add a sorter
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<DefaultTableModel>(model);
table.setRowSorter(sorter);
// Filter
try
{
RowFilter<DefaultTableModel, Object> rf = RowFilter.regexFilter("l", 0);
sorter.setRowFilter(rf);
}
catch (java.util.regex.PatternSyntaxException e) {}
Action action = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
TableCellListener tcl = (TableCellListener)e.getSource();
int column = tcl.getColumn();
if (column == 2)
{
int row = tcl.getRow();
double oldPrice = ((Double)tcl.getOldValue()).doubleValue();
double newPrice = ((Double)tcl.getNewValue()).doubleValue();
TableModel model = tcl.getTable().getModel();
double priceChange = new Double(newPrice - oldPrice);
model.setValueAt(priceChange, row, 3);
double shares = ((Integer)model.getValueAt(row, 1)).doubleValue();
Double value = new Double(shares * newPrice);
model.setValueAt(value, row, 4);
}
}
};
TableCellListener tcl = new TableCellListener(table, action);
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Table Cell Listener");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.add( scrollPane );
frame.setSize(400, 160);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
You may need to do the conversion from view to model. Take a look at Sorting and Filtering part of How to Use Tables tutorial:
When a table uses a sorter, the data the users sees may be in a
different order than that specified by the data model, and may not
include all rows specified by the data model. The data the user
actually sees is known as the view, and has its own set of
coordinates. JTable provides methods that convert from model
coordinates to view coordinates — convertColumnIndexToView and
convertRowIndexToView — and that convert from view coordinates to
model coordinates — convertColumnIndexToModel and
convertRowIndexToModel.
EDIT: convert from view (table) to model:
Replace these rows:
row = table.convertRowIndexToView(table.getEditingRow());
row = table.getEditingRow();
With:
row = table.convertRowIndexToModel(table.getEditingRow());
Related
How do I add an ActionListener to a JCheckBox?
Problem So my program takes in a .csv file and loads the data and displays it. When data is loaded in, it creates a new JCheckBox for every column header there is in the data. How do I add an ActionListener such that when the user ticks/unticks any of the boxes, it should do a certain function? When data is loaded in, it updates the JPanel by the code: public void updateChecklistPanel(){ checklistPanel.removeAll(); checklistPanel.setLayout(new GridLayout(currentData.getColumnNames().length, 1, 10, 0)); for (String columnName : currentData.getColumnNames()){ JCheckBox checkBox = new JCheckBox(); checkBox.setText(columnName); checklistPanel.add(checkBox); } checklistPanel.revalidate(); checklistPanel.repaint(); } I also have the following at the bottom: #Override public void actionPerformed(ActionEvent e) { if (e.getSource() == newDataFrameItem){ newFile(); System.out.println("New DataFrame Loaded in"); } if (e.getSource() == loadDataFrameItem){ loadFile(); System.out.println(".csv Data loaded into DataFrame."); } if (e.getSource() == saveDataFrameItem){ System.out.println("Saved the data to a .csv file"); } } What I'm trying to do is that when a checkbox is unticked, it should hide a column in the JTable and when ticked, it should redisplay the column. Current Solution The solution that I have come up with is to make a variable allColumnHeaders that is an ArrayList of Strings. I then also have a variable shownColumnHeaders that is an ArrayList of Booleans. When the user wants to show/hide a column, the showColumn(String columnName) and hideColumn(String columnName) function finds the index of the column Name in allColumnHeaders and sets the Boolean value of the index in shownColumnHeaders to either true/false. It the proceeds to create a new table model where the columns are only added if the Boolean value for that column is true. It will then set the model for the table to the new table model. The code for the following is show below: import java.awt.*; import java.util.ArrayList; import javax.swing.*; import javax.swing.table.DefaultTableModel; public class MRE extends JPanel { private static JTable table; private static ArrayList<String> allColumnHeaders = new ArrayList<>(); private static ArrayList<Boolean> shownColumnHeaders = new ArrayList<>(); private static void createAndShowGUI() { table = new JTable(5, 7); table.setPreferredScrollableViewportSize(table.getPreferredSize()); JScrollPane scrollPane = new JScrollPane( table ); JPanel buttons = new JPanel( new GridLayout(0, 1) ); for (int i = 0; i < table.getColumnCount(); i++) { String column = table.getColumnName(i); allColumnHeaders.add(column); JCheckBox checkBox = new JCheckBox(column); checkBox.addActionListener(event -> { JCheckBox cb = (JCheckBox) event.getSource(); if (cb.isSelected()) { System.out.println(checkBox.getText() + " is now being displayed"); showColumn(checkBox.getText()); } else { System.out.println(checkBox.getText() + " is now being hidden"); hideColumn(checkBox.getText()); } table.setModel(createTableModel()); }); checkBox.setSelected( true ); buttons.add( checkBox ); shownColumnHeaders.add(true); } JPanel wrapper = new JPanel(); wrapper.add( buttons ); JFrame frame = new JFrame("MRE"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(scrollPane, BorderLayout.CENTER); frame.add(wrapper, BorderLayout.LINE_END); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setLocationByPlatform( true ); frame.setVisible( true ); } public static DefaultTableModel createTableModel(){ DefaultTableModel tableModel = new DefaultTableModel(0, 0); String[] columnValues = new String[1]; for (int i = 0; i < shownColumnHeaders.size(); i++){ if (shownColumnHeaders.get(i)){ tableModel.addColumn(allColumnHeaders.get(i), columnValues); } } return tableModel; } public static void showColumn(String columnName){ for (int i = 0; i < allColumnHeaders.size(); i++) { if (allColumnHeaders.get(i).equals(columnName)){ shownColumnHeaders.set(i, true); } } } public static void hideColumn(String columnName){ for (int i = 0; i < allColumnHeaders.size(); i++) { if (allColumnHeaders.get(i).equals(columnName)){ shownColumnHeaders.set(i, false); } } } public static void main(String[] args) throws Exception { SwingUtilities.invokeLater( () -> createAndShowGUI() ); } }
Introduction It took me a while, but I came up with the following JTable GUI. Here's the starting display. Here's the GUI after I remove the item description. Here's the GUI after I remove the item price. Here's the GUI after I add the columns back. Explanation I created an Item class to hold one item. I created an Inventory class to hold a List of Item instances and a String array of the column headers. I created the JFrame and two JPanels. One JPanel holds the JTable and the other holds the JCheckBoxes. I used Swing layout managers to create the JPanels. So far, so basic. Creating the JTable took a bit of effort. I wanted to display the item price as currency, but that wasn't important for this example GUI. I created an ActionListener to add and remove columns from the JTable. I had to experiment a bit. The TableColumnModel addColumn method appends the column to the table. I had to create a DisplayTableColumn class to hold a TableColumn and a boolean that tells me whether or not to display the TableColumn. I wound up removing all the columns from the JTable and adding all the columns back to the JTable so that I could maintain the column sequence. I probably ran 100 tests before I could get this code to work. Code Here's the complete runnable code. import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; public class JCheckBoxTableGUI implements Runnable { public static void main(String[] args) { SwingUtilities.invokeLater(new JCheckBoxTableGUI()); } private final Inventory inventory; private final InventoryTableModel tableModel; private JFrame frame; private JTable table; public JCheckBoxTableGUI() { this.tableModel = new InventoryTableModel(); this.inventory = new Inventory(); String[] columns = inventory.getTableHeader(); for (String column : columns) { tableModel.addColumn(column); } List<Item> items = inventory.getInventory(); for (Item item : items) { Object[] object = new Object[5]; object[0] = item.getItemNumber(); object[1] = item.getItemName(); object[2] = item.getItemDescription(); object[3] = item.getItemQuantity(); object[4] = item.getItemPrice(); tableModel.addRow(object); } } #Override public void run() { frame = new JFrame("JCheckBox Table GUI"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(createTablePanel(), BorderLayout.CENTER); frame.add(createSelectionPanel(), BorderLayout.AFTER_LINE_ENDS); frame.pack(); frame.setLocationByPlatform(true); frame.setVisible(true); } private JPanel createTablePanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); table = new JTable(tableModel); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); table.getColumnModel().getColumn(0).setPreferredWidth(100); table.getColumnModel().getColumn(1).setPreferredWidth(150); table.getColumnModel().getColumn(2).setPreferredWidth(150); table.getColumnModel().getColumn(3).setPreferredWidth(100); table.getColumnModel().getColumn(4).setPreferredWidth(100); JScrollPane scrollPane = new JScrollPane(table); scrollPane.setPreferredSize(new Dimension(620, 300)); panel.add(scrollPane, BorderLayout.CENTER); return panel; } private JPanel createSelectionPanel() { JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0)); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); JPanel innerPanel = new JPanel(new GridLayout(0, 1, 5, 5)); ColumnListener listener = new ColumnListener(this); String[] columns = inventory.getTableHeader(); for (String column : columns) { JCheckBox checkBox = new JCheckBox("Display " + column); checkBox.addActionListener(listener); checkBox.setActionCommand(column); checkBox.setSelected(true); innerPanel.add(checkBox); } panel.add(innerPanel); return panel; } public JTable getTable() { return table; } public JFrame getFrame() { return frame; } public class ColumnListener implements ActionListener { private final JCheckBoxTableGUI frame; private final List<DisplayTableColumn> displayColumns; public ColumnListener(JCheckBoxTableGUI frame) { this.frame = frame; this.displayColumns = new ArrayList<>(); TableColumnModel tcm = frame.getTable().getColumnModel(); for (int index = 0; index < tcm.getColumnCount(); index++) { TableColumn tc = tcm.getColumn(index); displayColumns.add(new DisplayTableColumn(tc, true)); } } #Override public void actionPerformed(ActionEvent event) { JCheckBox checkBox = (JCheckBox) event.getSource(); String column = event.getActionCommand(); TableColumnModel tcm = frame.getTable().getColumnModel(); for (int index = 0; index < displayColumns.size(); index++) { DisplayTableColumn dtc = displayColumns.get(index); if (dtc.isShowTableColumn()) { tcm.removeColumn(dtc.getTableColumn()); } } int columnIndex = getColumnIndex(column); displayColumns.get(columnIndex).setShowTableColumn( checkBox.isSelected()); for (int index = 0; index < displayColumns.size(); index++) { DisplayTableColumn dtc = displayColumns.get(index); if (dtc.isShowTableColumn()) { tcm.addColumn(dtc.getTableColumn()); } } frame.getFrame().pack(); } private int getColumnIndex(String column) { for (int index = 0; index < displayColumns.size(); index++) { DisplayTableColumn dtc = displayColumns.get(index); if (column.equals(dtc.getTableColumn().getHeaderValue())) { return index; } } return -1; } } public class DisplayTableColumn { private boolean showTableColumn; private final TableColumn tableColumn; public DisplayTableColumn(TableColumn tableColumn, boolean showTableColumn) { this.tableColumn = tableColumn; this.showTableColumn = showTableColumn; } public boolean isShowTableColumn() { return showTableColumn; } public void setShowTableColumn(boolean showTableColumn) { this.showTableColumn = showTableColumn; } public TableColumn getTableColumn() { return tableColumn; } } public class InventoryTableModel extends DefaultTableModel { private static final long serialVersionUID = 1L; #Override public Class<?> getColumnClass(int columnIndex) { if (columnIndex <= 2) { return String.class; } else if (columnIndex == 3) { return Integer.class; } else { return Integer.class; } } } public class Inventory { private final List<Item> inventory; private final String[] tableHeader; public Inventory() { this.tableHeader = new String[] { "Item Number", "Item Name", "Item Description", "Item Quantity", "Item Price" }; this.inventory = new ArrayList<>(); inventory.add(new Item("X101111", "Samsung Camera", " ", 20, 69.99)); inventory.add(new Item("X101112", "Samsung Monitor", " ", 10, 279.99)); inventory.add(new Item("X101113", "Samsung Smartphone", " ", 110, 599.99)); inventory.add(new Item("X101114", "Apple Watch", " ", 20, 1259.99)); inventory.add(new Item("X101115", "Sony Playstation 5", " ", 0, 399.99)); } public String[] getTableHeader() { return tableHeader; } public List<Item> getInventory() { return inventory; } } public class Item { private int itemPrice; private int itemQuantity; private final String itemNumber; private final String itemName; private final String itemDescription; public Item(String itemNumber, String itemName, String itemDescription, int itemQuantity, double itemPrice) { this.itemNumber = itemNumber; this.itemName = itemName; this.itemDescription = itemDescription; this.itemQuantity = itemQuantity; setItemPrice(itemPrice); } public int getItemPrice() { return itemPrice; } public void setItemPrice(double itemPrice) { this.itemPrice = (int) Math.round(itemPrice * 100.0); } public int getItemQuantity() { return itemQuantity; } public void setItemQuantity(int itemQuantity) { this.itemQuantity = itemQuantity; } public String getItemNumber() { return itemNumber; } public String getItemName() { return itemName; } public String getItemDescription() { return itemDescription; } } }
This only demonstrates how to create a basic MRE: the csv file is irrelevant. data in the model is irrelevant. your loadFile, newFile and saveFile buttons are irrelevant. the TableModel is irrelevant Your question is about adding an ActionListener to dynamically created checkboxes based on the columns of the table. So all you need is a table with some columns and the resulting checkboxes. import java.awt.*; import javax.swing.*; public class MRE extends JPanel { private static void createAndShowGUI() { JTable table = new JTable(5, 7); table.setPreferredScrollableViewportSize(table.getPreferredSize()); JScrollPane scrollPane = new JScrollPane( table ); JPanel buttons = new JPanel( new GridLayout(0, 1) ); for (int i = 0; i < table.getColumnCount(); i++) { String column = table.getColumnName(i); JCheckBox checkBox = new JCheckBox("Display " + column); checkBox.setSelected( true ); buttons.add( checkBox ); } JPanel wrapper = new JPanel(); wrapper.add( buttons ); JFrame frame = new JFrame("MRE"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(scrollPane, BorderLayout.CENTER); frame.add(wrapper, BorderLayout.LINE_END); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setLocationByPlatform( true ); frame.setVisible( true ); } public static void main(String[] args) throws Exception { SwingUtilities.invokeLater( () -> createAndShowGUI() ); } } If you can modify this to demonstrate how to use your "reusable class" to manage column visibility, then I will update the above code to use my reusable class with 4 additional lines of code. Edit: Suggested improvements to your code: Make the code reusable and self contained Don't use static methods Don't change the data. Your original question was: How do I add an ActionListener to a JCheckBox? So to do that you need a class that: implements an ActionListener is self contained to implement the functionality you require. So the basic structure could be like the following: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SomeFunction implements ActionListener { private JTable table; public SomeFunction(JTable table) { this.table = table; } #Override public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof AbstractButton) { String command = e.getActionCommand(); AbstractButton button = (AbstractButton)e.getSource(); if (button.isSelected()) doSelected( command ); else doUnselected( command ); } } private void doSelected(String command) { System.out.println(command + " selected"); } private void doUnselected(String command) { System.out.println(command + " unselected"); } private static void createAndShowGUI() { JTable table = new JTable(5, 7); table.setPreferredScrollableViewportSize(table.getPreferredSize()); JScrollPane scrollPane = new JScrollPane( table ); JPanel buttons = new JPanel( new GridLayout(0, 1) ); SomeFunction listener = new SomeFunction( table ); // added // TableColumnManager listener = new TableColumnManager(table, false); // ColumnListener listener = new ColumnListener(); for (int i = 0; i < table.getColumnCount(); i++) { String column = table.getColumnName(i); JCheckBox checkBox = new JCheckBox("Display " + column); checkBox.setSelected( true ); checkBox.setActionCommand( column ); // added checkBox.addActionListener( listener ); // added buttons.add( checkBox ); } JPanel wrapper = new JPanel(); wrapper.add( buttons ); JFrame frame = new JFrame("MRE"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(scrollPane, BorderLayout.CENTER); frame.add(wrapper, BorderLayout.LINE_END); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setLocationByPlatform( true ); frame.setVisible( true ); } public static void main(String[] args) throws Exception { SwingUtilities.invokeLater( () -> createAndShowGUI() ); } } Whatever your function does it only needs to know about the table it should act upon. So try to restructure your code into a reusable class. If you want to use Gilberts code, then you need to restructure the ColumnListener class into a separate reusable self contained class. Finally you can also try my reusable class. Check out Table Column Manager for the class to download. Whatever reusable class you decide to use, you will only need to change a single line of code from the above example (once your functionality is contained in a reusable class). Note the static methods would not be part of the final class. They are only there so simplify testing and posting of an MRE in a single class file. Hope this helps with future design considerations.
Swing - JTable cell highlight with different Colors
I have a textbox with search button, if i click on search text, String gets highlighted in JTable cells, if the search text is having multiple Strings with comma separated, all those comma separated Strings has to be displayed in a different colors dynamically. Can you please suggest what changes i need to do to the below working code(single String highlight) import javax.swing.*; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.text.DefaultHighlighter; import java.awt.*; public class TableExample { JFrame f; private JTextField textField; TableExample() { f = new JFrame(); String data[][] = {{"101", "lala", "670000"}, {"101", "lala", "670000"}, {"102", "Jai", "780000"}, {"101", "Sachin", "700000"}}; String column[] = {"ID", "NAME", "SALARY"}; JTable jt = new JTable(); jt.setBounds(30, 40, 200, 300); JScrollPane sp = new JScrollPane(jt); DefaultTableModel dtm = new DefaultTableModel(data, column); dtm.setColumnIdentifiers(column); jt.setModel(dtm); JPanel inputpanel = new JPanel(); inputpanel.setLayout(new BoxLayout(inputpanel, BoxLayout.LINE_AXIS)); textField = new JTextField(); JButton button = new JButton("Highlight"); button.addActionListener(e -> { jt.repaint(); }); inputpanel.add(textField); inputpanel.add(button); jt.setDefaultRenderer(Object.class, new CellHighlightRenderer()); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, inputpanel, sp); f.add(splitPane); f.setSize(300, 400); } class CellHighlightRenderer extends JTextField implements TableCellRenderer { public DefaultHighlighter high = new DefaultHighlighter(); public DefaultHighlighter.DefaultHighlightPainter highlight_painter = new DefaultHighlighter.DefaultHighlightPainter( Color.YELLOW); public CellHighlightRenderer() { setBorder(BorderFactory.createEmptyBorder()); setHighlighter(high); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); setFont(table.getFont()); setValue(value); int pos = 0; String pattern = textField.getText(); // Grab the text. if (pattern != null && pattern.trim().length() > 0) while ((pos = value.toString().indexOf(pattern, pos)) >= 0) { try { high.addHighlight(pos, pos + pattern.length(), highlight_painter); pos += pattern.length(); } catch (Exception e) { e.printStackTrace(); } } return this; } protected void setValue(Object value) { setText((value == null) ? "" : value.toString()); } } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ TableExample te = new TableExample(); te.f.setVisible(true); }); } }
I have done what i think you asked. Read commends inside the code. Whenever u see a comment, try to compare it with your code. I didnt know what colors you want so i have done with random color to each word. Preview: package test; import java.awt.Color; import java.awt.Component; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.text.DefaultHighlighter; public class TableExample { JFrame f; private JTextField textField; // make the textfield a field so u can grab its text whenever u need it (in our // case in renderer) private HashMap<String, Color> words = new HashMap<>(); TableExample() { f = new JFrame(); // I changed the data a bit for my tests, take care of that. String data[][] = { { "101", "lala", "670000" }, { "101", "lala", "670000" }, { "102", "Jai", "780000" }, { "101", "Sachin", "700000" } }; String column[] = { "ID", "NAME", "SALARY" }; JTable jt = new JTable(); jt.setBounds(30, 40, 200, 300); JScrollPane sp = new JScrollPane(jt); // You were not adding the sp into your panel, take care that next time. DefaultTableModel dtm = new DefaultTableModel(data, column); // Create the model with our data. dtm.setColumnIdentifiers(column); jt.setModel(dtm); JPanel inputpanel = new JPanel(); inputpanel.setLayout(new BoxLayout(inputpanel, BoxLayout.LINE_AXIS)); textField = new JTextField(); JButton button = new JButton("Highlight"); button.addActionListener(e -> { // Button should only repaint the table (a.k.a read the renderer again). words.clear(); //Clear the map with the words String text = textField.getText(); // Grab the text. if (text.trim().length() > 0) { String[] splitPattern = text.split(","); //Split the words for (String s : splitPattern) { words.put(s, getARandomColor()); //Put each word with a RANDOM color. } } jt.repaint();// Changing the table's renderer is bad on every button click, and no needed. }); inputpanel.add(textField); inputpanel.add(button); jt.setDefaultRenderer(Object.class, new CellHighlightRenderer()); // Add the renderer only 1 time. // I add sp here, not the table (if you want your table to actually have a // scrollpane) JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, inputpanel, sp); f.add(splitPane); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Hopefully you didnt forget this. f.setSize(300, 400); f.pack(); // Use pack() to the frame between set it visible. // f.setVisible(true); //setVisible to constructor is bad idea. I suggest you to // avoid that, and show your frame after its initiation. } class CellHighlightRenderer extends JTextField implements TableCellRenderer { public DefaultHighlighter high = new DefaultHighlighter(); public DefaultHighlighter.DefaultHighlightPainter highlight_painter = new DefaultHighlighter.DefaultHighlightPainter( Color.YELLOW); public CellHighlightRenderer() { setBorder(BorderFactory.createEmptyBorder()); setHighlighter(high); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); setFont(table.getFont()); setValue(value); int pos = 0; if (words.size() > 0) { for (String word : words.keySet()) { while ((pos = value.toString().indexOf(word, pos)) >= 0) { try { highlight_painter = new DefaultHighlighter.DefaultHighlightPainter(words.get(word)); // high.addHighlight(first, last, highlight_painter); high.addHighlight(pos, pos + word.length(), highlight_painter); pos += word.length(); } catch (Exception e) { e.printStackTrace(); } } } } return this; } protected void setValue(Object value) { setText((value == null) ? "" : value.toString()); } } private Color getARandomColor() { return new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)); } public static void main(String[] args) { /* *******THIS IS VERY IMPORTANT******* */ /* All swing applications should run in their own thread, named EDT */ SwingUtilities.invokeLater(() -> { TableExample te = new TableExample(); te.f.setVisible(true); // Here, make it visisble }); } }
jTable right-click popup menu
I have a SQL database and I am working on a program that will allow me to add/delete/modify records. I already managed to add records I am working on editing/deleting them. I want to display the existing records in a table so I am using jTable. I found some code online and modified it to pull the records and display them in a jtable but i dont know how to code the rightclick and display a popup menu. In that popup menu I want to display options such as delete record and modify record. This is the code I am using the make the jTable and display the data: private void menuDeleteAuthorActionPerformed(java.awt.event.ActionEvent evt) { TableFromDatabase deleteAuthor = new TableFromDatabase(); deleteAuthor.pack(); deleteAuthor.setVisible(true); Vector columnNames = new Vector(); Vector data = new Vector(); try { Connection connection = DriverManager.getConnection( url, user, password ); // Read data from a table String sql = "SELECT * FROM Authors"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery( sql ); ResultSetMetaData md = rs.getMetaData(); int columns = md.getColumnCount(); // Get column names for (int i = 1; i <= columns; i++) { columnNames.addElement( md.getColumnName(i) ); } // Get row data while (rs.next()) { Vector row = new Vector(columns); for (int i = 1; i <= columns; i++) { row.addElement( rs.getObject(i) ); } data.addElement( row ); } rs.close(); stmt.close(); connection.close(); } catch(Exception e) { System.out.println( e ); } // Create table with database data JTable table = new JTable(data, columnNames) { public Class getColumnClass(int column) { for (int row = 0; row < getRowCount(); row++) { Object o = getValueAt(row, column); if (o != null) { return o.getClass(); } } return Object.class; } }; JScrollPane scrollPane = new JScrollPane( table ); getContentPane().add( scrollPane ); JPanel buttonPanel = new JPanel(); getContentPane().add( buttonPanel, BorderLayout.SOUTH ); } I am new to Java so please be kind in your responses. Thank you all in advance for any assistance!
Here is an example on how to do that. The easiest way to achieve this, is to set a JPopupMenu on the JTable directly. import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Vector; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; public class TestTableRightClick { protected void initUI() { final JFrame frame = new JFrame(TestTableRightClick.class.getSimpleName()); Vector<String> columns = new Vector<String>(Arrays.asList("Name", "Age")); Vector<Vector<String>> data = new Vector<Vector<String>>(); for (int i = 0; i < 50; i++) { Vector<String> row = new Vector<String>(); for (int j = 0; j < columns.size(); j++) { row.add("Cell " + (i + 1) + "," + (j + 1)); } data.add(row); } final JTable table = new JTable(data, columns); final JPopupMenu popupMenu = new JPopupMenu(); JMenuItem deleteItem = new JMenuItem("Delete"); deleteItem.addActionListener(new ActionListener() { #Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(frame, "Right-click performed on table and choose DELETE"); } }); popupMenu.add(deleteItem); table.setComponentPopupMenu(popupMenu); frame.add(new JScrollPane(table), BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { #Override public void run() { new TestTableRightClick().initUI(); } }); } } In case you want to automatically select the row where the right-click was made, add the following snippet: popupMenu.addPopupMenuListener(new PopupMenuListener() { #Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { SwingUtilities.invokeLater(new Runnable() { #Override public void run() { int rowAtPoint = table.rowAtPoint(SwingUtilities.convertPoint(popupMenu, new Point(0, 0), table)); if (rowAtPoint > -1) { table.setRowSelectionInterval(rowAtPoint, rowAtPoint); } } }); } #Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { // TODO Auto-generated method stub } #Override public void popupMenuCanceled(PopupMenuEvent e) { // TODO Auto-generated method stub } });
A problem with a JTable is that the right click does not change the row selection. So you if have an Action that works on a specific row you need to left click the row first before right clicking to have the popup menu displayed. If you want the row to be selected where your right click then you can use code like the following: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class TableRightClick extends JFrame implements ActionListener { JPopupMenu popup; public TableRightClick() { popup = new JPopupMenu(); popup.add( new JMenuItem("Do Something1") ); popup.add( new JMenuItem("Do Something2") ); popup.add( new JMenuItem("Do Something3") ); JMenuItem menuItem = new JMenuItem("ActionPerformed"); menuItem.addActionListener( this ); popup.add( menuItem ); JTable table = new JTable(50, 5); table.addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent e) { System.out.println("pressed"); } public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { JTable source = (JTable)e.getSource(); int row = source.rowAtPoint( e.getPoint() ); int column = source.columnAtPoint( e.getPoint() ); if (! source.isRowSelected(row)) source.changeSelection(row, column, false, false); popup.show(e.getComponent(), e.getX(), e.getY()); } } }); table.setPreferredScrollableViewportSize(table.getPreferredSize()); getContentPane().add( new JScrollPane(table) ); } public void actionPerformed(ActionEvent e) { Component c = (Component)e.getSource(); JPopupMenu popup = (JPopupMenu)c.getParent(); JTable table = (JTable)popup.getInvoker(); System.out.println(table.getSelectedRow() + " : " + table.getSelectedColumn()); } public static void main(String[] args) { TableRightClick frame = new TableRightClick(); frame.setDefaultCloseOperation( EXIT_ON_CLOSE ); frame.pack(); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } }
Unfortunately, none of these solutions worked for me on both Macs and PC's. Not sure why. But this worked on both: #Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { int row = table.rowAtPoint(SwingUtilities.convertPoint(popup, 0, 0, table)); if (row > -1) table.setRowSelectionInterval(row, row); } The main problem with my solution is that it doesn't show that the row is selected until after the user chooses one of the menu options.
Another issue is that context menus are dynamic, your solution does not deal with changing the menu depending on the clicked row popupMenu.addPopupMenuListener(new PopupMenuListener() { #Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { int rowAtPoint = table.rowAtPoint(SwingUtilities.convertPoint(popupMenu, new Point(0, 0), table)); generateTablePopupMenu(rowAtPoint); // here SwingUtilities.invokeLater(new Runnable() ...
row selection not moving with row moves in JTable
I have following code: import java.awt.BorderLayout; import java.awt.Dimension; import java.util.Vector; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.nimbus.NimbusLookAndFeel; import javax.swing.table.DefaultTableModel; public class NewClass1 extends JFrame { private JTable table; private JScrollPane scrollPane; private DefaultTableModel defaultTableModel; public NewClass1() { setLocationByPlatform(true); setLayout(new BorderLayout()); setPreferredSize(new Dimension(600, 400)); setTitle("Table Issues"); setDefaultCloseOperation(EXIT_ON_CLOSE); createTableModel(); table = new JTable(defaultTableModel); scrollPane = new JScrollPane(table); getContentPane().add(scrollPane, BorderLayout.CENTER); pack(); } private void createTableModel() { Vector cols = new Vector(); cols.add("A"); Vector rows = new Vector(); for (int i = 0; i < 50; i++) { Vector row = new Vector(); row.add((i + 1) + ""); rows.add(row); } defaultTableModel = new DefaultTableModel(rows, cols) { Class[] types = new Class[]{ String.class }; #Override public Class getColumnClass(int columnIndex) { return types[columnIndex]; } #Override public boolean isCellEditable(int row, int column) { return false; } }; } public static void main(String[] args) { try { UIManager.setLookAndFeel(new NimbusLookAndFeel()); } catch (Exception e) { } final NewClass1 nc = new NewClass1(); SwingUtilities.invokeLater(new Runnable() { public void run() { nc.setVisible(true); } }); while (true) { SwingUtilities.invokeLater(new Runnable() { public void run() { int row = (int) (Math.random() * 50); int move = (int) (Math.random() * 50); nc.defaultTableModel.moveRow(row, row, move); } }); try{ Thread.sleep(1000); }catch(Exception e){ } } } } Please run the above code and select row. My problem is with row movement, row selection is not moving. It is staying at fixed position. Suppose I selected row with column value 25, selected row must be of column value 25 after row movements. Please help me on this. My real problem is, user will select row and clicks menu to perform action, meanwhile other threads may have moved rows, and performed action will be on other row than actual one.
The easiest way is to remember the selected row somewhere outside of the ListSelectionModel and adjust the selection whenever the TableModel changes. For example you could do this: public class NewClass1 extends JFrame { private JTable table; private DefaultTableModel defaultTableModel; private JScrollPane scrollPane; private class SelectionHelper implements ListSelectionListener, TableModelListener { private Object selectedRow; #Override public void valueChanged(ListSelectionEvent event) { if (!event.getValueIsAdjusting()) return; int selectedIndex = table.getSelectedRow(); if (selectedIndex >= 0) { selectedRow = defaultTableModel.getDataVector().get(selectedIndex); } else { selectedRow = null; } } #Override public void tableChanged(TableModelEvent event) { if (selectedRow == null) return; int selectedIndex = defaultTableModel.getDataVector().indexOf(selectedRow); table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex); } } public NewClass1() { // ... createTableModel(); table = new JTable(defaultTableModel); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); SelectionHelper helper = new SelectionHelper(); table.getModel().addTableModelListener(helper); table.getSelectionModel().addListSelectionListener(helper); // ... } // ... } Note however, that you should adjust this code for production use, for example in regards to thread safety or portability (using the table and defaultTableModel attributes in the inner class is bad style).
Type of listener for updates to given cells/columns in JTable and incrementing focus
I am trying to use a JTable with the first column predefined. The user enters data into the 2nd column only (Quantity). Then I calculate the final income by multiplying the Service and Quantity columns and display it in the third column, Income. |Service | Quantity | Income |$40.00 | X | |$40.00 | 3 | 120 Here user inputs "3" because she did "3" of service X today at $40 each. The user can only update the Quantity column. The Income column will be calculated by the system. What type of listener should I use? I was using a TableModelListener but when I want to update Income to 120 by calling setValue = $120 it fires off a TableListenerEvent and hence an infinite loop. Should I use an ActionEvent, a ColumnListener or something else? Also, I want the "focus" to increment down the rows, always staying on the second column (the column the user edits).
for Listning changes into TableCell you have to implements TableModelListener for example import java.awt.event.KeyEvent; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; public class TableProcessing extends JFrame { private static final long serialVersionUID = 1L; private JTable table; private String[] columnNames = {"Item", "Quantity", "Price", "Cost"}; private Object[][] data = { {"Bread", new Integer(1), new Double(1.11), new Double(1.11)}, {"Milk", new Integer(1), new Double(2.22), new Double(2.22)}, {"Tea", new Integer(1), new Double(3.33), new Double(3.33)}, {"Cofee", new Integer(1), new Double(4.44), new Double(4.44)}}; private TableModelListener tableModelListener; public TableProcessing() { DefaultTableModel model = new DefaultTableModel(data, columnNames); table = new JTable(model) { private static final long serialVersionUID = 1L; #Override// Returning the Class of each column will allow different renderers public Class getColumnClass(int column) { // to be used based on Class return getValueAt(0, column).getClass(); } #Override // The Cost is not editable public boolean isCellEditable(int row, int column) { int modelColumn = convertColumnIndexToModel(column); return (modelColumn == 3) ? false : true; } }; table.setPreferredScrollableViewportSize(table.getPreferredSize()); //http://stackoverflow.com/questions/7188179/jtable-focus-query/7193023#7193023 table.setCellSelectionEnabled(true); KeyStroke tab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); InputMap map = table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); map.put(tab, "selectNextRowCell"); //http://stackoverflow.com/questions/7188179/jtable-focus-query/7193023#7193023 JScrollPane scrollPane = new JScrollPane(table); getContentPane().add(scrollPane); setTableModelListener(); } private void setTableModelListener() { tableModelListener = new TableModelListener() { #Override public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.UPDATE) { System.out.println("Cell " + e.getFirstRow() + ", " + e.getColumn() + " changed. The new value: " + table.getModel().getValueAt(e.getFirstRow(), e.getColumn())); int row = e.getFirstRow(); int column = e.getColumn(); if (column == 1 || column == 2) { TableModel model = table.getModel(); int quantity = ((Integer) model.getValueAt(row, 1)).intValue(); double price = ((Double) model.getValueAt(row, 2)).doubleValue(); Double value = new Double(quantity * price); model.setValueAt(value, row, 3); } } } }; table.getModel().addTableModelListener(tableModelListener); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { #Override public void run() { TableProcessing frame = new TableProcessing(); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } }