How can I save JComboBox edits when window focus is lost? - java

I have a Java Swing application where some data are presented in editable combo boxes. The combo boxes are displayed in a separate frame. The frame is opened ad hoc when a button is clicked. The frame has no window decoration and is closed/disposed when it loses its focus (i.e., the user clicks outside the window). When the frame is closed, the combo box contents are saved.
This works well, except for the last edited combo box. For the last combo box, the contents are still being edited when the window loses focus. The #getSelectedItem() method of the JComboBox returns null because the editing was not completed before the window lost focus. At least I assume that is what is happening.
How can I finish the editing and select the edited text when the window loses focus before disposing the frame?
Here is a minimal reproducible example:
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
class Example extends JFrame {
public static void main(String[] args) {
new Example();
}
public Example() {
Container c = getContentPane();
JPanel panel = new JPanel();
JComboBox<String> box1 = new JComboBox<String>();
box1.setEditable(true);
panel.add(box1);
JComboBox<String> box2 = new JComboBox<String>();
box2.setEditable(true);
panel.add(box2);
c.add(panel);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
this.addWindowFocusListener(new WindowAdapter() {
public void windowLostFocus(WindowEvent e) {
System.out.println("Field 1: " + box1.getSelectedItem() + ". Field 2: " + box2.getSelectedItem() + ".");
System.exit(0);
}
});
}
}
The example class will display a frame with two editable combo boxes. If you click outside of the frame, the application will exit after printing the contents of the two combo boxes. You will notice that the last edited combo box prints null if it was visited only once.

Add a FocusListener to the combo box text field that is used as the editor. An event should be generated when the text field loses focus.
See the getEditor() method of the JComboBox for access to the editor component.

Related

How to edit a JComboBox with text selected in JEditorPane

