How to set dismiss delay on JButton's rollover effect? - java

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.

Related

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

Hide a swing component without revalidating the layout?

If I set up a JFrame with some components and a layout manager, which initially looks perfectly fine, and then later due to some condition (say, clicking a button) I hide one of those components - the layout manager shuffles all the components around again.
See example code - initially 3 buttons appear. When you click the Hide button, the Hide button is hidden - but the two outer buttons then squash together. When you click the show button, they move apart again to make space. How can I stop that from happening, so that after I call pack (), components stay where they are no matter if they later become hidden?
In my real code I'm doing this with GridBagLayout, but used FlowLayout in the example below because its simpler and less code, and shows exactly the same behaviour.
I can only think of nasty ways of doing this, like using .setEnabled (false) instead of .setVisible (false), and then overriding the component's paintComponent () method to not draw the component when it is disabled.
It seems the exact opposite problem to here - Hide a button from Layout in Java Swing - where is complaining that hidden buttons do still take up space :) But there's no sample code there to show it working in that way.
Many thanks for any suggestions :)
Example:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RevalidateWhenSetChildInvisibleExample
{
private JButton button1;
private JButton button2;
private JButton button3;
public void run ()
{
// Set up action
Action hideButtonAction = new AbstractAction ()
{
#Override
public void actionPerformed (ActionEvent e)
{
button2.setVisible (false);
}
};
hideButtonAction.putValue (Action.NAME, "Hide");
Action showButtonAction = new AbstractAction ()
{
#Override
public void actionPerformed (ActionEvent e)
{
button2.setVisible (true);
}
};
showButtonAction.putValue (Action.NAME, "Show");
// Set up buttons
button1 = new JButton ("Dummy");
button2 = new JButton (hideButtonAction);
button3 = new JButton (showButtonAction);
// Set up content pane
JPanel contentPane = new JPanel ();
contentPane.setLayout (new FlowLayout ());
contentPane.add (button1);
contentPane.add (button2);
contentPane.add (button3);
// Set up frame
JFrame frame = new JFrame ();
frame.setContentPane (contentPane);
frame.pack ();
frame.setVisible (true);
}
public static void main (String args [])
{
SwingUtilities.invokeLater (new Runnable ()
{
public void run ()
{
new RevalidateWhenSetChildInvisibleExample ().run ();
}
});
}
}
You could use a CardLayout and then swap the button with an empty JPanel.
Read the section from the Swing tutorial on How to Use CardLayout for more information and examples.
The problem is the layout manager, which is not really a problem here because it is just doing its job. You could set the layout to null and then set the bounds for every button; this way they will NEVER move unless you change their position.
panel.setLayout(null);
button1.setBounds(10,10,50,20);
button2.setBounds(70,10,50,20);
button3.setBounds(xPos,yPos,width,height);
Another way is to use the GridLayout:
contentPane.setLayout(new GridLayout());
I tested it, and it worked fine, since the component did not get removed it stays the same.
Also, you should add the following to your code:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
This makes the program exits when the JFrame is closed; without it the program still runs at the background.

Text Box not working properly

