autoscroll JScrollPane disturbing - java

I'm working on a text editor, the main panel is composed of 3 JTextPanes, 2 on the side which show the number of line and common syntax errors, and 1 in the middle for the main edition. The whole stuff is packed in a JScrollPane.
The autoscroll issue appears when the users jump a line (press ENTER), the KeyListeners attached add a new entry in the 2 sides JTextPanes (num and syntax error for the line),
in reaction, the JScrollPane autoscroll in the bottom of the docs, probably to show the new text inserted in the 2 sides JTextPanes.
I partially fix the problem by setting the JScrollBar's position for each new line (added by the user) in my KeyListeners. Using scrollRectToVisible for example, or better by selecting a proper part of text in one of the 2 sides JTextPanes.
However, the final effect is not so great, for each new line the vertical scrollbar oscillates, and we can easily crash the app by pressing ENTER for a few seconds. I've been looking for solutions with a lot of methods of the JScrollPane class and trying AdjustmentListener but unsuccessfully. Would you help me?
PS: Sorry for my English. I am French, our forums suck.

SSCCE great source of inspiration, as this one worked well (couldn't see my problem when running it) it seems that my method actually works, but was not running in the right listener in my real code.
Thanks anyway!
There is the SSCCE, its a simple JScrollPane composed of one central JTextPane for edition and one lateral for the lines number. The placeScroll() method place the scrollbar so the caret in the main JTextPane is in the middle (vertically) when the paneLigne try to push it down.
Bye
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.BorderLayout;
import javax.swing.JScrollPane;
public class SSCCE extends JFrame {
private JTextPane paneLigne, main;
private String tempchain;
public SSCCE() {
this.setSize(500,500);
this.setTitle("S");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
paneLigne = new JTextPane();
paneLigne.setEditable(false);
tempchain = "";
for(int j=1; j<40; j++)
tempchain+=" " + j + " \n";
paneLigne.setText(tempchain);
main = new JTextPane();
main.addKeyListener(new KeyListener() {
private int lastnline=0;
public void keyPressed(KeyEvent arg0) {
if(arg0.getKeyCode()==10) {
String tecste=main.getText();
int n=nbLignes(tecste);
if(n>38) {
if(lastnline<n) {
paneLigne.setText(paneLigne.getText()+" " + (n+1) + " \n");
} else {
this.retablirLignes(tecste);
}
} else {
paneLigne.setText(tempchain);
}
lastnline=n;
this.placeScroll();
}
}
#Override
public void keyReleased(KeyEvent arg0) { }
#Override
public void keyTyped(KeyEvent arg0) { }
private int nbLignes(String str) {
int ret=1;
for(int n=0, cpt=0; (n=str.indexOf('\n', cpt))!=-1; cpt=n+1)
ret++;
return ret;
}
public void retablirLignes(String stret) {
int n=this.nbLignes(stret);
String retoure="";
if(n>=40) {
for(int j=1; j<n+2; j++)
retoure+=" " + j + " \n";
paneLigne.setText(retoure);
}
lastnline=n;
}
public void placeScroll() {
// TODO Auto-generated method stub
if(paneLigne!=null) {
int n=this.nbLignesBuen(main.getText().substring(0, main.getCaretPosition()));
if(n!=-1) {
paneLigne.select(paneLigne.getText().indexOf(""+n), n+1);
} else {
paneLigne.select(0,1);
}
}
}
private int nbLignesBuen(String str) { //return the index of the last 20th line
int ret=0;
for(int n, cpt=0; (n=str.indexOf('\n', cpt))!=-1; cpt=n+1)
ret++;
if(ret>20)
ret-=20;
else
ret=-1;
return ret;
}
});
JPanel contentpane=new JPanel(new BorderLayout());
contentpane.add(paneLigne, BorderLayout.WEST);
contentpane.add(main, BorderLayout.CENTER);
this.setContentPane(new JScrollPane(contentpane));
this.setVisible(true);
}
public static void main(String[] args) {
SSCCE fen = new SSCCE();
}
}

Related

How to rectify "over-sensitive" JTextField input issue

I've just come over to JAVA from BASIC, so please forgive me for any convention issues with my coding.
I have a mildly infuritating GUI-related issue. I have constructed a matrix-multiplication calculator which does calculations on inputted letters (using MOD 26). I run the program exclusively on Eclipse and have included a picture of the actual program in operation below...
The issue is with the Jtextfield boxes which take the inputted letters:
Actual program running on Eclipse
Basically, I wanted to be able to input a letter in a box, then to have the cursor move to the next box (cycling back to the first box after all the letters are inputted). I used a 'KeyListener' as shown in the code for the first JTextField 'letter box':
A_RUCE = new JTextField(2);
A_RUCE.setFont(new Font("Serif", Font.PLAIN, 18));
A_RUCE.setEditable(true);
cp.add(A_RUCE);
A_RUCE.setText("");
A_RUCE.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e){
String value=A_RUCE.getText();
if(value.length()==0){
B_RUCE.requestFocus();
}
}
});
The problem is that when I run the program, most of the time, the thing becomes "over sensitive". I try to input a letter and the cursor skips a box. I consider key repeating rate on my computer and such, and have adjusted settings, but this did not help.
All I am seeking is for when a letter is inputted, that the cursor moves to the next box without it 'skipping' over a box. I don't know why that happens, and I cannot figure out an alternative way to fix the issue. I would be very grateful if someone could help with this. Thank you kindly.
I'd be more inclined to use a DocumentListener. You can check the length of the text and then move onto the next field. This is the sort of thing you could do:
import java.awt.Component;
import java.awt.Container;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JPanel;
import java.awt.EventQueue;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
public class FB extends JFrame implements DocumentListener {
private JTextField[] fields = new JTextField[3];
public void insertUpdate(DocumentEvent e) {
int textLen = e.getDocument().getLength();
if (textLen == 1) {
// focus next
int nextToFocus = 0;
Component c = getFocusOwner();
for (int i = 0; i < fields.length; i++) {
if (fields[i] == c) {
nextToFocus = i + 1;
break;
}
}
nextToFocus %= fields.length;
System.out.printf("Next to focus is field %d%n", nextToFocus);
fields[nextToFocus].requestFocus();
}
}
public void removeUpdate(DocumentEvent e) {
} // No-op
public void changedUpdate(DocumentEvent e) {
// No-op
}
private void setGui() {
try {
setLocation(0, 100);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container cp = getContentPane();
JPanel fieldsPanel = new JPanel();
for (int i = 0; i < fields.length; i++) {
JTextField tf = new JTextField(1);
tf.getDocument().addDocumentListener(this);
fieldsPanel.add(tf);
fields[i] = tf;
}
setContentPane(fieldsPanel);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
EventQueue.invokeAndWait(() -> {
FB f = new FB();
f.setGui();
f.setSize(200, 200);
f.setVisible(true);
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

Can't update JLabel array

I'm writing a thing that has a JLabel array that has something in the first index and then detects right keys that shifts the thing to the right. Basically, [x][][][][][][] starts, then if I press the right key, it shifts to [][x][][][][][]. Currently, I can't get the JLabel to properly update. It shows up correctly at first, but when I hit the right key, the entire thing gets messed up. Ie, it starts like this:
(I have the numbers to gauge the shifting, they'll go away when I figure this problem out)
Which is right, but when I hit the right key, everything disappears (just blank.) However, my test in the console still gives me the correct values though.
The numbers don't even show up :/
Here's the relevant code:
public Template() {
for (int i = 0; i < 7; i++) {
positions[i] = new JLabel();
positions[i].setFont(new Font("Times New Roman", Font.PLAIN, 15));
System.out.println("Yay");
}
// I initialize my JLabel array here instead of below because if I don't do this, there's no change whatsoever if I press the right key. I'm not sure if I did it correctly, though.
initialize(p1, 0);
}
private void initialize(String p, int index) {
frame = new JFrame();
JPanel header = new JPanel();
header.setBackground(Color.WHITE);
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
header.setLayout(gbl);
header.setPreferredSize(new Dimension(400, 50));
c.fill = GridBagConstraints.HORIZONTAL;
for (int i = 0; i < 7; i++) {
if (i == index) {
positions[i].setText(p + "");
System.out.println("indexed set");
} else {
positions[i].setText("" + i);
System.out.println(positions[i].getText());
System.out.println("set");
}
c.gridx = i;
c.gridy = 0;
header.add(positions[i], c);
}
frame.add(header, BorderLayout.NORTH);
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
playMove(p);
}
public void playMove(String p) {
...
frame.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent keyEvent) {
System.out.println("Game.keyPressed -- event: " + keyEvent);
if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) {
System.out.println("Right key pressed!");
int index = searchHeader();
System.out.println("index: " + index);
if (index != 6) {
int shift = index + 1;
System.out.println("shift: " + shift);
SwingUtilities.updateComponentTreeUI(frame);
initialize(p, shift, t);
}
}
}
#Override
public void keyReleased(KeyEvent keyEvent) {
System.out.println("Game.keyReleased -- event: " + keyEvent);
}
});
}
public int searchHeader() {
for (int i = 0; i < 7; i++) {
String val = positions[i].getText();
if (val == "x" || val == "o") {
System.out.println("i:" + i);
return i;
}
}
return 0;
}
My System.out.println(); tests all return the correct values (the correct index, shift, and numbers in the array) but I'm not sure why the thing isn't showing up :/
If my code needs clarification or missed something (I did cut some irrelevant info off after taking advice from my last post) please tell me
Since you didn't provide a minimal runnable example that we could copy into our IDE, run, and debug, I came up with this example GUI that shows an X shifting to the right.
I used the right arrow key to perform the shift.
Here's the GUI when starting the application.
Here's the GUI after shifting the X three positions.
Here's the GUI after shifting the X four positions.
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Netbeans section.
The first thing I did was start the Swing application by calling the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
Next, I created a JFrame. The JFrame methods must be called in a specific order. This is the order I use for my Swing applications.
I created a main JPanel to hold the array of eight JLabels. I also created an int to hold the position of the X. I used a FlowLayout to lay out the array of JLabels. I( used the Box class to put some space between the JLabels.
As you're creating a more complicated game, create an application model using plain Java getter/setter classes.
I used Key Bindings to bind the right arrow key to the Action class. I used AbstractAction so I'd only have to code the actionPerformed method.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class ShiftRightExample implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ShiftRightExample());
}
private int position;
private JLabel[] spotLabel;
public ShiftRightExample() {
this.position = 0;
}
#Override
public void run() {
JFrame frame = new JFrame("Shift Right Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = createMainPanel();
frame.add(mainPanel, BorderLayout.CENTER);
setKeyBindings(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
this.spotLabel = new JLabel[8];
for (int index = 0; index < spotLabel.length; index++) {
spotLabel[index] = new JLabel(" ");
panel.add(spotLabel[index]);
if (index < (spotLabel.length - 1)) {
panel.add(Box.createHorizontalStrut(20));
}
}
updateMainPanel(position, "X");
return panel;
}
private void setKeyBindings(JPanel panel) {
panel.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "shiftRight");
panel.getActionMap().put("shiftRight", new ShiftRightAction());
}
public void updateMainPanel(int position, String value) {
spotLabel[position].setText(value);
}
public class ShiftRightAction extends AbstractAction {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent event) {
updateMainPanel(position, " ");
position++;
position = position % spotLabel.length;
updateMainPanel(position, "X");
}
}
}

Change background colour on typing certain value (jNumberField)

I am trying to change the background colour of a number field based on the numbers that are typed in. Just like a number field turns red when you type a letter in it. I want it to also change red when you type numbers below 1 and above 7.
I understand that you can do this with a button, but I want it to change when you're typing.
This is my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class applet03 extends JApplet {
private JButton bHoeveelheid = new JButton();
private JNumberField nfAantal = new JNumberField();
private JTextArea taLijst = new JTextArea("");
private JScrollPane taLijstScrollPane = new JScrollPane(taLijst);
public void init() {
Container cp = getContentPane();
cp.setLayout(null);
cp.setBounds(0, 0, 442, 478);
bHoeveelheid.setBounds(224, 56, 59, 33);
bHoeveelheid.setMargin(new Insets(2, 2, 2, 2));
bHoeveelheid.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
bHoeveelheid_ActionPerformed(evt);
}
});
cp.add(bHoeveelheid);
nfAantal.setBounds(304, 56, 99, 36);
nfAantal.setText("Vul getal in");
nfAantal.setHorizontalAlignment(SwingConstants.CENTER);
nfAantal.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent evt) {
nfAantal_FocusGained(evt);
}
});
nfAantal.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent evt) {
nfAantal_KeyPressed(evt);
}
});
cp.add(nfAantal);
taLijstScrollPane.setBounds(224, 136, 168, 180);
cp.add(taLijstScrollPane);
}
public void bHoeveelheid_ActionPerformed(ActionEvent evt) {
if (nfAantal.getInt() < 1 || nfAantal.getInt() > 7) {
nfAantal.setBackground(Color.RED);
} else {
//some other code (not impotant for now)
}
}
public void nfAantal_FocusGained(FocusEvent evt) {
if (nfAantal.getText().equals("Vul getal in")) {
nfAantal.clear();
}
}
public void nfAantal_KeyPressed(KeyEvent evt) {
if (nfAantal.getInt() < 1 || nfAantal.getInt() > 7) {
nfAantal.setBackground(Color.RED);
} else {
//some other code (not impotant for now)
}
}
}
The button part works, but the KeyEvent not.
And when I run this I keep getting these errors:
Exception in thread "AWT-EventQueue-1" java.lang.NumberFormatException: empty String
But when I do this, it kinda works. I still get the same errors but it works. (By the way not optimal, because it only appends it to the text field every second key pressing.):
public void nfAantal_KeyPressed(KeyEvent evt) {
if (nfAantal.getInt() < 1 || nfAantal.getInt() > 7) {
taLijst.append(nfAantal.getText());
} else {
//some other code (not impotant for now)
}
}
So if someone knows why this doesn't work or knows a better way to accomplish this. Then that would be very appreciated!
(By the way I use Java 1.8)
Edit:
I now have (nfAantal.getText().length() > 0) && (nfAantal.getInt() < 1 || nfAantal.getInt() > 7) in the if statement and that got rid of the errors. (Thanks to #Joe)
But that still doesn't solve my question on how to turn the background red on certain values.
Aditional information about JNumberField:
I think this link has the code that makes up JNumberField and this link contains a download to the .jar file with in there the JNumberField Java file.
Another edit:
I think I found out for myself why it doesn't work with a JNumberfield; in the code it changes the background to white if its value is numeric so only if there is a way to get around this part of the code or change it (which I don't know how to do) my question can be answered for a JNumberField, if this isn't the case then I will use the JFormattedTextField instead.
This is the code that needs to be altered or circumvented:
protected void processKeyEvent(KeyEvent e) {
super.processKeyEvent(e);
if (isNumeric() || getText().equals("-") ||
getText().equals("") || getText().equals("."))
setBackground(Color.white);
else
setBackground(Color.red);
}
I don't know what a JNumberField is. It's not part of the JDK and when I searched with Google, it turned up several different ones.
I also don't understand why you are writing an applet.
Therefore, the below code may not be appropriate since it is a stand-alone Swing application that uses JFormattedTextField and DocumentListener.
Explanations after the code.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.text.NumberFormat;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.NumberFormatter;
public class RedNumbr implements DocumentListener, Runnable {
private JFormattedTextField aantal;
private JFrame frame;
#Override
public void insertUpdate(DocumentEvent event) {
handleDocumentEvent(event);
}
#Override
public void removeUpdate(DocumentEvent event) {
handleDocumentEvent(event);
}
#Override
public void changedUpdate(DocumentEvent event) {
// Never called for 'JFormattedTextField'
}
#Override
public void run() {
showGui();
}
private JPanel createNumberPanel() {
JPanel numberPanel = new JPanel();
NumberFormat format = NumberFormat.getIntegerInstance();
NumberFormatter formatter = new NumberFormatter(format);
aantal = new JFormattedTextField(formatter);
aantal.setColumns(10);
Document doc = aantal.getDocument();
doc.addDocumentListener(this);
numberPanel.add(aantal);
return numberPanel;
}
private void handleDocumentEvent(DocumentEvent event) {
Document doc = event.getDocument();
int len = doc.getLength();
if (len > 0) {
try {
String text = doc.getText(0, len);
int number = Integer.parseInt(text);
Color fg;
if (number < 1 || number > 7) {
fg = Color.red;
}
else {
fg = UIManager.getColor("TextField.foreground");
}
aantal.setForeground(fg);
}
catch (BadLocationException | NumberFormatException x) {
// Ignore.
}
}
}
private void showGui() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createNumberPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new RedNumbr());
}
}
Whenever the contents of the JFormattedTextField are changed, the relevant DocumentListener method is invoked. In those methods I check whether the JFormattedTextField contains a number and if it does then I change the foreground color of the JFormattedTextField according to your conditions, i.e. if the number is less than one (1) or greater than 7 (seven).
Note that the NumberFormatter does not prevent entering non-digits because JFormattedTextField handles that when it loses focus. Nonetheless it handles entering positive and negative numbers, which saves you some work. And the point of my answer is simply to demonstrate how to change the foreground color based on the entered text, which I believe answers your question.
EDIT
In order to change the background of the JFormattedTextField, rather than the foreground, you just need to change two lines in my code, above.
Replace
fg = UIManager.getColor("TextField.foreground");
with
fg = UIManager.getColor("TextField.background");
and also replace
aantal.setForeground(fg);
with
aantal.setBackground(fg);
I suppose that the JNumberField is an extension of JTextField and if it is the case it could be the synchronization problem between the main thread and the thread that is triggered by the KeyEvent action. To make sure that the event thread will be carried out you code it with invokeLater.
public void nfAantal_KeyPressed(KeyEvent evt) {
if (nfAantal.getText().length() > 0) && (nfAantal.getInt() < 1 || nfAantal.getInt() > 7) {
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
nfAantal.setBackground(Color.RED);
}
});
} else {
//some other code (not impotant for now)
}
}

