I'm running an application with similar behaviour to that in the test case below.
The problem is that when you switch focus to a different window by clicking on it and keeping the below application frame in view and then click directly into the text field with the focusGained listener, close the dialog and then all key input will be lost to the all of the text fields in the application.
If you click anywhere in the application first or the icon in the task bar to gain focus back then this does not occur.
This is Java 8 specific - in Java 7 it will not lose focus, not sure about java 9 but that is not an option anyway
The test case below demonstrates this behaviour.
public class FocusTest extends JFrame
{
JTextField noFocus;
public static void main(String[] args)
{
FocusTest ft = new FocusTest();
ft.setVisible(true);
}
public FocusTest()
{
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100,100,300,150);
setLayout(new GridLayout(3,1, 2, 2));
setTitle("Losing keyboard...");
noFocus = new JTextField();
add(noFocus);
JTextField jft = new JTextField();
jft.addFocusListener(new FocusAdapter()
{
#Override
public void focusGained(FocusEvent e)
{
createDialog().setVisible(true);
noFocus.requestFocusInWindow();
}
});
add(jft);
JButton jb = new JButton("OPEN");
jb.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if(e.getActionCommand().equals("OPEN"))
createDialog().setVisible(true);
}
});
add(jb);
}
private JDialog createDialog()
{
final JDialog jd = new JDialog(this, true);
jd.setLocationRelativeTo(this);
jd.setLayout(new BorderLayout());
jd.getContentPane().add(new JTextField(), BorderLayout.CENTER);
JButton jb = new JButton("Close");
jb.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if(e.getActionCommand().equals("Close"))
jd.dispose();
}
});
jd.getContentPane().add(jb, BorderLayout.SOUTH);
jd.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
jd.pack();
return jd;
}
}
Not exactly sure what's happening, but one solution is to use a SwingUtilities.invokeLater():
#Override
public void focusGained(FocusEvent e)
{
noFocus.requestFocusInWindow();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createDialog().setVisible(true);
}
});
}
This will allow the text field to get focus properly before the dialog is made visible.
Related
I want when user press enter button on keyboard, then program should function like when click on ok button.
My code:
public class T3 extends JFrame implements ActionListener {
JButton okBtn;
public T3() {
this.setFocusable(true);
this.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
// if enter button is pressed in keyboard, then show "Enter Button pressed" message
}
#Override
public void keyPressed(KeyEvent e) {
// if enter button is pressed in keyboard, then show "Enter Button pressed" message
}
#Override
public void keyReleased(KeyEvent e) {
//To change body of implemented methods use File | Settings | File Templates.
}
});
add(createForm(), BorderLayout.NORTH);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 500);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new T3();
}
});
}
public JPanel createForm() {
JPanel panel = new JPanel();
okBtn = new JButton("Ok");
okBtn.addActionListener(this);
panel.add(okBtn);
return panel;
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == okBtn) {
System.out.println("Enter Button pressed");
}
}
}
Now, Not react !
KeyListeners don't work on containers when a child component has the focus. You will need an application wide KeyListener. Check out this SO question:
Setting up application wide Key Listeners
I have a problem with the focus listener implemented by CustomTextField class. The focus listener is only called when another Swing component is getting the focus. But If I move the JFrame istelf by dragging it with the mouse, the focusLost() method is never called (in other words, it doesn´t seem that the focus is shifting from CustomTextField to JFrame).
EDIT: The solution of my question is below:
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import javax.swing.*;
public class ScrollFocus extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ScrollFocus();
}
});
}
public ScrollFocus() {
this.setLayout(new BorderLayout());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Vector<String> values = new Vector<>();
values.add("a");
values.add("b");
values.add("c");
values.add("d");
values.add("e");
JComboBox<String> comboBox = new JComboBox<>(values);
JScrollPane scrollPane = new JScrollPane(comboBox);
this.add(scrollPane, BorderLayout.NORTH);
CustomTextField customTextField = new CustomTextField();
this.add(customTextField, BorderLayout.CENTER);
JButton button = new JButton("press");
final JPopupMenu menu = new JPopupMenu("Menu");
menu.add(new JMenuItem("Test"));
button.setComponentPopupMenu(menu);
this.add(button, BorderLayout.SOUTH);
pack();
setVisible(true);
}
class CustomTextField extends JTextField implements FocusListener {
private CustomPopup customPopup = new CustomPopup();
public CustomTextField() {
this.addFocusListener(this);
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "VK_UP");
this.getActionMap().put("VK_UP", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
setPopupSize();
customPopup.show(CustomTextField.this, CustomTextField.this.getX(), CustomTextField.this.getY() + CustomTextField.this.getHeight());
customPopup.setSelectedIndex(0);
}
});
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "VK_DOWN");
this.getActionMap().put("VK_DOWN", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
setPopupSize();
customPopup.show(CustomTextField.this, CustomTextField.this.getX(), CustomTextField.this.getY() + CustomTextField.this.getHeight());
customPopup.setSelectedIndex(0);
}
});
}
public void setPopupSize() {
customPopup.setPopupSize(new Dimension(this.getWidth(), 110));
}
#Override
public void focusGained(FocusEvent e) {
}
#Override
public void focusLost(FocusEvent e) {
}
class CustomPopup extends JPopupMenu {
String[] values = new String[]{"Value1", "Value2", "Value3", "Value4", "Value5", "Value6", "Value7",
"Value8","Value9", "Value10", "Value11", "Value12", "Value13", "Value14", "Value15", "Value16",};
JList<String> list = new JList<>(values);
JScrollPane scrollPane = new JScrollPane(list);
public int index = 0;
public CustomPopup() {
this.setLayout(new GridLayout(0,1));
this.add(scrollPane);
this.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_UP){
if(customPopup.index > 0)
customPopup.setSelectedIndex(--customPopup.index);
}
else if(e.getKeyCode() == KeyEvent.VK_DOWN){
if(customPopup.index < customPopup.getListSize()-1)
customPopup.setSelectedIndex(++customPopup.index);
}
}
});
this.addFocusListener(new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
index=0;
}
});
pack();
}
public void setSelectedIndex(int index) {
list.setSelectedIndex(index);
list.ensureIndexIsVisible(index);
requestFocus();
}
public int getListSize() {
return values.length;
}
}
}
}
//customPopup.setVisible(true);
customPopup.show((JComponent)e.getSource(), 0, 20);
You should be using the show(...) method to show the popup. This must add some listeners to the popup so you will no longer need the FocusListener on the text field.
However, now this is a different problem. The text field loses focus so the Action never get invoked. That would be ok, but the JList never gains focus so it doesn't respond to the up/down keys unless you click on the list box first. I'm not sure what the problem is here.
Maybe you can try to make the popup, scrollpane and list all non-focusable so that focus remains on the text field?
'Focus', which is admittedly a slightly ambiguous term, generally applies to a component, not to an entire window. We think of the "window with focus", but I think what we really mean is "the current window, the one which contains the focus." I would not expect focus_lost to be called if I moved the window (aka JFrame) itself.
Another way to think of it; if I had a text field, clicked in it, and typed a letter or two, I would see those letters in that text field. If I then moved the window slightly and typed another letter or two, I would still expect those letters to appear in that field. It still has focus, and never lost it.
I have a swing GUI with multiple JTabbedPanes; each tab contains two JButtons at the top, then a JTextArea (for user input), and a JTextField at the bottom for a result.
My problem is that I can't get the JTextArea to gain focus after switching tabs without either clicking in it with the mouse or using the tab key on the keyboard?
I have...
frame.addWindowFocusListener(new WindowAdapter() {
public void windowGainedFocus(WindowEvent e) {
textArea_1.requestFocusInWindow();
...and this works well when the app is first run (the textArea in the first tab has focus) but when I switch to another tabbedpane the first button now has the focus instead of the textArea, and when I switch back to the first tab the textArea has lost focus and once again the first button has focus.
I've tried adding a requestFocus to each textArea, and I've tried "Bring to front" on each textArea, and I've messed around with Focus Traversal but nothing I do seems to make the textArea gain focus on a tab change?
This has had me stumped for a week so any help will be gratefully received?
Add a ChangeListener to your JTabbedPane. Here's the general idea:
[Sorry, I used JTextFields instead of JTextAreas since I had an old stub laying around - but the idea is the same.]
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class JTabbedPaneDemo3 implements Runnable
{
JTextField txtFoo;
JTextField txtBar;
JTabbedPane tabbedPane;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new JTabbedPaneDemo3());
}
public void run()
{
txtFoo = new JTextField(10);
final JPanel pnlFoo = new JPanel();
pnlFoo.add(new JButton("Button 1"));
pnlFoo.add(new JLabel("Foo"));
pnlFoo.add(txtFoo);
txtBar = new JTextField(10);
final JPanel pnlBar = new JPanel();
pnlBar.add(new JButton("Button 3"));
pnlBar.add(new JLabel("Bar"));
pnlBar.add(txtBar);
tabbedPane = new JTabbedPane();
tabbedPane.addTab("Tab 1", pnlFoo);
tabbedPane.addTab("Tab 2", pnlBar);
tabbedPane.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
Component comp = tabbedPane.getSelectedComponent();
if (comp.equals(pnlFoo))
{
txtFoo.requestFocusInWindow();
}
else if (comp.equals(pnlBar))
{
txtBar.requestFocusInWindow();
}
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(460, 200);
frame.getContentPane().add(tabbedPane, BorderLayout.CENTER);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
txtFoo.requestFocusInWindow();
}
}
I couldn't make "getSelectedComponent()" work for me, it just wasn't seeing any changes made to the tab selection, but I modified your suggestion slightly and took MadProgrammer's advice and added invokeLater...
tabbedPane.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
JTabbedPane pane = (JTabbedPane) e.getSource();
int selectedIndex = pane.getSelectedIndex();
if (selectedIndex == 0)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
textArea1.requestFocusInWindow(); }});
}
else if (selectedIndex == 1)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
textArea2.requestFocusInWindow(); }});
}
else if (selectedIndex == 2)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
textArea3.requestFocusInWindow(); }});
}
else if (selectedIndex == 3)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
textArea4.requestFocusInWindow(); }});
}
else if (selectedIndex == 4)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
textArea5.requestFocusInWindow(); }});
}
}
});
...and now all is right with the world again! Thanks to all.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.lang.Thread;
public class Lthread extends JPanel{
private JTextField acctTF;
private JTextField pinTF;
private JButton cancelB;
private JLabel balanceL;
private JButton searchB;
public Lthread() {
buildGUI();
hookupEvents();
}
private void buildGUI() {
JLabel acctL=new JLabel("Account Number:");
JLabel pinL=new JLabel("PIN:");
acctTF=new JTextField(12);
pinTF=new JTextField(4);
JPanel dataEntryP=new JPanel();
dataEntryP.setLayout(new FlowLayout(FlowLayout.CENTER));
dataEntryP.add(acctL);
dataEntryP.add(acctTF);
dataEntryP.add(pinL);
dataEntryP.add(pinTF);
searchB=new JButton("Search");
cancelB=new JButton("Cancel Search");
cancelB.setEnabled(false);
JPanel innerButtonP=new JPanel();
innerButtonP.setLayout(new GridLayout(1,-1,5,5));
innerButtonP.add(searchB);
innerButtonP.add(cancelB);
JPanel buttonP=new JPanel();
buttonP.setLayout(new FlowLayout(FlowLayout.CENTER));
buttonP.add(innerButtonP);
JLabel balancePrefixL=new JLabel("Account Balance: ");
balanceL=new JLabel("BALANCE UNKNOWN");
JPanel balanceP=new JPanel();
balanceP.setLayout(new FlowLayout(FlowLayout.CENTER));
balanceP.add(balanceL);
JPanel northP=new JPanel();
northP.setLayout(new GridLayout(-1,1,5,5));
northP.add(dataEntryP);
northP.add(buttonP);
northP.add(balanceP);
setLayout(new BorderLayout());
add(northP,BorderLayout.NORTH);
}
private void hookupEvents() {
searchB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
search(ae);
}
});
cancelB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
cancelSearch(ae);
}
});
}
private void search(ActionEvent ae) {
searchB.setEnabled(false);
cancelB.setEnabled(true);
balanceL.setText("SEARCHING...");
String acct=acctTF.getText();
String pin=pinTF.getText();
String bal=lookupBalance(acct,pin);
setBalance(bal);
}
private String lookupBalance(String acct,String pin) {
try {
Thread.sleep(5000);
return "1,234.56";
} catch(Exception exc) {
return "search cancelled";
}
}
private void setBalance(String newBalance) {
balanceL.setText(newBalance);
cancelB.setEnabled(false);
searchB.setEnabled(true);
}
private void cancelSearch(ActionEvent ae) {
System.out.println("in cancelSearch");
}
public static void main(String args[]) {
Lthread Lt=new Lthread();
JFrame f=new JFrame("Balance Lookup-Can't cancel");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setContentPane(Lt);
f.setSize(400,150);
f.setVisible(true);
}
}
As soon as we click search,cancel should get enabled,but it does not get enabled, why?
Another thing is label balanceL should display SEARCHING. When search is clicked but that is also not displayed. Why?
You're doing all the work (well, sleeping) in the UI thread. That's stopping all the rest of the UI from updating, processing events etc.
You should offload your work (the searching/sleeping) to a background thread, using SwingWorker to marshall any UI calls back to the UI thread. (You can't touch UI components from a non-UI thread.)
A search for "swing threads" gives loads of hits for tutorials etc, if you want more information. In particular, you probably want to read the Java Tutorial trail on threading in Swing.
because the eventdispatcher thread is blocked in the search() function. it should never be blocked. use SwingUtilities.invokeLater().
for your problem change search method to
private void search(ActionEvent ae) {
searchB.setEnabled(false);
cancelB.setEnabled(true);
balanceL.setText("SEARCHING...");
final String acct=acctTF.getText();
final String pin=pinTF.getText();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String bal=lookupBalance(acct,pin);
setBalance(bal);
}
});
}
Problem: Code does not turn the button back on (Example gives it 5 seconds for you to press)
Example code:
public static void main(String[] args) throws InterruptedException
{
Example call = new Example();
Thread.sleep(5000);
call.ButtonSwitch(1);
}
NOTE: this is the smallest coding i could make to show my problem
public class Example extends JFrame implements ActionListener {
static Example frame2 = new Example();
GridLayout experimentLayout = new GridLayout(0,1);
JPanel Game = new JPanel();
JButton button1 = new JButton("Press");
public Example()
{
Create();
}
public void Set()
{
setResizable(false);
}
public static void Create() {
/* Use an appropriate Look and Feel */
try {
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
} catch (UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (InstantiationException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
/* Turn off metal's use of bold fonts */
UIManager.put("swing.boldMetal", Boolean.FALSE);
//Schedule a job for the event dispatch thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI()
{
//Create and set up the window.
frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane.
frame2.addComponentsToPane(frame2.getContentPane());
//Display the window.
frame2.pack();
frame2.setVisible(true);
}
public void addComponentsToPane(final Container pane)
{
Game.setLayout(experimentLayout);
JPanel controls = new JPanel();
controls.setLayout(new GridLayout(2,3));
//Set up components preferred size
JButton b = new JButton("Just fake button");
Dimension buttonSize = b.getPreferredSize();
Game.setPreferredSize(new Dimension((int)(buttonSize.getWidth() * 2),
(int)(buttonSize.getHeight() * 1)* 4));
Game.add(button1);
button1.addActionListener(this);
//Process the Apply gaps button press
pane.add(Game, BorderLayout.NORTH);
pane.add(new JSeparator(), BorderLayout.CENTER);
pane.add(controls, BorderLayout.SOUTH);
}
//Turns button off On Click
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == button1)
{
button1.setEnabled(false);
}
}
//This does not turn the button on but tries to
public void ButtonSwitch(int num)
{
if (num == 1)
{
System.out.println("This is called");
button1.setEnabled(true);
}
}
}
I want to make the method Enable the button, but if this is not possible a way to do this in action listener without user input would be the second option (which would look like the Button switch method placed inside the ActionListener)
The problem comes from the bad design of the class. The point is that you are not calling setEnabled(true) and setEnabled(false) on the same button1. In your main,
Example call = new Example();
Thread.sleep(5000);
call.ButtonSwitch(1);
the last line invokes setEnabled(true) on the button of call, while the actionPerformed invokes the setEnabled(false) on the button of frame2.
Regardless, you are doing it wrong:
Don't mix the main (entry) thread with the EDT.
Don't hold a member of the same class type as the containing class (unless there's a special reason to do so).
Here is a real MCVE of a working code:
public class Example extends JFrame {
JButton button = new JButton("Press");
Timer timer = new Timer(5000, e -> button.setEnabled(true));
public Example() {
add(button);
button.addActionListener(e -> {
button.setEnabled(false);
timer.start();
});
timer.setRepeats(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args) throws InterruptedException {
SwingUtilities.invokeLater(() -> new Example());
}
}
Notes:
Method and non-final variable names start with a lowercase.
Don't use setPreferredSize, override getPreferredSize instead.