I wrote a program for an applet that is supposed to display different text in a text box when you push a button. My program has no errors when I compiled it, but the text box doesn't display correctly. I don't know what is wrong with it. Here is my code
import java.awt.*;
import java.awt.event.*;
public class colors{
Button button1;
Button button2;
Button button3;
Label label1;
TextField objTextField1;
public static void main (String args[]){
colors c = new colors();
}
public colors() {
Frame f = new Frame ("Colors");
Button button1 = new Button("Blue");
button1.setBounds(10,305,120,75);
button1.addMouseListener(new MyMouseListener1());
Button button2 = new Button("Red");
button2.setBounds(140,305,120,75);
button2.addMouseListener(new MyMouseListener2());
Button button3 = new Button("Yellow");
button3.setBounds(270,305,120,75);
button3.addMouseListener(new MyMouseListener3());
f.add(button1);
f.add(button2);
f.add(button3);
label1 = new Label("Click a Button to Reveal Text");
label1.setBounds(20,105,200,25);
f.add(label1);
objTextField1 = new TextField("Which Color?", 15);
objTextField1.setBounds(20,75,125,50);
f.add(objTextField1);
f.add(label1);
f.add(objTextField1);
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we){
System.exit(0);
}
});
f.setSize(400,400);
f.setVisible(true);
}
public class MyMouseListener1 extends MouseAdapter{
public void mouseClicked(MouseEvent me){
objTextField1.setText("Blue");
}
}
public class MyMouseListener2 extends MouseAdapter{
public void mouseClicked(MouseEvent me){
objTextField1.setText("Red");
}
}
public class MyMouseListener3 extends MouseAdapter{
public void mouseClicked(MouseEvent me){
objTextField1.setText("Yellow");
}
}
}
When a Button is clicked it fires an ActionEvent.
You should use an ActionListener instead of a MouseListener.
public void actionPerformed(ActionEvent e) {
...//code that reacts to the action...
}
AND don't forget to add
button.addActionListener(instance);
I have tested your code and it's working as expected but I have noticed some of the points in your code as mentioned below:
Use setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) instead of System.exit(0) and adding WindowListener to close the window.
Use frame.pack() instead of frame.setSize() that fits the components as per component's preferred size.
Don't use null layout and never use absolute positioning via calling setBounds() instead use a proper Layout Manager that suits as per your application design.
Read more How to Use Various Layout Managers
Use SwingUtilities.invokeLater() or EventQueue.invokeLater() to make sure that EDT is initialized properly.
Read more
Why to use SwingUtilities.invokeLater in main method?
SwingUtilities.invokeLater
Should we use EventQueue.invokeLater for any GUI update in a Java desktop application?
Follow Java Naming convention
Keep the instance members private
This is already addressed by #TAsk that you should be using ActionListener instead of MouseListener if you are interested in only mouseClicked() method.
You should be using Swing components instead of AWT components as already mentioned by #peeskillet
To make an applet you should extend javax.swing.JApplet class & override init() method.
To change the color, you must write your logics in actionPerformed() of ActionListener. But it's an interface. So, you can make use of Anonymous Inner class & implement actionPerformed() in it.
So, when you call addActionListener() on a JButton, I recommend you to do that by using Anonymous Inner class. It would be more clear through following code.
My Suggestion: Whenever you write code, always keep OOD principles in your mind. This isn't right place to discuss that, but your code has a Code smell which is Duplication in code.
Below is the best way to do what you want & we're also using DRY Principle.
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
public class ColorChanger extends javax.swing.JApplet {
private JPanel mainPanel;
private JButton btnRed;
private JButton btnGreen;
private JButton btnBlue;
#Override
public void init() {
super.init();
mainPanel = new JPanel();
btnRed = new JButton("Red");
btnGreen = new JButton("Green");
btnBlue = new JButton("Blue");
this.add(mainPanel);
mainPanel.add(btnRed);
mainPanel.add(btnGreen);
mainPanel.add(btnBlue);
bindActionEvent(btnRed, Color.RED);
bindActionEvent(btnGreen, Color.GREEN);
bindActionEvent(btnBlue, Color.BLUE);
}
private void bindActionEvent(JButton b1, Color color) {
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
mainPanel.setBackground(color);
//Write setText() for your TextField here.
}
});
} //END Of Helper Method
}

Open JFrame from JDialog and it shows on top of JDialog

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.

How to move 3 buttons inside a window

