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.
Related
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.
By analogy with ToolTipManager setDismissDelay(int milliseconds) method, i would like to implement a dismiss delay for the rollover effect on a JButton.
In my swing application i have set different icons for my JButtons (setIcon, setPressedIcon and setRolloverIcon methods), but i'm trying to solve an issue occurring when a particular JButton, which should open a modal dialog, is pressed.
When the button is pressed and the modal dialog is shown, the jbutton still shows the Rollover icon, even if i passed the "normal" icon to setPressedIcon method.
Also, the rollover icon won't disappear until the cursor returns to main frame, also if the jdialog has been closed.
I made an example to show what i mean. I placed only two buttons into main frame, each button has a green square icon as "normal" icon, and a red icon for rollover effect.
As i sayed, i would like the buttons to show again the green icon when they are pressed. The first button will behave "wrongly", since the red icon is visible after the jdialog creation.
For the second button i solved this issue overriding isPressed () method (in its DefaultButtonModel), by calling setRollover (false) when the button is pressed.
I don't think this is the best solution, i would prefer not to act directly on ButtonModel.
So i would like to know if you have a better idea, maybe something similar to a setDismissDelay method, as i sayd before. Thanks !
Here there's an SSCE :
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SSCE
{
public static void main (String[] a) {
SwingUtilities.invokeLater (new Runnable () {
public void run () {
JFrame frame = new JFrame ("Icon Test");
frame.setContentPane (new MainPanel (frame));
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setResizable (false);
frame.pack ();
frame.setLocationRelativeTo (null);
frame.setVisible (true);
}
});
}
}
class MainPanel extends JPanel
{
public MainPanel (JFrame parent) {
JButton firstButton = createButton (createButtonImage (Color.GREEN), createButtonImage (Color.RED), parent);
JButton secondButton = createButton (createButtonImage (Color.GREEN), createButtonImage (Color.RED), parent);
secondButton.setModel (new DefaultButtonModel () {
#Override public boolean isPressed () {
boolean isPressed = super.isPressed ();
if (isPressed) setRollover (false);
return isPressed;
}
});
add (firstButton);
add (secondButton);
}
private JButton createButton (BufferedImage normalImage, BufferedImage rolloverImage, final JFrame parent) {
ImageIcon normalIcon = new ImageIcon (normalImage), rolloverIcon = new ImageIcon (rolloverImage);
JButton button = new JButton (new AbstractAction () {
public void actionPerformed (ActionEvent e) {
JDialog dialog = new JDialog (parent, "Test Dialog",true);
dialog.setSize (400, 400);
dialog.setLocationRelativeTo (parent);
dialog.setVisible (true);
}
});
button.setBorderPainted (false);
button.setCursor (Cursor.getPredefinedCursor (Cursor.HAND_CURSOR));
button.setFocusPainted (false);
button.setContentAreaFilled (false);
button.setIcon (normalIcon);
button.setPressedIcon (normalIcon);
button.setRolloverEnabled (true);
button.setRolloverIcon (rolloverIcon);
return button;
}
private BufferedImage createButtonImage (Color color) {
BufferedImage image = new BufferedImage (20, 20, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics ();
g.setColor (color);
g.fillRect (0, 0, 20, 20);
g.dispose ();
return image;
}
}
EDIT :
As #camickr suggested, i tried to wrap the ActionListener code in a SwingUtilities.invokeLater ().
I won't repost the full code, i have only replaced those lines :
JButton button = new JButton (new AbstractAction () {
public void actionPerformed (ActionEvent e) {
JDialog dialog = new JDialog (parent, "Test Dialog",true);
dialog.setSize (400, 400);
dialog.setLocationRelativeTo (parent);
dialog.setVisible (true);
}
});
with :
JButton button = new JButton ();
button.addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent e) {
SwingUtilities.invokeLater (new Runnable () {
public void run () {
JDialog dialog = new JDialog (parent, "Test Dialog",true);
dialog.setSize (400, 400);
dialog.setLocationRelativeTo (parent);
dialog.setVisible (true);
}
});
}
});
However, this doesn't solve my problem, the red icon is still visible when the dialog is created.
I tried some small adjustments, with addActionListener or setAction, also only calling setVisible into the invokeLater call, but it still doesn't work.
Also, how could i use a Timer without using the same code on ButtonModel which i am using now ?
I already tried some "hacks" by setting "normal icon" inside the actionPerformed and then invoking the other Action with a "custom" ActionEvent, but i would like to have a "clean" solution.
All code in a listener executes on the Event Dispatch Thread (EDT).
The problem is that the state of the button is not changed before the ActionListener code is invoked. Once the modal dialog is displayed, the button state change code isn't executed until the dialog is closed.
Wrap the code in the ActionListener in a SwingUtilities.invokeLater(). This code will be added to the end of the EDT allowing normal button processing to finish before the dialog is displayed.
Read the section from the Swing tutorial on Concurrency in Swing for more information about the EDT.
Edit:
i would prefer not to act directly on ButtonModel
Spend more time playing with the code. The problem is that there is no mouseExited that is generated when the dialog is displayed so the state of the ButtonModel is never updated.
Another option might be to manually generate a MouseEvent for the mouseExited event and dispatch the event to the button before the dialog is displayed.
Although this approach would also be considered a hack.
how could i use a Timer
Again, the problem is the state of the button. Even if you use a Timer you would manually need to reset the state of the model.
Your current solution seems reasonable since all the logic is located in a class that customizes the behaviour.
This is the scenario,
My JFrame has a button it will open a JDialog when click it and it is a model dialog.
JDialog has another button and i want to open another JFrmae open when click it.
Result : another Jframe open but it will not come to the top.It shows under the dialog.I want to open the 2nd JFrame on top of that dialog.
can use secondFrame.setAlwaysOnTop(true); but i don't have control to close it or move it.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
public class FrameTest
{
public static void main(String args[])
{
JFrame firstFrame = new JFrame("My 1st Frame");
JButton button = new JButton("Frame Click");
button.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
JDialog dialog = new JDialog();
dialog.setSize(100, 100);
dialog.setModal(true);
JButton button1 = new JButton("Dialog Click");
button1.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
JFrame secondFrame = new JFrame("My 2nd Frame");
secondFrame.setVisible(true);
secondFrame.setSize(400, 200);
secondFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
secondFrame.setAlwaysOnTop(true);
}
});
dialog.add(button1);
dialog.setVisible(true);
}
});
firstFrame.add(button);
firstFrame.setVisible(true);
firstFrame.setSize(400, 200);
firstFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
JDialog has another button and i want to open another JFrmae open when
click it.
Don't do that. A tipical Swing application has a single main JFrame and several JDialogs. See this topic The Use of Multiple JFrames, Good/Bad Practice?
Result : another Jframe open but it will not come to the top.It shows
under the dialog.I want to open the 2nd JFrame on top of that dialog.
Of course it does because the dialog is modal.
can use secondFrame.setAlwaysOnTop(true); but i don't have control to
close it or move it.
It won't solve anything because the problem has to do with modality in dialogs. See this article: How to Use Modality in Dialogs to understand how modality works. There's an explanation in this answer too.
Try
secondFrame.setModalExclusionType(ModalExclusionType.APPLICATION_EXCLUDE);
It worked for me in the same situation.
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.
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.