I have 4 jTextFields that I save the input to a file once a submit button is pressed. I want to be able to keep the submit button disabled until each field is at least not null. Where can i put something like this
if(jTextField1 == null || jTextField2 == null || jTextField3 == null || jTextField4 == null){
jButton2.setEnabled(false);
}
so that the program will enable/disable the button live. Like once the last field even has 1 character in it I want it to be enabled?
You need to add listeners to detect when the user enters text. In order to have it register any change (and not just when the user hits Enter) you should attach a DocumentListener to the underlying document of each JTextField.
Then, have each listener call a function to do your check and update the JButton's enabled status accordingly.
Related
A simple runnable demo:
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.List;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class ButtonDemo extends JFrame implements DocumentListener {
/**
*
*/
private static final long serialVersionUID = -68704905659973315L;
private JPanel panel = null;
private JTextField field1 = null;
private JTextField field2 = null;
private JButton btn = null;
private List<JTextField> textFields = null;
public static void main(String[] args) {
new ButtonDemo();
}
private ButtonDemo() {
this.panel = new JPanel();
this.field1 = new JTextField("JTextField_1");
this.field2 = new JTextField("JTextField_2");
this.field1.getDocument().addDocumentListener(this);
this.field2.getDocument().addDocumentListener(this);
this.textFields = new Vector<>();
this.textFields.add(field1);
this.textFields.add(field2);
this.btn = new JButton("Tests-Button");
this.panel.setLayout(new FlowLayout());
this.panel.add(field1);
this.panel.add(field2);
this.panel.add(btn);
this.add(panel);
this.setPreferredSize(new Dimension(200, 200));
this.pack();
this.setVisible(true);
}
#Override
public void insertUpdate(DocumentEvent e) {
updateButtonEnabledStatus(btn, textFields);
}
#Override
public void removeUpdate(DocumentEvent e) {
updateButtonEnabledStatus(btn, textFields);
}
#Override
public void changedUpdate(DocumentEvent e) {
updateButtonEnabledStatus(btn, textFields);
}
private void updateButtonEnabledStatus(JButton btn, List<JTextField> fields) {
boolean enabled = true;
for (JTextField field : fields) {
if (field.getText().length() == 0) {
enabled = false;
break;
}
}
btn.setEnabled(enabled);
}
}
The jTextField may be empty but it wont necessarily be null. You want to test the contents of it.
if(jTextField1.getText() == null || jTextField2.getText() == null || jTextField3.getText() == null || jTextField4.getText() == null){
jButton2.setEnabled(false);
}
If you want to update the button you need to check the contents on edit. You can do that by implementing an action listener to watch the contents of the text fields. You can do that with a DocumentListener (http://docs.oracle.com/javase/tutorial/uiswing/events/documentlistener.html).
use the keyPressed listener for each textfield to run your check wherever the text of a textfield is changed.
the method to disable the buttons is setEnabled(false);
Note that you will have to make the buttons dissabled when the program starts if your textfiends are empty at that time (listeners won't run)
Disable the button while initializing the components
eg:
public Home() {
initComponents();
button.setEnabled(false);
}
2)Enable it by calling the event 'KeyReleased' in the last jTextField.
eg:
private void jTextFieldKeyReleased(java.awt.event.KeyEvent evt) {
button.setEnabled(true);
}
If you do so, the button will automatically enable when any character enter into the last text feild.
Thank You :)
Related
A button in my app gets all the text you enter in 8 text fields and sends it to a table. I need code so that you need to fill in ALL fields before you can send the info. How do I write the if statement?
This is the code for the add info button:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
model.addRow(new Object[]{jTextField1.getText(), jTextField2.getText(),
jTextField3.getText(), jTextField4.getText(), jTextField5.getText(),
jTextField6.getText(), jTextField7.getText(), jTextField8.getText()});
}
You use the AND boolean operator; &&
if(!textfield1.getText().equals("") && !textfield2.getText().equals("") && !textfield3.getText().equals("") && so on){
//fill table in
}
In case you didn't know, ! means NOT, so we're saying
'if text field 1 is not empty and textfield 2 is not empty and ...'
A different approach is to disable the button until data is entered in all the text fields. The you can enable the button:
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.*;
public class DataEntered implements DocumentListener
{
private JButton button;
private List<JTextField> textFields = new ArrayList<JTextField>();
public DataEntered(JButton button)
{
this.button = button;
}
public void addTextField(JTextField textField)
{
textFields.add( textField );
textField.getDocument().addDocumentListener( this );
}
public boolean isDataEntered()
{
for (JTextField textField : textFields)
{
if (textField.getText().trim().length() == 0)
return false;
}
return true;
}
#Override
public void insertUpdate(DocumentEvent e)
{
checkData();
}
#Override
public void removeUpdate(DocumentEvent e)
{
checkData();
}
#Override
public void changedUpdate(DocumentEvent e) {}
private void checkData()
{
button.setEnabled( isDataEntered() );
}
private static void createAndShowUI()
{
JButton submit = new JButton( "Submit" );
submit.setEnabled( false );
JTextField textField1 = new JTextField(10);
JTextField textField2 = new JTextField(10);
DataEntered de = new DataEntered( submit );
de.addTextField( textField1 );
de.addTextField( textField2 );
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(textField1, BorderLayout.WEST);
frame.add(textField2, BorderLayout.EAST);
frame.add(submit, BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
A benefit of this approach is you don't worry about if statements which is a good thing as the code is easily changed.
You can check, if a field is empty by getting the text length and comparing it to zero.
If you don't want to check every text field in one single if-statement, you could check them separately and return, if one field has an invalid value.
if (jTextField1.getText().length() == 0)
{
//Tell the user, that the first field has to be filled in.
return;
}
if (jTextField2.getText().length() == 0)
{
//Tell the user, that the second field has to be filled in.
return;
}
//...
Otherwise, you could check them all in one if-statement:
if (jTextField1.getText().length() != 0
&& jTextField2.getText().length() != 0
&& ...)
{
//Fill in the table
}
else
{
//Tell the user, that the all fields have to be filled in.
}
i am writing a simple BMI calculator program. The application includes ActionListener, which handles button click, check if textfields are filled in and executes calculations.
For now, the ActionListener method is as a subclass of a main class. And it looks like this:
BMICalc.java
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class BMICalc extends JFrame {
private JMenuBar menuBar1;
private JMenu jMenu1;
private JMenuItem jMenuItem1, jMenuItem2;
private JButton jButton1;
private JPanel mainPanel, jPanel1;
private JLabel jLabel1, jLabel2;
private JTextField jTextField1, jTextField2;
private BMICalc() {
super("BMI Calculator");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(new Dimension(250, 300));
setLocationRelativeTo(null);
setLayout(new BorderLayout(10, 10));
mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
add(mainPanel);
jPanel1 = new JPanel(new GridLayout(6,2));
mainPanel.add(jPanel1, BorderLayout.CENTER);
menuBar1 = new JMenuBar();
jMenu1 = new JMenu("Help");
menuBar1.add(jMenu1);
jMenuItem1 = new JMenuItem("The purpose");
jMenu1.add(jMenuItem1);
jMenuItem2 = new JMenuItem("About");
jMenu1.add(jMenuItem2);
setJMenuBar(menuBar1);
jLabel1 = new JLabel("Enter weight in [kg]:");
jPanel1.add(jLabel1);
jTextField1 = new JTextField("");
jPanel1.add(jTextField1);
jLabel2 = new JLabel("Enter height in [cm]:");
jPanel1.add(jLabel2);
jTextField2 = new JTextField("");
jPanel1.add(jTextField2);
jButton1 = new JButton("Calculate");
mainPanel.add(jButton1, BorderLayout.SOUTH);
Handler handler = new Handler();
jButton1.addActionListener(handler);
jMenuItem1.addActionListener(handler);
jMenuItem2.addActionListener(handler);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
BMICalc bmicalc = new BMICalc();
bmicalc.setVisible(true);
}
});
}
private class Handler implements ActionListener {
public void actionPerformed(ActionEvent event) {
if (event.getSource() == jButton1) {
if (jTextField1.getText().equals("") || jTextField2.getText().equals("")) {
JOptionPane.showMessageDialog(null, "All fields must be filled in!", "Error", JOptionPane.INFORMATION_MESSAGE);
}
else {
Calculations calcs = new Calculations();
calcs.calculateBMI(jTextField1.getText(), jTextField2.getText());
JOptionPane.showMessageDialog(null, "Your BMI: " +calcs.returnBMI());
}
}
else if (event.getSource() == jMenuItem1) {
JOptionPane.showMessageDialog(null, "The program calculates BMI based on information entered by user." , "The purpose of this program", JOptionPane.INFORMATION_MESSAGE);
}
else if (event.getSource() == jMenuItem2) {
JOptionPane.showMessageDialog(null, "BMI Calc v. 1.0 " , "About", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}
Calculations.java
public class Calculations {
private double BMI;
private int weight, height;
public void calculateBMI(String sWeight, String sHeight) {
weight = Integer.parseInt(sWeight);
height = Integer.parseInt(sHeight);
BMI = weight/(height*height*0.0001);
}
public String returnBMI() {
return String.format("%.2f", BMI);
}
}
It works just fine, but I would like to make the code 'clenaer' and make the Handler a class, not a subclass, in another file. I've created a Handler.java and moved the whole Handler subclass, but the class doesn't see the jTextFields and jButton, as they are private (and as far as I'm concerned, they should be).
How can I separate ActionListener class, access these jObjects in it and still be fair with privacy stuff?
Thank you very much for answers.
You can pass the objects you need to the Handler class using the constructor:
public class Handler {
private JButton button;
private JTextField textField;
public Handler(JButton button, JTextField textField) {
this.button = button;
this.textField = textField;
}
}
And when you instantiate the class you just pass in the two variables you want:
Handler handler = new Handler(jButton1, jTextField1);
Explanation:
your Handler class is inner class of of BMICalc. When a nested class is not static (see also difference between static and non-static nested classes) it means that objects of those class exist within an object of the parent class. That's why your Handler class see private fields.
This is no problem for us when the class is static. You just have to pass in those variables to the Handler somehow (constructor or setter fields) and then you can reuse your class for other button-text field combinations.
Edit: Yet another way:
If your handler is to be used here, and only here, and nowhere else in the code, you could instantiate anonymous Handler and assign it to the field (no need to reuse somewhere else). So, in example:
jMenuItem1.addActionListener(new Handler() {
#Override
public void actionPerformed(ActionEvent event) {
JOptionPane.showMessageDialog(null, "The program calculates BMI based on information entered by user." , "The purpose of this program", JOptionPane.INFORMATION_MESSAGE);
}
});
jMenuItem2.addActionListener(new Handler() {
#Override
public void actionPerformed(ActionEvent event) {
JOptionPane.showMessageDialog(null, "BMI Calc v. 1.0 " , "About", JOptionPane.INFORMATION_MESSAGE);
}
});
Now you don't have to create one huge Handler with a lot of fields and ifs...
Note that the class Handler that you show is not a subclass of Main. To be a subclass means it inherits. What you have is an inner class.
You need to pass the references to the handler so it can refer to them. For example:
public class Handler implements ActionListener {
private final JTextField jTextField1;
private final JButton jButton1;
public Handler(final JTextField textField, final JButton button)
{
this.jTextField1 = textField;
this.jButton1 = button;
}
}
And create it like this:
Handler handler = new Handler(jTextField1, jButton1);
If you want to protect those JTextField and JMenuItem from the others classes while having the handler in another classe, then you need to add some methods to the BMICalc class:
public boolean isButton1(ActionEvent event) {
return event.getSource() == jButton1;
}
public boolean isJMenuItem1(ActionEvent event) {
return event.getSource() == jMenuItem1;
}
public boolean isJMenuItem2(ActionEvent event) {
return event.getSource() == jMenuItem2;
}
public String getJButton1Text() {
return this.jButton1.getText();
}
public String getJTextField1Text() {
return jTextField1.getText();
}
public String getJTextField2Text() {
return jTextField2.getText();
}
Then you need to have the following Handler class:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
public class Handler implements ActionListener {
private final BMICalc calc;
public Handler(BMICalc calc) {
this.calc = calc;
}
public void actionPerformed(ActionEvent event) {
if (calc.isButton1(event)) {
if (calc.getJTextField1Text().equals("") || calc.getJTextField2Text().equals("")) {
JOptionPane.showMessageDialog(null, "All fields must be filled in!", "Error", JOptionPane.INFORMATION_MESSAGE);
}
else {
Calculations calcs = new Calculations();
calcs.calculateBMI(calc.getJTextField1Text(), calc.getJTextField2Text());
JOptionPane.showMessageDialog(null, "Your BMI: " +calcs.returnBMI());
}
}
else if (calc.isJMenuItem1(event)) {
JOptionPane.showMessageDialog(null, "The program calculates BMI based on information entered by user." , "The purpose of this program", JOptionPane.INFORMATION_MESSAGE);
}
else if (calc.isJMenuItem2(event)) {
JOptionPane.showMessageDialog(null, "BMI Calc v. 1.0 " , "About", JOptionPane.INFORMATION_MESSAGE);
}
}
}
And change on line in the BMICalc :
Handler handler = new Handler(this);
But as the handler is supposed to handle only button and input of this view (and BMICalc class), it would make more sense (for me) to keep this Handler class private and inside the BMICalc class).
Hope this helps !
I have a buttongroup of 5 radio buttons and I need to check if at least one of them is selected or not.
Is this right?
buttonGroup1.isSelected(ButtonModel);
But what is ButtonmModel here?
buttonGroup1.getSelection()==null
Additionally, and more to the point of your question, you can determine if there is a selection among the radio buttons of a button group using the getSelected() method of the ButtonGroup class since it returns null if nothing is selected.
private boolean isSelection(ButtonGroup buttonGroup) {
return (buttonGroup.getSelected() != null);
}
I hope this helps someone!!!
You can't do much by way of the ButtonModel when it comes to finding a selected radio button. But a ButtonGroup does have the method getElements() that returns an Enumeration of AbstractButtons. These AbstractButtons can be cast to JRadioButtons during iteration through the Enumeration...
The following method will return the selected JRadioButton of the ButtonGroup passed into it, or return null if none is selected...
private JRadioButton getSelectedRadioButton(ButtonGroup buttonGroup) {
Enumeration<AbstractButton> abstractButtons = buttonGroup.getElements();
JRadioButton radioButton = null;
while (abstractButtons.hasMoreElements()) {
radioButton = (JRadioButton) abstractButtons.nextElement();
if (radioButton.isSelected()) {
break;
}
}
return radioButton;
}
Once you have the selected JRadioButton, it's elementary to access its properties. Suppose you want the text property of whatever button is selected...
String selectedRadioButtonText = getSelectedRadioButton(buttonGroup).getText();
I hope this helps someone!!!
Since you only have 5 radio buttons you could manually check all of them
(assume the radio buttons are named radio1, ... , radio5):
boolean isRadio1, isRadio2, isRadio3, isRadio4, isRadio5 = false;
if (radio1.isSelected() == true) isRadio1 = true;
if (radio2.isSelected() == true) isRadio2 = true;
if (radio3.isSelected() == true) isRadio3 = true;
if (radio4.isSelected() == true) isRadio4 = true;
if (radio5.isSelected() == true) isRadio5 = true;
Not the most elegant solution, but it will do the trick.
package radiobuttongroup;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
public class RadioButtonGroup {
public static void main(String[] args) {
new RadioButtonGroup();
}
private RadioButtonGroup() {
Container contentPane = frame.getContentPane();
contentPane.setLayout(new GridLayout(0, 1));
for (int i = 1; i <= 5; i++) {
JRadioButton radio = new JRadioButton(Integer.toString(i));
ButtonModel buttonModel = radio.getModel();
modelToRadioButton.put(buttonModel, radio);
buttonGroup.add(radio);
contentPane.add(radio);
}
JButton buttonTestSelection = new JButton("Selection Test");
JButton buttonClearSelection = new JButton("Clear Selection");
contentPane.add(buttonTestSelection);
contentPane.add(buttonClearSelection);
buttonTestSelection.addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent arg0) {
ButtonModel buttonModel = buttonGroup.getSelection();
if (buttonModel == null) {
System.out.println("No radio button is selected");
}
else {
if (modelToRadioButton.containsKey(buttonModel)) {
JRadioButton b = modelToRadioButton.get(buttonModel);
System.out.println("You selected button " + b.getText());
}
else {
System.err.println("Weird, unrecognised button model!");
}
}
}
});
buttonClearSelection.addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
buttonGroup.clearSelection();
}
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private JFrame frame = new JFrame("RadioButtonGroup");
private Map<ButtonModel, JRadioButton> modelToRadioButton =
new LinkedHashMap<ButtonModel, JRadioButton>();
private ButtonGroup buttonGroup = new ButtonGroup();
}
I have a GUI application that uses an InputVerifier to check the content of text fields before yielding the focus. This is all very normal. Yesterday, however, discovered a problem - it seems to be a bug, but I cannot find any mention of it anywhere. Before I report this as a bug, I thought I would ask: am I missing something obvious here?
Situation:
A set of text fields with InputVerifiers.
Listeners for FocusLost and FocusGained on all controls, so I can see what is happening.
A separate thread uses a DefaultKeyboardFocusManager to report (every 2 seconds) which control has the focus.
I place invalid data in a JTextField in the middle of the form, and try to leave the control.
If I try to move away from this control using the mouse, or using the tab-key, I cannot. The FocusLost event does not fire and the control properly retains the focus.
However, if I try to move away from the control in reverse tab order, using Shift-Tab, sometimes the FocusLost event fires. If this happens, the separate thread reports that no control has the focus, i.e., getFocusOwner() returns null.
Edit: below is a small sample program that shows the problem. The problem has nothing to do with the extra thread - the thread is just there to make the problem more obvious. If there is a race-condition, it is somewhere in Swing.
To see the problem, go to the second text box and empty it. The control should retain the focus, and does so unless you leave it by pressing shift-tab. Unlike the full application, the error seems to occur here 100% of the time. This is true both under OpenJDK 6 and under Oracle Java 7.
This is almost too obvious to be a bug, plus it happens in multiple Java environments. Hence, my suspicion that I am missing something obvious. Anyone?
public class FocusBugDemo extends JFrame {
static JTextField txtOne = new JTextField("Ignore this control");
static JTextField txtTwo = new JTextField("Delete this text, then press shift-tab");
static JLabel lblFocus = new JLabel("");
static KeyboardFocusManager kfm = new DefaultKeyboardFocusManager();
public static void main(String[] args) {
new FocusBugDemo();
Thread t = new Thread() {
#Override
public void run() {
while(true) {
Component c = kfm.getFocusOwner();
String focusInfo = "elsewhere";
if (c == null) { focusInfo = "null";
} else if (c == txtOne) { focusInfo = "txtOne";
} else if (c == txtTwo) { focusInfo = "txtTwo";
}
lblFocus.setText(System.currentTimeMillis() + " - Focus owner " + focusInfo);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
t.start();
}
private FocusBugDemo() {
super("Focus bug demo");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300,100));
setLayout(new GridLayout(3,1));
NotEmpty validator = new NotEmpty();
txtOne.setInputVerifier(validator);
txtTwo.setInputVerifier(validator);
add(txtOne);
add(txtTwo);
add(lblFocus);
pack();
setVisible(true);
}
private class NotEmpty extends InputVerifier {
#Override
public boolean verify(JComponent input) {
JTextField txtField = (JTextField) input;
return (txtField.getText().length() > 0);
}
}
}
Now reported to Oracle as bug 7167871.
Using your sscce, I am unable to reproduce the effect you describe on Mac OS X, Java 6, which supports #CatalinaIsland's observation. In particular, focus never leaves an empty text field using either tab or shift-tab; focus becomes null only when the frame is deactivated.
I see two threads accessing multiple fields with no synchronization at all. At a minimum, you should use EventQueue.invokeLater() in t to update the GUI, as described in Concurrency in Swing and show below.
The broader question is this: What focus problem are you trying to solve using t?
import java.awt.Component;
import java.awt.DefaultKeyboardFocusManager;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
public class FocusDemo {
private static final JTextField txtOne =
new JTextField("Ignore this control");
private static final JTextField txtTwo =
new JTextField("Delete this text, then press shift-tab");
private static final JLabel lblFocus = new JLabel("");
public static void main(String[] args) {
new FocusDemo();
Thread t = new Thread() {
#Override
public void run() {
while (true) {
EventQueue.invokeLater(new Runnable() {
KeyboardFocusManager kfm =
new DefaultKeyboardFocusManager();
#Override
public void run() {
Component c = kfm.getFocusOwner();
String focusInfo = "elsewhere";
if (c == null) {
focusInfo = "null";
} else if (c == txtOne) {
focusInfo = "txtOne";
} else if (c == txtTwo) {
focusInfo = "txtTwo";
}
lblFocus.setText(System.currentTimeMillis()
+ " - Focus owner " + focusInfo);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}
};
t.start();
}
private FocusDemo() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Focus bug demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setPreferredSize(new Dimension(300, 100));
f.setLayout(new GridLayout(3, 1));
NotEmpty validator = new NotEmpty();
txtOne.setInputVerifier(validator);
txtTwo.setInputVerifier(validator);
f.add(txtOne);
f.add(txtTwo);
f.add(lblFocus);
f.pack();
f.setVisible(true);
}
});
}
private class NotEmpty extends InputVerifier {
#Override
public boolean verify(JComponent input) {
JTextField txtField = (JTextField) input;
return (txtField.getText().length() > 0);
}
}
}
I'm trying to build a javax.swing.JTextField with javax.swing.JList for auto-completing like Google.
When a write a word, Google show several matches and
when a press the ▼ I can select some match using ▲ and ▼ and
can edit my input with ◀ and ▶ .
When I press Enter key search the content in the box.
When a press Esc the box change to the original input.
My aplication is about the Bible and I want to looking for a particular word when I'm studying the Word. I have seen the Java2sAutoTextField but don't have this particular behavior with the arrow keys.
This needs a custom coded component. Definitely a class that extends JTextField and in that class you have a JPopupMenu that will contain your JList. You will have to position the JPopupMenu right under the text field so that it looks like 1 component.
Your next trick is to filter as you type. I usually do this using Java6 TableRowSorter coupled with a JTable to which I pre-fill it with data. You're gonna need some change listeners on the JTextField and intercept each key typed and fetch your data.
Key pressed
Perform query in DB (or some data storage to get similar entries)
Populate JTable with those entires
Set RowFilter with regex based on JTextField entry to filter through retrieved data
Manage your actions with key listeners
EDIT
I whipped up a sample swing app to show what I stated. This is a copy/paste example and should work right off the bat (need JDK 1.6+). I basically got what you wanted and I put comments in places where I tell you to fill in the blanks.. like for example the Escape key event is consumed and you can do whatever you want with it.
The method initTableModel() just initializes the table model with data. Normally you would want to dynamically populate the table model with data from a database or something. A lot could be tweaked, but this is for example sake ;) So this should be a good enough example for you to modify to your complete your goal. Any more than this and you have to pay me $$$ :)
package test.text.googleclone;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.regex.PatternSyntaxException;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
public class SearchAutoFillTest {
private JFrame frame = null;
private JTextField searchField = null;
private JPopupMenu popup = null;
private JTable searchTable = null;
private TableRowSorter<DefaultTableModel> rowSorter = null;
private DefaultTableModel searchTableModel = null;
public SearchAutoFillTest() {
searchTableModel = new DefaultTableModel();
initTableModel();
rowSorter = new TableRowSorter<DefaultTableModel>(searchTableModel);
searchTable = new JTable(searchTableModel);
searchTable.setRowSorter(rowSorter);
searchTable.setFillsViewportHeight(true);
searchTable.getColumnModel().setColumnSelectionAllowed(false);
searchTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
searchTable.getTableHeader().setReorderingAllowed(false);
searchTable.setPreferredSize(new Dimension(775, 100));
searchTable.setGridColor(Color.WHITE);
searchField = new JTextField();
searchField.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void changedUpdate(DocumentEvent e) {
showPopup(e);
}
#Override
public void insertUpdate(DocumentEvent e) {
showPopup(e);
}
#Override
public void removeUpdate(DocumentEvent e) {
showPopup(e);
}
});
searchField.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
switch(code)
{
case KeyEvent.VK_UP:
{
cycleTableSelectionUp();
break;
}
case KeyEvent.VK_DOWN:
{
cycleTableSelectionDown();
break;
}
case KeyEvent.VK_LEFT:
{
//Do whatever you want here
break;
}
case KeyEvent.VK_RIGHT:
{
//Do whatever you want here
break;
}
}
}
#Override
public void keyPressed(KeyEvent e) {
}
});
KeyStroke keyStroke = KeyStroke.getKeyStroke("ESCAPE");
searchField.getInputMap().put(keyStroke, "ESCAPE");
searchField.getActionMap().put("ESCAPE", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
//Do what you wish here with the escape key.
}
});
popup = new JPopupMenu();
popup.add(searchTable);
popup.setVisible(false);
popup.setBorder(BorderFactory.createEmptyBorder());
JPanel searchPanel = new JPanel(new BorderLayout(5, 5));
searchPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
searchPanel.add(searchField, BorderLayout.CENTER);
frame = new JFrame();
frame.setLayout(new BorderLayout(5, 5));
frame.add(searchPanel, BorderLayout.NORTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 500);
center(frame);
frame.setVisible(true);
}
private final void newFilter() {
RowFilter<DefaultTableModel, Object> rf = null;
try {
rf = RowFilter.regexFilter(getFilterText(), 0);
}
catch(PatternSyntaxException e) {
return;
}
rowSorter.setRowFilter(rf);
}
private final String getFilterText() {
String orig = searchField.getText();
return "("+orig.toLowerCase()+")|("+orig.toUpperCase()+")";
}
private void showPopup(DocumentEvent e) {
if(e.getDocument().getLength() > 0) {
if(!popup.isVisible()) {
Rectangle r = searchField.getBounds();
popup.show(searchField, (r.x-4), (r.y+16));
popup.setVisible(true);
}
newFilter();
searchField.grabFocus();
}
else {
popup.setVisible(false);
}
}
private void cycleTableSelectionUp() {
ListSelectionModel selModel = searchTable.getSelectionModel();
int index0 = selModel.getMinSelectionIndex();
if(index0 > 0) {
selModel.setSelectionInterval(index0-1, index0-1);
}
}
private void cycleTableSelectionDown() {
ListSelectionModel selModel = searchTable.getSelectionModel();
int index0 = selModel.getMinSelectionIndex();
if(index0 == -1) {
selModel.setSelectionInterval(0, 0);
}
else if(index0 > -1) {
selModel.setSelectionInterval(index0+1, index0+1);
}
}
private void initTableModel() {
String[] columns = new String[] {"A"};
String[][] data = new String[][]
{
new String[] {"a"},
new String[] {"aa"},
new String[] {"aaab"},
new String[] {"aaabb"},
new String[] {"aaabbbz"},
new String[] {"b"},
new String[] {"bb"},
new String[] {"bbb"},
new String[] {"bbbbbbb"},
new String[] {"bbbbbbbeee"},
new String[] {"bbbbbbbeeexxx"},
new String[] {"ccc"},
new String[] {"cccc"},
new String[] {"ccccc"},
new String[] {"cccccaaaa"},
new String[] {"ccccccaaaa"},
};
searchTableModel.setDataVector(data, columns);
}
private void center(Window w) {
int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
int windowWidth = w.getWidth();
int windowHeight = w.getHeight();
if (windowHeight > screenHeight) {
return;
}
if (windowWidth > screenWidth) {
return;
}
int x = (screenWidth - windowWidth) / 2;
int y = (screenHeight - windowHeight) / 2;
w.setLocation(x, y);
}
public static void main(String ... args) {
new SearchAutoFillTest();
}
}
This component is called autocomplete and is included in a so called swing extensions porject.
Just have a look at: http://swingx.java.net/
There is a webstart with demos: http://swinglabs-demos.java.net/demos/swingxset6/swingxset.jnlp
use AutoComplete JTextField placed into JToolBar / MenuBar, notice you must to sort ArrayList before usage,
use undecoratted JDialog instead of JPopup (still have got a few important bugs),
a) create only one JDialog with parent to the JTextField or JMenuBar or JFrame,
b) always to search for getBounds from AutoComplete JTextField before visible JDialog on the screen, this Bounds are for possitioning JDialog correctly on the screen
c) wrap JDialog#setVisible(true) to the invokeLater()
override Escape for JDialog.setVisible(false)
put there close / hide JButton to avoiding overrive rest of important methods on focusLost (this calendar have got excelent workaround on focusLost, mouseClick, etc ...., could it be very easy to replace calendar funcionality with result from Comparator, you have to download codesource)
you can put there (my view) 6 / 9 / max 12 buttons, you can remove JButton Feels by setBackground(Color.white) for example, you cann't, please don't do it something with JDialog and these JButtons, you job will be only to setText("result from Comparator")
in the case that your ArrayList for AutoComplete JTextField was sorted, then you have two choises
a) easiest override bias from AutoComplete funcionality by add fils separate array for setText() for 6 / 9 / max 12 buttons on popup JDialog, if you setBackground(Color.white), then you don't care somehow about to hide JButtons without text
b) another way could be to create own Comparator for searching (the same AutoComplete funcionality) first 6 / 9 / max 12 matches,
for capturing an events from 6 / 9 / max 12 JButtons use putClientProperty or EventHandler or Swing Actions, where you only to test if text isEmpty :-),
maybe Swing Actions could be the best of ways because its events are scallable and you can enabled/disable (if JButtons text isEmpty) output from this Action by default
It sounds like you want a JComboBox (see Swing guide) rather than a JTextField/JList.
Of course, then you have a drop-down button, but there are possible ways to deal with this - see here.
It would be something along these lines:
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import javax.swing.*;
public class Component extends JComponent {
private final static String[] terms = {"Jesus",
"Jesus walks on water" //...
};
private static ArrayList<String> recent = new ArrayList<String>();
JTextField jtf;
JList jl;
public Component(){
// set up design
jtf = new JTextField();
jtf.setSize(this.getWidth() - 25, 25);
this.add(jtf);
//...
// add key listeners
}
class Listener implements KeyListener{
#Override
public void keyPressed(KeyEvent arg0) {
}
#Override
public void keyReleased(KeyEvent arg0) {
}
#Override
public void keyTyped(KeyEvent arg0) {
if (arg0.getKeyCode() == KeyEvent.VK_DOWN){
// set next item on list
}
else if (arg0.getKeyCode() == KeyEvent.VK_UP){
// set previous item on list
}
else if (arg0.getKeyCode() == KeyEvent.VK_ENTER){
// search
}
else if (arg0.getKeyCode() == KeyEvent.VK_ESCAPE){
jtf.setText("");
}
else{
// check list for matches
}
}
}
}
The default behavior is that all key events go to the component which has the focus. So what you need to do is identify keys which should really go to the other component and install a KeyListener to both.
In that listener, you can forward the events to the other component.
See this answer how to dispatch an event to a new component. In your case, source must be the other component (the list, if your text field originally received the event and vice versa).