In the below code I am attempting to move the three buttons to the left when you click the left button. When I click it; nothing happens currently. Can anyone explain to me what I am doing wrong here? Also, for some reason it has stopped compiling correctly and I am unsure why but I BELIEVE it is because of a mistake in my code while attempting to get the buttons to move to the left when you click the button. I do NOT want the window to move. Just the buttons within the window. Does any one see what I am doing wrong and can you explain it?
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Buttons extends JFrame {
//Control Definitions
JButton resetButton;
JButton leftButton;
JButton colorButton;
JPanel buttonPanel;
// Layout Definiton
eventHandle evt;
FlowLayout flt;
Point point; //to Hold Previous Window Position
Color color; //to Hold Previous Color
public Buttons() {
super("Buttons Window");
flt = new FlowLayout();//inialize the Flow Layout
buttonPanel = new JPanel(flt);
//inialize the buttonPanel With Flow Layout
//initialize buttons
resetButton = new JButton("Reset");
leftButton = new JButton("Left");
colorButton = new JButton("Blue");
evt = new eventHandle(); //initiate the eventhandle class
buttonPanel.add(leftButton); //add leftButton
buttonPanel.add(colorButton);//add colorButton
buttonPanel.add(resetButton);//add colorButton
getContentPane().add(buttonPanel);//buttonPanel
//add actionlistners
leftButton.addActionListener(evt);
colorButton.addActionListener(evt);
resetButton.addActionListener(evt);
setBounds(20, 120, 250, 70);
//following Initate the point with Center of Scren
point = new Point((Toolkit.getDefaultToolkit().
getScreenSize().width - getWidth()) / 2,
(Toolkit.getDefaultToolkit().getScreenSize().height
- getHeight()) / 2);
setLocation(point); //locates the window in center
color = buttonPanel.getBackground();//stores the initial color
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
class eventHandle implements ActionListener { //Event Handler
public void actionPerformed(ActionEvent e) {
{
if (e.getSource() == leftButton) ///if its from leftButton
{
leftButton.setAlignmentX(Component.LEFT_ALIGNMENT);
colorButton.setAlignmentX(Component.LEFT_ALIGNMENT);
resetButton.setAlignmentX(Component.LEFT_ALIGNMENT);
//setLocation( (point.x -150), point.y);//shift the window 150 pixels left
} else if (e.getSource() == colorButton) {
buttonPanel.setBackground(color.BLUE);
//sets the backgorund to Blue
} else {
leftButton.setAlignmentX(Component.CENTER_ALIGNMENT);
//sets the location to previous location
colorButton.setAlignmentX(Component.CENTER_ALIGNMENT);
resetButton.setAlignmentX(Component.CENTER_ALIGNMENT);
}
}
}
}
public static void main(String[] args) {
Buttons buttonwindow = new Buttons();
}
}
It has stopped compiling, because you deleted one accolade, so put one accolade "}" just above the method:
public static void main(String[] args)
and the code should compile. pls feedback.
EDIT:
Also rewrite your main method like this:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Buttons buttonwindow = new Buttons();
}
}
);
}
Every usage of Swing components must be done thorugh the Event Dispatch Thread (abbreviated EDT) or you will probably get unwanted visual effects. See here for explanation.
EDIT^2:
To achieve the desired behavior, rewrite the the action listener like this:
if (e.getSource() == leftButton) {
((FlowLayout)buttonPanel.getLayout()).setAlignment(FlowLayout.LEFT); //1
buttonPanel.revalidate(); //2
}
else if (e.getSource() == colorButton) {
buttonPanel.setBackground(color.BLUE);
}
else {
((FlowLayout)buttonPanel.getLayout()).setAlignment(FlowLayout.CENTER);
buttonPanel.revalidate();
}
Any change to the visual appereance to the Swing component must be done through the assigned layout manager, in this case FlowLayout - in line 1.
To see the change you must notify the Swing components layout manager to rearrange the components - in line 2 the revalidate() method "notifies" the layout manager to recalculate the new positions and eventually "notifies" the EDT to draw it on the screen.
You should update the layout manager to align the components to the left or right. Try something like;
((FlowLayout)getLayout()).setAlignment(FlowLayout.LEFT);
Instead
You code won't compile as the static main method appears inside the inner class eventHandle. You can fix simply by moving it into the class body of the outer class Buttons.
As you have all the objects references at class level, you could do the button alignment using, for instance:
flt.setAlignment(FlowLayout.RIGHT);
buttonPanel.revalidate();
...
Here you are adjusting the layout alignment of your FlowLayout and revalidating to visually reflect the updated changes on your panel.

Categories