So I have an editable Jtable (TreeTable actually) with a custom model. The current setValueAt method needs to be updated to prevent values greater than a certain amount (dependant on the row).
This is easy to prevent (simply don't set the v开发者_如何学编程alue if it's invalid) but what's the best way to inform the user that the chosen amount was invalid? Popping a dialog from the model seems rather nasty.
Popping a dialog from the model seems rather nasty.
Agreed. Use a custom editor for the column. Maybe something like:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class TableEdit extends JFrame
{
TableEdit()
{
JTable table = new JTable(5,5);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollpane = new JScrollPane(table);
getContentPane().add(scrollpane);
// Use a custom editor
TableCellEditor fce = new FiveCharacterEditor();
table.setDefaultEditor(Object.class, fce);
}
class FiveCharacterEditor extends DefaultCellEditor
{
FiveCharacterEditor()
{
super( new JTextField() );
}
public boolean stopCellEditing()
{
JTable table = (JTable)getComponent().getParent();
try
{
String editingValue = (String)getCellEditorValue();
if(editingValue.length() != 5)
{
JTextField textField = (JTextField)getComponent();
textField.setBorder(new LineBorder(Color.red));
textField.selectAll();
textField.requestFocusInWindow();
JOptionPane.showMessageDialog(
null,
"Please enter string with 5 letters.",
"Alert!",JOptionPane.ERROR_MESSAGE);
return false;
}
}
catch(ClassCastException exception)
{
return false;
}
return super.stopCellEditing();
}
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
Component c = super.getTableCellEditorComponent(
table, value, isSelected, row, column);
((JComponent)c).setBorder(new LineBorder(Color.black));
return c;
}
}
public static void main(String [] args)
{
JFrame frame = new TableEdit();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
as soon as the user enters an invalid value ,change background of typical cell or text to some eye catching color(dark red or something like that)...I think this will be the simplest.
An alternative solution would be to:
- Create your own custom CellEditor which will extend the DefaultCellEditor.
- Get the particular column(s) of your JTable where you want to use your custom CellEditor
- Attach your custom CellEditor to the particular column(s) of your JTable
I shall go through these points step-by-step below:
Below you'll find the fully compilable code for my custom CellEditor. The editor posts an alert box to the screen if the value entered is not within a certain range.
import java.awt.Color; import javax.swing.AbstractAction; import javax.swing.DefaultCellEditor; import javax.swing.JFormattedTextField; import javax.swing.JOptionPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.Component; import java.awt.Toolkit; import java.text.NumberFormat; import java.text.ParseException; import javax.swing.border.LineBorder; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.NumberFormatter; /** * * This class ensures that the Mark entered * in the Marks column is of the correct format * and within the correct range.<p> * * In the event that there is an incorrect format/value entered, * an alert box will appear instructing the user same. * Implements a cell editor that uses a formatted text field * to edit Integer values.<p> * * When instantiated, the object essentially is listening for * any events with regards to it's associated table i.e. * first 100 table etc. * * @author Mark Burleigh * @version %I%, %G% * @since 1.0 */ public class MarksColumnEditor extends DefaultCellEditor { JFormattedTextField ftf; NumberFormat integerFormat; private Integer minimum, maximum; private boolean DEBUG = false; public MarksColumnEditor(int min, int max) { super(new JFormattedTextField()); ftf = (JFormattedTextField)getComponent(); minimum = new Integer(min); maximum = new Integer(max); //Set up the editor for the integer cells. integerFormat = NumberFormat.getIntegerInstance(); NumberFormatter intFormatter = new NumberFormatter(integerFormat); intFormatter.setFormat(integerFormat); /** * * NumberFormatter inherits setMinimum and setMaximum * from class Class InternationalFormatter * * setMinimum() - Sets the minimum permissible value. * setMaximum() - Sets the maximum permissible value. * * @see https://docs.oracle.com/javase/7/docs/api/javax/swing/text/InternationalFormatter.html#setMaximum(java.lang.Comparable) * */ // intFormatter.setMinimum(minimum); intFormatter.setMaximum(maximum); ftf.setFormatterFactory( new DefaultFormatterFactory(intFormatter)); ftf.setValue(minimum); ftf.setHorizontalAlignment(JTextField.TRAILING); ftf.setFocusLostBehavior(JFormattedTextField.PERSIST); //React when the user presses Enter while the editor is //active. (Tab is handled as specified by //JFormattedTextField's focusLostBehavior property.) ftf.getInputMap().put(KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0), "check"); ftf.getActionMap().put("check", new AbstractAction() { public void actionPerformed(ActionEvent e) { if (!ftf.isEditValid()) { //The text is invalid. if (userSaysRevert()) { //reverted ftf.postActionEvent(); //inform the editor } } else try { //The text is valid, ftf.commitEdit(); //so use it. ftf.postActionEvent(); //stop editing } catch (java.text.ParseException exc) { } } }); } /** * * This method is implicitly used as soon as * a MarksColumnEditor object is instantiated * e.g. inside in the Eas_Main.java * somJTableColumn.setCellEditor( * new MarksColumnEditor(aMinMark,aMaxMark)); * <p> * It overrides the same method in the DefaultCellEditor * * * @param table - the JTable that is asking the editor to edit; can be null * @param value - the value of the cell to be edited; it is up to the specific * editor to interpret and draw the value. For example, if value is the string * "true", it could be rendered as a string or it could be rendered as a check * box that is checked. null is a valid value * @param isSelected - true if the cell is to be rendered with highlighting * @param row - the row of the cell being edited * @param column - the column of the cell being edited * * @return Component - the component for editing * * @see https://docs.oracle.com/javase/7/docs/api/javax/swing/table/TableCellEditor.html * */ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { JFormattedTextField ftf = (JFormattedTextField)super.getTableCellEditorComponent( table, value, isSelected, row, column); ftf.setValue(value); ftf.setFont(new java.awt.Font("Tahoma", 1, 15)); ftf.setBorder(new LineBorder(Color.BLUE)); // ftf.setBackground(Color.LIGHT_GRAY); // ftf.setBackground(new Color(204,255,204)); ftf.setBackground(new Color(219,254,219)); return ftf; } /** * * This overrides the method located in DefaultCellEditor. * This method is never implicitly called by the developer. * The method ensures that the value entered is an integer * * * @param * @param * * @return Object - the integer value entered by the user * into the particular cell * * @see https://docs.oracle.com/javase/7/docs/api/javax/swing/CellEditor.html#getCellEditorValue() * */ public Object getCellEditorValue() { JFormattedTextField ftf = (JFormattedTextField) getComponent(); Object o = ftf.getValue(); if(o != null) { if (o instanceof Integer) { return o; } else if (o instanceof Number) { return new Integer(((Number)o).intValue()); } else { if (DEBUG) { System.out.println("getCellEditorValue: o isn't a Number"); } if(o == null) { System.out.println(); System.out.println("Object o = null"); System.out.println(); } try { return integerFormat.parseObject(o.toString()); } catch (ParseException exc) { System.err.println("getCellEditorValue: can't parse o: " + o); return null; } } }// end if o != null return null; // if the value in the cell is null and is left unchanged // then the value null is return to the cell } /** * Override to check whether the edit is valid, * setting the value if it is and complaining if * away, we need to invoke the superclass's version * of this method so that everything gets cleaned up. * * * @return {boolean} true - let the editor go away * * @see * */ public boolean stopCellEditing() { JFormattedTextField ftf = (JFormattedTextField)getComponent(); if (ftf.isEditValid()) { try { ftf.commitEdit(); } catch (java.text.ParseException exc) { } } else { //text is invalid if (!userSaysRevert()) { //user wants to edit return false; //don't let the editor go away } } return super.stopCellEditing(); } /** * Lets the user know that the text they entered is * bad. Returns true if the user elects to revert to * the last good value. Otherwise, returns false, * indicating that the user wants to continue editing. * * @return {boolean} true - referred to the previous Mark * */ protected boolean userSaysRevert() { Toolkit.getDefaultToolkit().beep(); ftf.selectAll(); if (maximum !=0) { Object[] options = {"Edit", "Revert"}; int answer = JOptionPane.showOptionDialog( SwingUtilities.getWindowAncestor(ftf), "The Mark must be an number between "+ minimum + " and "+ maximum + ".\n" +"You can either continue editing or revert to the\n" +"last valid Mark entered.", "Invalid Mark", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[1]); if (answer == 1) { //Revert! ftf.setValue(ftf.getValue()); return true; } } else { JOptionPane.showMessageDialog(SwingUtilities.getWindowAncestor(ftf), "You must press the save button first", "Alert", JOptionPane.ERROR_MESSAGE ); ftf.setValue(null); ftf.repaint(); return true; //revert to empty mark } return false; } }
Get the particular column(s) of your JTable where you want to use your custom CellEditor. This code be implemented in your GUI class e.g.
TableColumn marks_column_A = first_100_Table.getColumnModel().getColumn(2);
Attach your custom CellEditor to the particular column(s) of your JTable. This code be implemented in your GUI class (setCellRenderer() method is located in Class TableColumn) e.g.
mark_column_A.setCellRenderer(new MarksColumnEditor(0,300));
Resulting Output Below you'll find a screenshot of a JTable which uses the above code. Whenever and user enters a number with a value that is less than 0 (min value) or greater than 300 (max value), an alert box will appear outlining the user of an incorrect value.
A custom cell editor as suggested by camickr is fine in most cases. But sometimes errors can occur in your model's setValueAt
method and you have to notify your users if you can't handle them.
For this case, you can implement custom events and event listeners.
On error, you fire a custom event from the setValueAt
method. In the JTable
or in any other appropriate component you add an event listener to your model to listen to those events and handle them, for example by popping up a dialog.
A basic example how to implement custom events can be found here.
精彩评论