JDateChooser inside JTable cell enter key does not always work - java

I'm using slightly modified JDateChooserCellEditor class which allows me to put jDateChooser inside my jTable cell. Here is the code of class:
public class JDateChooserCellEditor extends AbstractCellEditor implements
TableCellEditor {
private JDateChooser dateChooser = new JDateChooser();
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
Date date = null;
if (value instanceof Date) {
date = (Date) value;
}
dateChooser.setDateFormatString("dd-MM-yyyy");
dateChooser.setDate(date);
return dateChooser;
}
public Object getCellEditorValue() {
dateChooser.setDateFormatString("dd-MM-yyyy");
return dateChooser.getDate();
}
One thing does not work and I cannot find a solution. When I click for the first time on a cell that has jDateChooser inside, select date and hit enter key - nothing happens. The component maintains its focus but never confirms data. But if I after that select different cell the enter key magically works and date is saved to my jTable. After another try it does not work.. Next try - it works. It is so confusing. Thank you all for any help.

Your TableCellEditor is incorrect. Assuming that your Table model stores instances of Date, invoke setDefaultEditor() as shown with DemoTableModel, which is found in the class com.toedter.calendar.demo.DemoTable and illustrated here.
table.setDefaultEditor(Date.class, new JDateChooserCellEditor());
Addendum: Don't set the date format in the cell editor; specify it in the JDateChooser constructor or using setDateFormatString().

Well, I have found solution for my problem. I think its not the best way but it works and I have lost too much time trying to fix this. Adding listener to jDateChooser component and notifying to stop editing on property change as user kleopatra stated seems to solve the problem.
dateChooser.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("date")) {
stopCellEditing();
}
}
});
Thank you all for help.

A variation of your answer worked for me
private AbstractTableModel model;
.....
... in method getTableCellEditorComponent(JTable table, Object value, boolean isSelected, final int row, final int column) .....
try{
model = (AbstractTableModel) table.getModel();
} catch( Exception e){};
dateChooser.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String pname = evt.getPropertyName();
if ( "date".equals( pname)) {
try{
model.fireTableCellUpdated( row, column);
} catch( Exception e){};
}
}
});
Super side effect - if you make your table smaller, with your mouse, and the JDateChooser exits the window table (margins are outside the table) while editing, then everything works perfect - no workaround necessary - It is a Swing bug that the refresh is done only when you have to take care of other windows outside you (probably an auto refresh all is called - that's why this bug was not discovered yet)

******
yourtable.getColumnModel().getColumn(7).setCellEditor(getDateChooserCellEditor());
or
yourtable.setDefaultEditor(java.util.Date.class, getDateChooserCellEditor());
******
public JDateChooserCellEditor getDateChooserCellEditor() {
JDateChooserCellEditor cellEditor = new JDateChooserCellEditor(){
#Override
public Component getTableCellEditorComponent(JTable jtable, Object o, boolean bln, int i, int i1) {
java.awt.event.ActionListener l = getStopCellEditorActionListener(jtable);
Component component = super.getTableCellEditorComponent(jtable, o, bln, i, i1);
JDateChooser dateChooser = (JDateChooser) component;
JTextField dateEditor = (JTextField) dateChooser.getDateEditor().getUiComponent();
try {
dateEditor.removeActionListener(l);
dateEditor.addActionListener(l);
} catch (Exception e) {
JOptionPane.showMessageDialog(null, e.getMessage());
}
return component;
}
};
return cellEditor;
}
private java.awt.event.ActionListener getStopCellEditorActionListener(JTable table) {
return new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
table.editingStopped(new ChangeEvent(table.getCellEditor()));
}
};
}

Related

How to make JTable with JComboBox respond only on double click instead of single click

So this is the first time I tried using CellEditors for my JTable to embed JComboBox and JSpinner. Everything works fine as expected wherein I can see the values in JComboBox model as well as JSpinner's model values.
However, I noticed that it always displays the JComboBox's values as soon as I make a single click on JTable's column that has the JComboBox.
It's not very user friendly because I think the user would prefer to double click on a JTable's column to get the dropdown box values and select values from it instead of a single click.
How can I change the JComboBox's behaviour to only display itself on double click?
I thought I'd apply a MouseListener to the JComboBox but I don't know what to do next.
Here's what I've written so far.
public class ScheduleDayCellEditor extends DefaultCellEditor{
private JComboBox jcmbDays;
private JTable jtblSchedule;
private DefaultComboBoxModel model;
public ScheduleDayCellEditor(){
super(new JComboBox());
model = new DefaultComboBoxModel(new String[]{"Mon","Tue","Wed","Thu","Fri"});
jcmbDays = new JComboBox(model);
jcmbDays.setEditable(false);
jcmbDays.setSelectedIndex(-1);
jcmbDays.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() == 2){
//? ? ? ?
}
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
});
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
return jcmbDays;
}
#Override
public Object getCellEditorValue() {
return jcmbDays.getSelectedItem(); //To change body of generated methods, choose Tools | Templates.
}
Here's a screenshot for additional description.
I'd appreciate any help.
Thank you.
If you don't need to extend DefaultCellEditor for some other reason, you can simply invoke its setClickCountToStart() method with a count of 2.
DefaultCellEditor editor = new DefaultCellEditor(jcmbDays);
editor.setClickCountToStart(2);
jcmbColumn.setCellEditor(editor);
Simply override isCellEditable by applying further criterion:
#Override
public boolean isCellEditable(EventObject aAnEvent) {
boolean cellEditable = super.isCellEditable(aAnEvent);
if (cellEditable && aAnEvent instanceof MouseEvent) {
cellEditable = ((MouseEvent) aAnEvent).getClickCount() == 2;
}
return cellEditable;
}