I have a UI with two components - a JEditorPane and a JComboBox. My goal is to be able to type something into the JEditorPane, select a portion of the text, and while it is still selected type and/or select a value in an editable JComboBox.
This is for a text editor type of program where I want to change the font size of just the selected text in the editor pane. Where the font size is coming from the editable combo box. To clarify, I'm not asking how to apply styles to the text, I'm asking how to select a value in the combo box without losing the focus/selection in the JEditorPane.
Here's the code for the UI, but I wasn't sure where to begin doing anything with the focus...
public static void main(String [] args)
{
JFrame frame = new JFrame();
JPanel contentPane = new JPanel();
JComboBox<String> combo = new JComboBox(new String [] {"Hello", "World"});
contentPane.add(combo);
JEditorPane editor = new JEditorPane();
contentPane.add(editor);
frame.setContentPane(contentPane);
frame.pack();
frame.setVisible(true);
}
I'm asking how to select a value in the combo box without losing the focus/selection in the JEditorPane.
You don't lose the selection of the text in the editor pane when you select an item from the combo box. The selection remains, but it is just not painted until the editor pane regains focus.
So the easiest way to do this is to use a JMenuItem. Read the section from the Swing tutorial on Text Component Features for an example that does this.
If you still want to use the combo box then you can add Integer values to the combo box then the code in your ActionListener for the combo box would look something like:
#Override
public void actionPerformed(ActionEvent e)
{
Integer value = (Integer)comboBox.getSelectedItem();
Action action = new StyledEditorKit.FontSizeAction("Font size", value);
action.actionPerformed(null);
}
The StyledEditorKit actions extend from TextAction. The TextAction knows the last text component that had focus and therefore the font change is applied to that text component.
If you really want the text field to show the selection then you need to create a custom Caret and override the focusLost method to NOT invoke setSelectionVisible(false) (which is the default behaviour.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class DefaultCaretTest extends JFrame
{
public DefaultCaretTest()
{
JTextField textField1 = new JTextField("Text Field1 ");
JTextField textField2 = new JTextField("Text Field2 ");
textField1.setCaret(new SelectionCaret());
textField2.setCaret(new SelectionCaret());
textField1.select(5, 11);
textField2.select(5, 11);
((DefaultCaret)textField2.getCaret()).setSelectionVisible(true);
add(textField1, BorderLayout.WEST);
add(textField2, BorderLayout.EAST);
}
static class SelectionCaret extends DefaultCaret
{
public SelectionCaret()
{
setBlinkRate( UIManager.getInt("TextField.caretBlinkRate") );
}
public void focusGained(FocusEvent e)
{
setVisible(true);
setSelectionVisible(true);
}
public void focusLost(FocusEvent e)
{
setVisible(false);
}
}
public static void main(String[] args)
{
DefaultCaretTest frame = new DefaultCaretTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
Of course the selection will remain when focus is on any other component, not just the combo box.
You can also use:
comboBox.setFocusable(false);
Since the combo box can't gain focus the focus will remain on the text component, but the problem with this is that the user won't be able to use the keyboard to select a font size from the combo box. A proper GUI design always allows the user to use either the keyboard or the mouse to perform an action.

"Un-rollover" a JButton when a JOptionPane is displayed

I have a situation where I need to display a JOptionPane after clicking on a JButton. The JButton has a default icon, and a rollover icon (which displays when, well, the mouse rolls-over the button). However, once the button is clicked and a JOptionPane appears, the rollover icon does not change back to the original, and continues to remain so until the user brings the mouse back to the JButton's frame after selecting an appropriate JOptionPane choice. How would I "un-rollover" the JButton when it is clicked and the JOptionPane is displayed?
TL;DR: JButton displays rollover icon even when being clicked and JOptionPanel is displayed. Me no likey.
Here's the SSCCE:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class ButtonUnrollover {
public static void main(String[] args) {
JFrame f = new JFrame();
final JPanel p = new JPanel();
JButton b = new JButton();
b.setIcon(UIManager.getIcon("OptionPane.informationIcon"));
b.setRolloverIcon(UIManager.getIcon("OptionPane.errorIcon"));
// b.setSelectedIcon(UIManager.getIcon("OptionPane.informationIcon"));
// b.setRolloverSelectedIcon(UIManager.getIcon("OptionPane.informationIcon"));
// b.setPressedIcon(UIManager.getIcon("OptionPane.informationIcon"));
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane jOP = new JOptionPane("Dummy message");
JDialog dialog = jOP.createDialog(p, null);
dialog.setVisible(true);
}
});
f.add(p);
f.pack();
f.setVisible(true);
}
}
NB: I have found several similar questions to this one. However, this question is not a duplicate because those questions pertain to an issue slightly different from this one (such as the button staying pressed, not rolled-over). A few of these questions (well, actually all of them I could find) are:
JButton stays pressed when focus stolen by JOptionPane
JButton stays pressed after a JOptionPane is displayed
JButton “stay pressed” after click in Java Applet
The rollover state is managed by the ButtonModel. You can reset the rollover flag via the model's setRollover(boolean b) method, which will set the Icon back to the non-rollover state Icon. Implemented in your example ActionListener:
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
b.getModel().setRollover(false);//reset the rollover flag
JOptionPane jOP = new JOptionPane("Dummy message");
JDialog dialog = jOP.createDialog(p, null);
dialog.setVisible(true);
}
});
You might also wish to check if the Mouse is still located over the JButton after the dialog is closed to reset the rollover flag (if necessary) - you can do so via MouseInfo, checking if the JButton contains the point by converting the Screen coordinates retrieved from MouseInfo.getPointerInfo().getLocation() to component coordinates using SwingUtilities.convertPointFromScreen.
If you can live with your dialog box not being modal, add
dialog.setModal(false);
to your action listener block.

JTextArea editable or not editable depending on how it's called