Java Swing adding mouse Listener (inner class) for an array of buttons leads to malfunction

Ok, so the program I want to build is simple. There are five buttons named from 0 to 4. If any of the buttons are pressed, then the number 0 to 4 is printed in console.
I have used a GridLayout to place the buttons in the frame. And to set up each button I have created a method, inicializarIG().
This inicializarIG() method creates an array of 5 buttons and inside a for loop it does:
Create an instance of a button for each cell in the array of buttons.
Set up a mouseListener for each button. The value to print in each Listener is different, it is determined by the index of the loop (AND I WANT TO DO IT BY USING THE INDEX!).
Add The button to the main frame.
Surprisingly, This simple program doesn´t work properly. It always print the number "5" no matter what button is pressed:
NOTE: I had to put the index var outside the inicializarIG() method in order to fulfill the var scope for the Listeners. I don't know if the problem is related, just saying in cause it might help.
THE CODE:
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
public class IGrafica {
private JFrame frame;
public int index=0;
public IGrafica(){
frame = new JFrame();
configureFrame();
inicializarIG();
}
public void configureFrame(){
frame.setBounds(100,100,400,400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(1,5)); //
}
public void inicializarIG(){
//Buttons
JButton [] botones = new JButton [5];
//Loop to set up buttons and add the mouseListener
for (index = 0; index < botones.length; index++) {
botones[index] = new JButton(Integer.toString(index));
//Set up each listener
botones[index].addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
System.out.println(index);
//texto.setBackground(colores[index]);
}
});
//Add the button
frame.getContentPane().add(botones[index]);
}
}
public void visualizate(){
frame.setVisible(true);
}
public static void main(String[] args) {
IGrafica window = new IGrafica();
EventQueue.invokeLater(new Runnable() {
public void run() {
window.visualizate();
}
});
}
}
Thank you in advance. Any idea will be welcomed.
Jesús
First of all, don't use a MouseListener for this, but rather use an ActionListener, since this works best for buttons. Next of all, you need to remember that your listener is a class of its own, and it needs a variable of its own to store its own index, else it uses the final value of the loop index which is not what you want. Either this or use the ActionEvent's actionCommand property, a value which will match the text of the button. So:
botones[index].addActionListener(new MyActionListener(index));
and
// a private inner class
private class MyActionListener implements ActionListener {
private int index;
public MyActionListener(int index) {
this.index = index;
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("index is: " + index);
System.out.println("Action Command is: " + e.getActionCommand());
}
}
Or if you want to use an anonymous inner class:
botones[index].addActionListener(new ActionListener() {
private int myIndex;
{
this.myIndex = index;
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("index is: " + myIndex);
}
});

Auto-Completing JTextField and Arrow Keys

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).

Categories