JTextField doesn't update on keypress

I have a JTable that uses a custom TableModel. I extended the AbstractCellEditor class and the cell correctly displays the text typed in to the textfield when I double-click the textfield. but when I just single-click select the cell in the table and start typing, the textfield receives the text but when I press enter, it doesn't update the text field. I attached a focus listener to the textfield to troubleshoot and found that it only gains and loses focus when I double click on the field. With a single-click it doesn't gain focus (even though it allows me to edit it). This boggles my mind! I've tried textField.grabFocus(), textField.requestFocusInWindow(), and all sorts of other things. Any suggestions? Thanks!
public class IndexerCellEditor extends AbstractCellEditor implements
TableCellEditor {
private JTextField textField;
private RecordValue currentValue;
public IndexerCellEditor(){
textField = new JTextField();
}
#Override
public boolean isCellEditable(EventObject e){
if(e instanceof MouseEvent){
return ((MouseEvent)e).getClickCount() >= 2;
}
return true;
}
#Override
public Object getCellEditorValue() {
return currentValue;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
textField.setBorder(BorderFactory.createLineBorder(Color.black, 1));
currentValue = (RecordValue) value;
textField.setText(currentValue.getValue());
textField.addFocusListener(new FocusListener(){
#Override
public void focusGained(FocusEvent e) {
System.out.println("focus gained");
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("focus lost");
}
});
textField.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
currentValue.setValue(((JTextField)e.getSource()).getText());
fireEditingStopped();
}
});
return textField;
}
}
OK so after about 8 more hours of banging my head against the wall, I found out 2 things:
I don't need an action listener on the jtextfield because the JTable takes care of that for me. When I hit enter after double clicking + typing OR single-click + typing, JTable automatically calls stopCellEditing(), which brings me to
I need to override stopCellEditing() in my IndexerCellEditor class to save the JTextField text before passing it up to the parent. The code I was missing:
#Override
public boolean stopCellEditing(){
currentValue = textField.getText();
return super.stopCellEditing();
}
Hope this helps anyone with the same problem.
EDIT
This works in my case because I also extended DefaultTableModel, which takes care of notifying the listeners with the method:
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
cells[rowIndex][columnIndex] = (String) aValue;
fireTableCellUpdated(rowIndex, columnIndex);
}
I tested this some more by building two different tables with the same extended DefaultTableModel. Placing them side-by-side in a JPanel, I could edit one cell in one table and upon pressing enter, it would update both the edited cell and its counterpart cell in the other table. In short, the listeners DO need to be notified with a fire... method call somewhere in the project.

Controlled editing of a row selection in JTable