Total re-edit with compilable example that clarifies my issue.
Overall program: Class MainFrame displays a JTable with results from a SQL query. MainFrame also has JButtons for refreshing, adding, updating, and querying the table. Clicking the Update button makes visible a text area and submit button. Users can enter an id number into the text area. When they click submit a new frame, UpdateFrame, opens with all the data from the record the corresponds to the id number.
Stripped-down versions of MainFrame and UpdateFrame are below.
UpdateFrame2.java
package kft1task4;
import javax.swing.*;
import java.awt.event.*;
import java.sql.SQLException;
import javax.swing.JScrollPane;
public class UpdateFrame2 extends JFrame implements ActionListener {
JPanel pane = new JPanel();
JTextArea jta = new JTextArea("This is a text area");
UpdateFrame2() {
setVisible(true);
setBounds(1000,400,1000,500);
pane.setLayout(null);
add(pane);
jta.setBounds(110,100,100,15);
pane.add(jta);
}
#Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
} //End actionListener
} //End class
Very simple. One frame with one panel with one JTextArea. The JTextArea should be editable; I should be able to type in it.
MainFrame2.java
package kft1task4;
import java.awt.event.*;
import java.sql.SQLException;
import javax.swing.*;
public class MainFrame2 extends JFrame implements ActionListener {
JPanel pane = new JPanel();
JButton closeButt = new JButton("Push me to close the program");
JButton updateButt = new JButton("Push me to update a record");
JButton submitUpdButt = new JButton("Submit");
JLabel updateLabel = new JLabel("Select student id to update");
JTextArea updateTA = new JTextArea();
MainFrame2(){
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(1000,200,1500,1000);
pane.setLayout(null);
add(pane);
updateButt.setBounds(620,550,200,100);
updateButt.addActionListener(this);
pane.add(updateButt);
closeButt.setBounds(1290,550,200,100);
closeButt.addActionListener(this);
pane.add(closeButt);
submitUpdButt.setBounds(820,735,200,25);
submitUpdButt.addActionListener(this);
submitUpdButt.setVisible(false);
pane.add(submitUpdButt);
updateLabel.setBounds(620,700,200,15);
updateLabel.setVisible(false);
pane.add(updateLabel);
updateTA.setBounds(820,700,200,15);
updateTA.setVisible(false);
pane.add(updateTA);
}
#Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == closeButt){
System.exit(0);
}
if(source == updateButt){
updateLabel.setVisible(true);
updateTA.setVisible(true);
submitUpdButt.setVisible(true);
}
if(source == submitUpdButt){
//submitUpdButt.setVisible(false);
new UpdateFrame2();
updateTA.setText(null);
updateTA.setVisible(false);
updateLabel.setVisible(false);
submitUpdButt.setVisible(false);
}
}
}
Note the three fields: updateLabel, updateTA, and submitUpdButt (and please forgive the poor naming). When new MainFrame2() is first instantiated, those three fields are .setVisible(False). Clicking updateButt makes them visible.
Click submitUpdButt performs five actions: First, it instantiates a new UpdateFrame2(). Second, it clears the text from UpdateTA. Finally, it makes the three fields invisible. Those 5 actions complete with no problem.
Now here's the oddity: Notice that I've listed "submitUpdButt.setVisible(false)" twice. Once before "new UpdateFrame2()" and once after. I comment out one and leave other in place. If "submitUpdButt.setVisible(false)" appears before "new UpdateFrame2()," the UpdateFrame appears, and its text area is editable.
If "submitUpdButt.setVisible(false)" appears after "new UpdateFrame2()," as it's written above, the UpdateFrame appears. But its text area is not editable.
To clarify: Every other element of the program behaves exactly the same. The 3 fields appear and disappear as expected. The window open and close correctly. The text "This is a text area" appears where it should. No errors are produced. But the text area in UpdateFrame2 is editable or not based on where I put "submitUpdButt.setVisible(false)".
I hope this description is more clear than my last one.

What text input component last had the focus?

