I would like to check the validity of user input every time they change a value on the JTable. The method I thought of is similar to this simplified sample code:
public static void main(String[] args) {
JFrame main = new JFrame();
JTable table = new JTable(6, 4);
table.setSize(300, 300);
table.getModel().addTableModelListener((TableModelEvent e) -> {
Object s = e.getSource();
TableModel d = (TableModel) s;
if(!checkValid(d.getValueAt(e.getFirstRow(), e.getColumn())))
{
d.setValueAt(" - ", e.getFirstRow(), e.getColumn());
}
});
main.add(table);
main.setSize(300,300);
main.setLocationRelativeTo(null);
main.setVisible(true);
main.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
The code will check the input if a change in the table occurs and will revert back to "-" if the input is invalid.
However, an error will occur stating that Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError.
a.) Could someone explain the error and how to solve the issue?
b.) Or is there a better way of implementing a listener that checks the user input BEFORE exiting editing mode or saving the table?
EDIT: I have tried implementing the CellEditorListener like the sample below:
table.getCellEditor().addCellEditorListener(new CellEditorListener() {
public void editingStopped(ChangeEvent e)
{
}
public void editingCanceled(ChangeEvent e)
{
}
});
This in turn prompted an error Exception in thread "main" java.lang.NullPointerException. There isn't that much of documentation on CellEditorListener and didn't quite understood on how it works and how to use it.
According to the corresponding section of the corresponding Java tutorials, you can override stopCellEditing of DefaultCellEditor to return false if the editor should not lose focus or true otherwise. Which means we can use it to check user input first and then, according to the user's input, return false if he/she enters invalid text (or true if he/she enters valid one).
In the following example code I'm using a JTextField, which lets the users type whatever they want and then checks the user's input in stopCellEditing to be non-empty (as defined by my static checkValid method, but you can obviously alter it according to your needs):
import java.awt.Toolkit;
import javax.swing.DefaultCellEditor;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;
public class Main {
public static boolean checkValid(final String text) {
return text != null && !text.trim().isEmpty(); //Your checks here...
}
public static class MyCellEditor extends DefaultCellEditor {
public MyCellEditor() {
super(new JTextField());
}
#Override
public boolean stopCellEditing() {
final JTextField field = (JTextField) getComponent();
if (checkValid(field.getText())) {
//field.setBackground(Color.WHITE);
return super.stopCellEditing(); //Fires 'editing stopped' event and returns true.
}
Toolkit.getDefaultToolkit().beep();
//field.setBackground(Color.ORANGE.darker());
JOptionPane.showMessageDialog(field, "You must enter a non-empty value!", "Oups!", JOptionPane.ERROR_MESSAGE);
return false;
}
}
public static void main(final String[] args) {
final JTable table = new JTable(new DefaultTableModel(10, 10));
table.setDefaultEditor(Object.class, new MyCellEditor());
final JFrame frame = new JFrame("JTable DefaultEditor");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(table);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
I use DefaultTableModel for easy initialization of the table. It also returns that each cell in the table is editable (we obviously need at least one cell to be editable, in order to check the validity of the program). Every cell starts initially empty, but the cell editor won't let you leave it empty, if you start an editing event.
An alternative solution could be to add an InputVerifier in the JTextField of the editor, but this would be a bit more tricky as I tested it, so I would rather not post it here in favor of the better above solution (and also suggested by the Java tutorial).
Related
I am trying to make a functionality for quick search of clients from database on some keystrokes from user, using an editable combobox. What i wanted to have is, user will put in some letters and if those lettters match with some clients, those clients will be remained in the current data model of the combobox.
The code is as follows.
Please fix the exception occuring in the code. Thanks in Advance !!
Exception in thread "AWT-EventQueue-0"
java.lang.IllegalStateException: Attempt to mutate in notification
import java.util.ArrayList;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
class ComboSearch extends JFrame implements CaretListener
{
private JComboBox mycombo;
private ArrayList<String> list;
private DefaultComboBoxModel<String> isolatemodel,model;
public ComboSearch()
{
setSize(400, 400);
setLayout(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
mycombo=new JComboBox();
mycombo.setEditable(true);
mycombo.setBounds(30,30, 350, 50);
isolatemodel=new DefaultComboBoxModel();
model=new DefaultComboBoxModel();
list=new ArrayList();
add(mycombo);
mycombo.setModel(isolatemodel);
((JTextField)mycombo.getEditor()
.getEditorComponent()).addCaretListener(this);
setVisible(true);}
private void addElements()
{
list.add("Rambhau, Vijay Nagar");
list.add("Surya, Ashok Puri");
list.add("Mourya, Shahjapur");
list.add("Kishorji & sons, Bhopal");
list.add("Fablica & jewels, Itanagar");
list.add("Guru Kripa,Ujjain");
list.add("Hariram Nai & Bakes, Indore");
list.add("Ganesh Sev Bhandar, Harda");
list.add("Greatsome Higs, Jabalpur");
list.add("Treks and hains, Nalanda");
list.add("Tata Indora, Hoshangabad");
list.add("Paankhai Seth, Madurai");
list.add("Katappa, Shikara");
list.add("Gunjan Samosa, Vijay Nagar");
list.add("Ramesh hustlers , Vijay Nagar");
}
public void makeModels()
{
addElements();
list.stream().forEach((client) -> {
isolatemodel.addElement(client);
});
}
#Override
public void caretUpdate(CaretEvent e)
{
String searchText=((JTextField)mycombo.getEditor()
.getEditorComponent()).getText();
if(!searchText.isEmpty())
{
for(int i=0; i<isolatemodel.getSize();i++)
{
if(isolatemodel.getElementAt(i).contains(searchText))
{
model.removeAllElements();
model.addElement(isolatemodel.getElementAt(i));
}
}
mycombo.setModel(model);
mycombo.showPopup();
}
else
{
mycombo.setModel(isolatemodel);
}
}
}
public class Execute
{
public static void main(String[] args)
{
ComboSearch searchIt=new ComboSearch();
searchIt.makeModels();
}
}
model.removeAllElements();
model.addElement(isolatemodel.getElementAt(i));
and if those lettters match with some clients, those clients will be remained in the current data model
Well, then it doesn't make sense to remove all the items every time you find a match. Then you will only ever have one entry left in the combo box.
You need to remove all the items BEFORE you start your loop processing and then just add back in the elements that match.
IllegalStateException:
You are trying to update the combo box model before processing of the typed event has finished processing.
Wrap the code in the listener in a SwingUtiltities.invokeLater(...) to the code will be executed after all processing has been finished.
Also, you would generally use a DocuementListener to be notified when the text of the editor has changed, not a CaretListener. The user could use the arrow keys to move the caret so there is no need to update the model in that case.
EDIT at end of post
Test Code and Output
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import javax.swing.JFormattedTextField;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.text.NumberFormatter;
public class Test{
private JFormattedTextField input, input2;
private NumberFormatter formatter;
private PropertyChangeListener listener;
public Test(){
formatter = new NumberFormatter(NumberFormat.getNumberInstance());
input = new JFormattedTextField(formatter);
input2 = new JFormattedTextField(formatter);
listener = new PropertyChangeListener(){
#Override
public void propertyChange(PropertyChangeEvent evt) {
convert(evt);
}
};
input.setColumns(4);
input2.setColumns(4);
input.addPropertyChangeListener("value", listener);
input2.addPropertyChangeListener("value", listener);
input.setValue(0.0);
JPanel panel = new JPanel();
panel.add(input);
panel.add(input2);
JOptionPane.showMessageDialog(null, panel);
}
private void convert(PropertyChangeEvent evt){
if (evt.getSource()== input){
if (evt.getSource()!= null){
double temp;
temp = converter((Double)evt.getNewValue());
input2.setValue(temp);
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable(){
public void run(){
new Test();
}
});
}
private double converter(double value){
value = value*2;
return value;
}
}
The stack trace:
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Double
at test.Test.convert(Test.java:46)
My thoughts
Because I have a method that passes in a double (it's convert()) , and seemingly evt.getNewValue() returns the direct value, which at the time of input is technically a long, it's throwing that error.
But every attempt at parsing my evt.getNewValue() to a double hasn't worked. Perhaps a little knowledge of what I'm trying to do with this program would help.
What The Program is for
So I've got a JPanel (in a tabbedPane) that has two JFormattedTextField inputs. It's a conversion application. My conversion class method passes in a double and returns a double. I'd like the fields to be linked together, or in other words, as soon as one field's input is changed the other changes with it (as in it's the output of the conversion).
I was considering scrapping the PropertyChangListener and going for a DocumentListener instead, but opted to try the former first as the latter has 3 overrideable methods I have to take care of, one of which might cause some unexpected results (highlighting and deleting the field would trigger two events for example).
TL;DR:
Is there a better way of getting a dynamically updating, dual input field application? Input one number into one field and the other field's number automatically updates.
Still a novice at Java.
Edit1
I've found a temporary solution: Have a DecimalFormat as the format in the JFormattedTextField. But if it could work without having a decimal as well I'd love it.
Edit2
Question answered, didn't realize evt.getNewValue() was returning a Number instance.
All you know for sure is that the object returned by evt.getNewValue() is a Number object. What if you use that information to your advantage and try something along these lines:
temp = ((Number)evt.getNewValue()).doubleValue();
I've created an application that uses FocusListener to make sure a text fieid's value is always positive. When the user inputs negative value and then click the "tab" key to move focus away from the text field, the value will be multiplied by -1 so that the resulted value is positive. However, when I ran the application, the text field didn't change. I am not sure what I did wrong, and will appreciate any help.
Here is my code:
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
public class AlwaysPositive extends JFrame implements FocusListener {
JTextField posField = new JTextField("30",5);
public AlwaysPositive() {
super("AlwaysPositive");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel pane = new JPanel();
JTextField posField = new JTextField("30",5);
JButton ok= new JButton("ok");
posField.addFocusListener(this);
pane.add(posField);
pane.add(ok);
add(pane);
setVisible(true);
}
public void focusLost(FocusEvent event) {
try {
float pos = Float.parseFloat(posField.getText());
if (pos < 0)
pos = pos*-1;
posField.setText("" + pos);
} catch (NumberFormatException nfe) {
posField.setText("0");
}
}
public void focusGained(FocusEvent event) {
}
public static void main(String[] arguments) {
AlwaysPositive ap = new AlwaysPositive();
}
}
The main problem is you are shadowing your variables
You declare
JTextField posField = new JTextField("30",5);
As an instance variable, but in your constructor, you redeclare it again...
public AlwaysPositive() {
//...
JTextField posField = new JTextField("30",5);
posField.addFocusListener(this);
//...
}
Add attach the focus listener to it, but in the focusLost method, you are referring to the instance variable, which isn't the one that is actually on the screen
Start by changing the declaration within the constructor
public AlwaysPositive() {
//...
posField = new JTextField("30",5);
posField.addFocusListener(this);
//...
}
However, there are better solutions to use then FocusListener.
For example, you could use an InputVerifier that will allow you to verify the value of the field and make decisions about whether focus should be moved or not.
Take a look at How to Use the Focus Subsystem and Validating Input in particular
You could also use a DocumentFilter to restrict what the user can actually enter, filtering the input as the user types it. Take a look at Text Component Features and Implementing a Document Filter in particular.
You can also take a look at these examples for more ideas
When you create object of same name inside a method, the listener is set to the method object and not to the Class object.
I enter a name on my JTextfield , But my table don't filter any thing!
My code:
public class UserPage_Admin extends JFrame {
JTable table;
UserModel model;
public UserPage_Admin() {
model = new UserModel(...);
TableRowSorter sorter = new TableRowSorter<TableModel>(model);
table = new JTable(model);
table.setRowSorter(sorter);
add(new JScrollPane(table), BorderLayout.CENTER);
add(panelForm(), BorderLayout.PAGE_START);
RowFilter<UserModel, Object> rf = null;
try {
rf = RowFilter.regexFilter(filterTF.getText(), 0);
} catch (PatternSyntaxException pse) {
return;
}
sorter.setRowFilter(rf);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(850, 600);
setVisible(true);
}
you are calling RowFilter.regexFilter(filterTF.getText(), 0); in UserPage_Admin() constructor. How it supposed to read the text from the filterTF. I think you should call it from an Action Event Listener assigned to a JButton which will be called upon submitting(clicking) the text as follows:
submitButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String text = filterText.getText();
if (text.length() == 0) {
sorter.setRowFilter(null);
} else {
sorter.setRowFilter(RowFilter.regexFilter(text, 0));
}
}
});
If you want to use Filtering upon user key type event, add key listener to the text field you are taking input filter-string.
filterTxtFeild.addKeyListener(new KeyAdapter() {
public void keykeyReleased(KeyEvent evt) {
// on each key type event filter.
// put your filter code as submit button
}
});
However, as it is suggested in the comments below, to work with Swing Text Component, one should have used the Document.addDocumentListener(DocumentListener). A Swing text component uses a Document to represent its content. Document events occur when the content of a document changes in any way. Add the document listener as follows:
filterTxtFeild.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
// put your filter code here upon data insertion
}
#Override
public void removeUpdate(DocumentEvent e)
{
//put your filter code here upon data removal
}
#Override
public void changedUpdate(DocumentEvent e) {}
});
Edit: Why DocumentListener is preferable ?
If we want validation of input in the data source, using KeyEvent while filtering the data you’ll find it does not reflect the user’s keystroke and input events are sent to the listeners before they are processed by the data source. suppose that when we want an user name to be entered, someone input a text like "$%^&". On such invalid input, KeyEvent will still be fired even though no valid changes has been made to data source. But, DocumentListeners are notified only when a valid changes has been made to the data source. Data entry components produce events in which a validator can listen for asynchronously, one should never modify the contents of a text component from within a DocumentListener. If we do so, the program will likely deadlock.
I enter a name on my JTextfield , But my table don't filter any thing!
there are two ways, you don't mentioned expected logics
filtering on KeyTyped from DocumentListener (your code talking about ???)
from ActionListener invoked from ENTER Key
both a.m. ways added to JTextField
then there are another two very important options
filtering in whole JTables, columns and rows (your code talking about ???)
in one (quite possible in two or more, never tried) columns
everything depends of your goal
everything by using standard methods implemented in API
You simply fail to use sorter after initializing it. You should call JTable.setRowSorter().
I can't .setText(...) for a JTextField outside of the class that creates the gui. I'm very confused and I feel like there is something basic I am missing. I need some help here.
Here is what I am doing:
In a class (called MainClass) I create an instance of a class that creates my gui
TestText gui = new TestText();
with a constructor that sets the default settings (a JTextField and a button with a listener). Then I call the a setter that I wrote, where I pass it a string that is to set the text of the JTextField:
gui.setText("new");
But "new" doesn't show up on the gui.
I know my setter works from within the class because if I make a call to the setter from the button that I created in gui then the changes show up on the gui.
The part that really confuses me is this: If I call my getter just before my setter, then it returns the old value. Then if I call the getter again after I call the setter then it returns the new value, while the gui continues to show the old value. I thought that maybe it just isn't repainting the gui so I tried all kinds of permutations of .invalidate(), .validate(), .update() and .repaint(), all from the MainClass and from inside the setter. But none did anything.
Is it possible that I somehow have 2 different instances of the gui and I'm only editing one of them?
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestText {
private JTextField textField;
private JButton button;
private JPanel frame;
JFrame jFrame;
public void setText(String text) {
textField.setText(text);
}
public String getText() {
return textField.getText();
}
public TestText() {
this.textField.setText("98.6");
this.jFrame = new JFrame("TestText");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setText("new (button)");
}
});
}
public void setData(TestText data) {
data.setText("new (setData)");
}
public void getData(TestText data) {
}
public boolean isModified(TestText data) {
return false;
}
public void createGui(String[] args) {
jFrame.setContentPane(new TestText().frame);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.pack();
jFrame.setVisible(true);
}
}
and then here's the main class that I'm trying to create the gui from:
public class MainClass {
public static void main(String[] args) {
TestText gui = new TestText();
gui.createGui(null);
System.out.println(gui.getText());
gui.setData(gui);
System.out.println(gui.getText());
gui.setText("new (MainClass)");
System.out.println(gui.getText());
}
}
It looks like you're missing the reference to the text field I think...
gui.referenceToTextField.setText("new word");
EDIT: Very nice SSCCE! However, there are several problems (not in the order provided, necessarily).
You are overriding the setText() method. Don't do this unless you want the method to do something different—why you would want to do this I have no idea.
You aren't even using the args array in the createGui() method. You can create methods without specifying any parameters/arguments.
The getData() method is, right now, useless (If I were you, given what you're trying to accomplish, I would remove the method entirely). I'm assuming, from the apt method name (another good thing to do), that you want to retrieve the data from the text field. Put this line inside the method (and change the word void to String) and you should be set!
return textField.getText();
Truthfully, this shouldn't even run due to a NullPointerException. You aren't initializing any of the components other than the JFrame. You need to do things like textField = new JTextField(20).
Even if you could run this, the button wouldn't work at all because the button hasn't been told that it does anything. To do this call button.addActionListener() with the name of the listening class as the argument. If the GUI and listening classes happen to be in one class together (like I will show you in a minute), the argument is simply this.
You aren't adding any components to the frame. For every component you wish to put into your frame, you must call add(Component cmpt).
Having said this, I think I'm just going to try to recreate what you're trying to do here into one class. You don't really need two separate classes unless the listening portion is excessively long.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class TestText extends JFrame implements ActionListener {
JTextField textField = new JTextField(20);
JButton set = new JButton("Set Text");
JButton get = new JButton("Get Text");
public TestText() {
super();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(textField);
set.addActionListener(this); //this tells the program that the button actually triggers an event
add(set);
get.addActionListener(this);
add(get);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == set) {
textField.setText(JOptionPane.showInputDialog(null, "Enter a new word for the text field:"));
} else {
System.out.println(textField.getText());
}
}
public static void main(String[] args) {
TestText tt = new TestText();
}
}
After doing some reading I think it is due to my code not accessing the Event Dispatch Thread like #camickr suggested. Here is some documentation that helped me solve my problem.