I have a JTable displaying rows from an SQL database. The table is relatively small (only 4 columns and up to 1000 rows).
I would like to give the user the opportunity to edit any cells in the table but want to avoid restricting it so much so that they must use an edit dialog box (this makes for far easier error checking and validation but is less intuitive)
I have tried a few different ways of controlling edit selections using the valueChanged method of my JTable but haven't had much luck.
I would like each row to be edited and written to the database at the conclusion of editing. I would like that once a cell has been clicked to start the editing of that row, no other rows can be selected until the user has finished editing the row (other rows are grayed out). After editing each cell and pressing enter, the edit selection should jump to the next column in the same row.
Can anyone give pointers on how I can achieve this?
// Create table with database data
table = new JTable(new DefaultTableModel(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;
}
#Override
public boolean isCellEditable(int row, int col){
return true;
}
#Override
public boolean editCellAt(int row, int column) {
boolean ans = super.editCellAt(row, column);
if (ans) {
Component editor = table.getEditorComponent();
editor.requestFocusInWindow();
}
return ans;
}
#Override
public void valueChanged(ListSelectionEvent source) {
super.valueChanged(source);
if (table!=null)
table.changeSelection(getSelectedRow(), getSelectedColumn()+1, false, false);
}
};
Edit - custom cell editor with table pointer seems to be a start
public class ExchangeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTable table;
JComponent component = new JTextField();
public ExchangeTableCellEditor(JTable table) {
this.table = table;
}
public boolean stopCellEditing() {
boolean ans = super.stopCellEditing();
//now we want to increment the cell count
table.editCellAt(table.getSelectedRow(), table.getSelectedColumn()+1);
return ans;
}
#Override
public void cancelCellEditing() {
//do nothing... must accept cell changes
}
#Override
public Object getCellEditorValue() {
return ((JTextField)component).getText();
}
#Override
public Component getTableCellEditorComponent(JTable arg0, Object value,
boolean arg2, int arg3, int arg4) {
((JTextField)component).setText((String)value);
return component;
}
}
The default renderer and editor is typically adequate for most data types, but you can define custom renderers and editors as needed.
Addendum: I'm unfamiliar with the approach shown in your fragment. Instead, register a TableModelListener with your model, as shown below, and update the database with whatever granularity is warranted. See also How to Use Tables: Listening for Data Changes.
Addendum: #kleopatra is correct about your TableCellEditor. One convenient way to notify listeners is to invoke the super implementation, as shown here. Note that the delegate invokes fireEditingStopped().
/** #see https://stackoverflow.com/questions/9155596 */
public class NewJavaGUI extends JPanel {
private final JTable table;
public NewJavaGUI() {
String[] colNames = {"C1", "C2", "C3"};
DefaultTableModel model = new DefaultTableModel(colNames, 0) {
#Override
public boolean isCellEditable(int row, int col) {
// return your actual criteria
return true;
}
#Override
public Class getColumnClass(int col) {
// return your actual type tokens
return getValueAt(0, col).getClass();
}
};
// Add data; note auto-boxing
model.addRow(new Object[]{"A1", "A2", 42});
model.addRow(new Object[]{"B1", "B2", 42d});
model.addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
// DML as indicated
}
});
table = new JTable(model);
this.add(table);
}
private void display() {
JFrame f = new JFrame("NewJavaGUI");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new NewJavaGUI().display();
}
});
}
}
The behaviour you mention can be achieved by forcing your table to start editing again.
First make sure you now yourRow and Column and that you add your own tablecelleditor that extands from the AbstractCellEditor
then add this to your stopCellEditing method:
EventQueue.invokeLater(new Runnable()
{
public void run()
{
yourTable.editCellAt( yourRow, yourColumn+1);
}
});

How can I make a JTable cell do different things on single-click than on double-click?

I am using an editable JTable that contains a column named Subject. When the first row is empty and the user clicks on a subject cell to add new task, by default, the user has to click twice to make the cell editable. I want to make it editable on single-click and have it open another form on double-click. I have tried MouseListener but have not been able to solve it. Is there a way to solve this problem? If so, what is it?
My code:
class mouseRenderer extends DefaultTableCellRenderer {
JLabel lblcell = new JLabel();
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected, boolean hasFocus, int row,
int column) {
ttable.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
selrow = ttable.getSelectedRow();
selcol = ttable.getSelectedColumn();
if(e.getClickCount() == 1) {
if(selrow == 0) {
lblcell.setText("");
}
}
}
});
return lblcell;
}
}
For the one-click to edit, you could try the 'setClickCountToStart()' method of the celleditor used in your jtable.
You can try to create a custom CellEditor like this one and set it with setCellEditor()
public class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {
public boolean isCellEditable(EventObject evt) {
if (evt instanceof MouseEvent) {
int clickCount;
// For single-click activation
clickCount = 1;
// For double-click activation
clickCount = 2;
// For triple-click activation
clickCount = 3;
return ((MouseEvent)evt).getClickCount() >= clickCount;
}
return true;
}
}
The MouseListener is the way to go for capturing double clicks on a row. It should work fine.
As far as one-click to edit, you might want to select rows using a MouseMotionListener and let the JTable take the single-click to edit. Another option might be to use a MouseListener to detect the cell that was clicked, but that is getting a little messy.

JSpinner Update

I creates a dataTable and cellEditor form one column. This column is simple jSpinner. I have the following problem. When I enter some value in the spinner and select the another row, the value in the previous row won't be changed. If I press , it'll done. If I select or button, it will done too. But if I enter value and change selection, it won't be done. Help, please. Here is the CellEditor code.
public class DurationTableCellEditor extends AbstractCellEditor implements TableCellEditor{
final JSpinner spinner = new JSpinner();
// Initializes the spinner.
public DurationTableCellEditor() {
spinner.setModel(new SpinnerNumberModel(1,1,50000,1));
}
// Prepares the spinner component and returns it.
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
spinner.setValue(new Integer(value.toString()).intValue());
spinner.setCursor(null);
return spinner;
}
// Enables the editor only for double-clicks.
#Override
public boolean isCellEditable(EventObject evt) {
if (evt instanceof MouseEvent) {
return ((MouseEvent)evt).getClickCount() >= 1;
}
return true;
}
// Returns the spinners current value.
public Object getCellEditorValue() {
return spinner.getValue();
}
}
It's not clear how you're updating your data model, but one approach would be to implement ChangeListener in your CellEditor, much as this example implements ItemListener. For reference, see How to Use Tables: Using Other Editors. In particular, look at fireEditingStopped(). Finally, you'll need a corresponding TableCellRenderer.
do commitEdit()
// Returns the spinners current value.
public Object getCellEditorValue() {
spinner.commitEdit();
return spinner.getValue();
}

Categories