Suppose I have a Java application that has more than one component in which you can enter text. Now suppose this application also has a dialog that lets you insert a single character (like the dialog in Word that comes up when you select Insert from the Edit menu) into those components. You want it to insert the character into whichever text component last had the focus.
But how do you know which text component last had the focus?
I could keep track of this manually, by having each text component report to the application whenever it gets the focus and then have the application insert the new character into whichever component that last had the focus.
But this must be a common problem (consider Paste buttons in tool bars---how does it know where to paste it into?). Is there something already built in to Swing that lets you get a handle to the last text component that had the focus? Or do I need to write this myself?
Is there something already built in to Swing that lets you get a handle to the last text component that had the focus?
You create an Action that extends TextAction. The TextAction class has a method that allows you to obtain the last text component that had focus.
Edit:
You can create your own Action and do whatever you want. The Action can then be added to any JMenuItem or JButton. For example:
class SelectAll extends TextAction
{
public SelectAll()
{
super("Select All");
}
public void actionPerformed(ActionEvent e)
{
JTextComponent component = getFocusedComponent();
component.selectAll();
}
}
If you just want to insert a character at the caret position of the text field then you can probably just do
component.replaceSelection(...);
Edit 2:
I don't understand what the confusion is with this answer. Here is a simple example:
select some text
use the mouse to click on the check box
tab or use the mouse to click on the "Cut" button
It doesn't matter that the text field doesn't currently have focus when the Action is invoked. The TextAction tracks the last text component that had focus.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class TextActionTest extends JFrame
{
JTextField textField = new JTextField("Select Me");
JTabbedPane tabbedPane;
public TextActionTest()
{
add(textField, BorderLayout.NORTH);
add(new JCheckBox("Click Me!"));
add(new JButton(new CutAction()), BorderLayout.SOUTH);
}
public static void main(String[] args)
{
TextActionTest frame = new TextActionTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
class CutAction extends TextAction
{
public CutAction()
{
super("Click to Cut Text");
}
public void actionPerformed(ActionEvent e)
{
JTextComponent component = getFocusedComponent();
// JTextComponent component = getTextComponent(e);
component.cut();
}
}
}
Just like suggested by #lesmana (+1 for that).
Here you have an example that shows that on focusLost the focus listener returns the previously focused component.
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Focusing
{
public static void main(String[] args)
{
JPanel p = new JPanel();
JTextField tf1 = new JTextField(6);
tf1.setName("tf1");
p.add(tf1);
JTextField tf2 = new JTextField(6);
tf2.setName("tf2");
p.add(tf2);
FocusListener fl = new FocusListener()
{
#Override
public void focusGained(FocusEvent e)
{
System.out.println("focusGained e.getSource().c=" + ((JComponent) e.getSource()).getName());
}
#Override
public void focusLost(FocusEvent e)
{
System.out.println("focusLost e.getSource().c=" + ((JComponent) e.getSource()).getName());
}
};
tf1.addFocusListener(fl);
tf2.addFocusListener(fl);
JPanel contentPane = new JPanel();
contentPane.add(p);
JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
All the best, Boro.
I've never done this directly, but you could look into the FocusEvents and the Focus Subsystem.
Hopefully there is something in the Focus Subsystem that would fire events that you could listen for.
You can register a FocusListener to every text component. The FocusEvent object has a reference to the last component which had focus.

java detect clicked buttons

I have multiple panels on a JFrame window. I am going to populate each panel differently every time. For example:
i start the GUI: (image center panel, right panel, bottom panel). Center panel is populated with 20 buttons, right panel with 10 buttons and bottom panel with 3.
second start of the GUI (same gui). Center panel has 50 buttons, right panel has 12 buttons, bottom has 3.
So everytime there is a random number of buttons, impossible to be all uniquely named.
Given the fact that I don't have a unique name for each button (just a list) I would like to know which buttons were clicked according to the panel they belong to. is that possible?
Somehow the buttons are being created; Let's assume you are somehow numbering them in a way you can retrieve later.
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.ArrayList;
import javax.swing.JButton;
public class ButtonTest extends JFrame implements ActionListener {
public ButtonTest() {
super();
initGUI();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private final List<JButton> buttons = new ArrayList<JButton>();
private static final int NUM_BUTTONS = 20;
public void initGUI() {
JPanel panel = new JPanel();
for (int i = 0; i < NUM_BUTTONS; i++) {
String label = "Button " + i;
JButton button = new JButton(label);
button.setActionCommand(label);
button.addActionListener(this);
buttons.add(button);
panel.add(button);
}
getContentPane().add(panel);
}
public static void main(String[] args) {
new ButtonTest();
}
public void actionPerformed(ActionEvent e) {
String actionCommand = ((JButton) e.getSource()).getActionCommand();
System.out.println("Action command for pressed button: " + actionCommand);
// Use the action command to determine which button was pressed
}
}
The ActionEvent has a getSource() method which will be the reference to the button that was clicked. You can then check the action command of the button if you need to.
If you want to know which panel contains the button, try calling getParent() on the JButton itself. To find out which button was clicked, as camickr suggests, use getSource() on the ActionEvent.

Categories