Why does JPopupMenu take 2 passes of UIThread before rendering? - java

The first pass appears to either invalidate the drawable area or draws the background. And the second pass renders the menu. If there is any delay (as the example below exaserbates) then you get a grey square flickering effect.
This is JDK8 on Linux.
How can I stop this flicker effect?
public class MenuTester {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setBounds(100, 100, 300, 200);
final JButton button = new JButton("Show Menu");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new JMenuItem("aaaa"));
popupMenu.add(new JMenuItem("bbbb"));
popupMenu.add(new JMenuItem("cccc"));
popupMenu.setLocation(100, 100);
popupMenu.setVisible(true);
try {
Thread.sleep(2000); // Leave enough time to clearly see the ?invalidated/background? area.
} catch (InterruptedException ex) {
// Nothing to do
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// Hide after 1 second
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
// Nothing to do
}
popupMenu.setVisible(false);
}
});
}
});
frame.add(button);
frame.setVisible(true);
}
}

Swing has, for as long as I remember it, had a "delay" when showing windows, this might have do with the time between the frame been realised by the OS and connection of the native message and event queues, but this is pure observation
I took you code and by simply by wrapping the frame's creation into a EventQueue.invokeLater was able to get a similar behaviour
You will get different results on different systems depending on there system up and configurations
What is the event that causes the window to be rendered in the first pass?
All I did was took your code and wrapped the creation the UI in an EventQueue.invokeLater, for example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MenuTester {
public static void main(String[] args) {
new MenuTester();
}
public MenuTester() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final JFrame frame = new JFrame();
frame.setBounds(100, 100, 300, 200);
final JButton button = new JButton("Show Menu");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new JMenuItem("aaaa"));
popupMenu.add(new JMenuItem("bbbb"));
popupMenu.add(new JMenuItem("cccc"));
popupMenu.setLocation(100, 100);
popupMenu.setVisible(true);
// try {
// Thread.sleep(2000); // Leave enough time to clearly see the ?invalidated/background? area.
// } catch (InterruptedException ex) {
// // Nothing to do
// }
// SwingUtilities.invokeLater(new Runnable() {
// #Override
// public void run() {
// // Hide after 1 second
// try {
// Thread.sleep(1000);
// } catch (InterruptedException ex) {
// // Nothing to do
// }
// popupMenu.setVisible(false);
// }
// });
}
});
frame.add(button);
frame.setVisible(true);
}
});
}
}
And is there a way to render the window as transparent during that first pass and opaque during the 2nd?
This is not a new problem, this has been the state of affair since I started with Swing at Java 1.3. What you're asking would mean you knew when the paint pass was done AND was complete. Swing isn't altogether stupid, it can make some clever decisions in order to optimise the rendering process (like not painting components which are visible)
The other problem is, with a JPopupMenu, you don't actually know if it's been displayed in a window or not (or just been displayed as a component on the glass pane for example) so the whole thing is woefully complicated

Related

Adding Menu before beginning of game

For some reason my jFrame no longer pops up after I add the menu. Is there something I am missing? I'm trying to make a menu that pops up before the beginning of the game and has buttons "play" as well as a text box that allows the user to input a username.
Any suggestions for how I could fix my code? Thank you!
this is my Menu class:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JPanel;
public class Menu extends JPanel{
private static final long serialVersionUID = 1L;
public Menu() {
JPanel buttonPanel = new JPanel(new GridLayout());
JButton play = new JButton();
JButton help = new JButton();
buttonPanel.add(play);
buttonPanel.add(help);
setFocusable(true);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Game.started = true;
}
});
}
public void paint (Graphics g) {
super.paint(g);
g.setColor(Color.black);
g.fillRect(400, 400, Game.WIDTH, Game.HEIGHT);
}
}
and this is my Main class from which I run my program:
import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Main implements Runnable{
public void run() {
final JFrame frame = new JFrame();
frame.setTitle("Flying Square");
frame.setSize(Game.WIDTH, Game.HEIGHT);
//The menu
final Menu menu = new Menu();
final Game game = new Game();
frame.add(menu, BorderLayout.CENTER);
menu.setVisible(true);
try {while (Game.started == false) {
Thread.sleep(10);
}} catch (InterruptedException e){
e.printStackTrace();
}
frame.remove(menu);
//Main playing area
frame.add(game, BorderLayout.CENTER);
game.setVisible(true);
frame.revalidate();
// Put the frame on the screen
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
// add listeners
frame.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
flyingObject.jump();
}
});
frame.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE)
{
flyingObject.jump();
}
}
});
// Start game
Game.reset();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}
}
So, this...
try {while (Game.started == false) {
Thread.sleep(10);
}} catch (InterruptedException e){
e.printStackTrace();
}
Is blocking the Event Dispatching Thread, preventing it from processing any events and basically causing your program to hang.
This is not how you want to process responses from the user. Your Menu should be monitoring for input from the user, probably through one or more ActionListeners, when an action is triggered, it should be notifying some kind of controller, the controller can then make decisions about what it needs to do, like switch the panels for example
You're going to want to break your code down into at least three chunks, the "game" the "menu" and the "controller", this way it will be easier to manage, rather than trying to retrofit the functionality into an existing code
It would recommend having a look at
How to Use CardLayout to help you facilite the switching of the view
How to Use Key Bindings instead of KeyListener
Model-View-Controller
Observer Pattern

GIF stops animating while gaming mouse is moving

While I move my gaming mouse inside a javax.swing.JFrame, all animated GIFs (javax.swing.ImageIcon inside a javax.swing.JLabel) stops animating until the mouse stops moving.
This only happens with a gaming mouse with a driver on macOS (tested it with a Rocket-Kone XTD and a Razer gaming mouse on two computers). When I use other mice everything works fine.
The gaming mice also causing javax.swing.Timers to stop calling their actionPerformed() methods. I opened a thread here for this problem, but this can be solved using java.util.TimerTask instead. (Edit: Actually TimerTask also don't fix it because the JFrame doesn't repaint until the mouse stops moving.)
But I found no alternative for animating GIFs. I'm more interested to solve the problem instead of using alternatives though I would be thankful for an working alternative too.
Code:
import java.lang.reflect.InvocationTargetException;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Mouse {
public static void main(String[] args) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
new Mouse();
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Mouse() {
JFrame frame = new JFrame();
JLabel label = new JLabel(new ImageIcon(getClass().getResource("waiting.gif")));
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(label);
}
}
Running application:
MCVE:
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import javax.swing.*;
public class Mouse {
public static void main(String[] args) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
try {
new Mouse();
} catch (MalformedURLException ex) {
ex.printStackTrace();
}
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Mouse() throws MalformedURLException {
JFrame frame = new JFrame();
JLabel label = new JLabel(new ImageIcon(
new URL("https://i.stack.imgur.com/HXCUV.gif")));
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(label);
}
}
I solved the problem as I reduced the polling-rate of my mouse from 1000Hz to 500Hz. Now everything works perfect. I think the problem was that the UI-Thread was overextended handling the 1000 polls per second so it was to busy to animate the GIF.

Loop Instead of Button for displaying images in JLabel? [duplicate]

in a java aplication I have a Jlabel which i want to assign a new image to every time i click a button, using a for loop i can get it to just display the last image skipping all in between images, i know there is a error in my logic here maybe i should not be using a for loop?? any advice
private String imageList[];
ImageIcon image;
imageList = new String[] {"src\\Tour_Eiffel_Wikimedia_Commons.jpg","src\\Ben.jpg", "src\\Rio.jpg", "src\\Liberty.jpg", "src\\Pyramid.jpg"};
//constructor setting first image to display on load
public GeographyGameGUI() {
image = new ImageIcon(imageList[0]);
imageLbl.setIcon(image);
}
//button method
private void nextBtnActionPerformed(java.awt.event.ActionEvent evt) {
for (imgCount = 1; imgCount < imageList.length; imgCount++) {
image = new ImageIcon(imageList[imgCount]);
imageLbl.setIcon(image);
}
if i dont use a for loop and simply use a counter (displayed below) which i declare outside of the button method it loops correctly displaying the images but runs into a ArrayIndexOutOfBoundsException. what is the best practice here? thanks
image = new ImageIcon(imageList[imgCount]);
imageLbl.setIcon(image);
imgCount++;
You're, essentially, blocking the Event Dispatching Thread, prevent it from updating the UI. See Concurrency in Swing for more details
Instead, you should use a javax.swing.Timer to loop over the images, allowing the UI to update before changing to the next one...
See How to use Swing Timers for more details.
Java arrays are zero indexed, this means that the first element in the array is a position 0, not 1
Don't reference src directly within your code, the src directory will not exist once the application is built and packaged
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label;
private String[] imageList = new String[] {"/Tour_Eiffel_Wikimedia_Commons.jpg","/Ben.jpg", "/Rio.jpg", "/Liberty.jpg", "/Pyramid.jpg"};
public TestPane() {
setLayout(new BorderLayout());
label = new JLabel();
add(label);
JButton btn = new JButton("Play");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
Timer timer = new Timer(1000, new ActionListener() {
private int count;
#Override
public void actionPerformed(ActionEvent e) {
if (count < imageList.length) {
try {
label.setIcon(
new ImageIcon(
ImageIO.read(
TestPane.this.getClass().getResource(imageList[count]))));
} catch (IOException exp) {
exp.printStackTrace();
}
count++;
} else {
((Timer)e.getSource()).stop();
}
}
});
timer.stop();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Your counter reaches the end of the array so you get out of bounds exception. After each increment you should check whether the end of array has been reached, and if so, set the counter to 0.
If you want to iterate over a few images with a delay on single click you need to use SwingWorker. Using delays in your action listener will suspend event dispatch thread, which means that no other updates or interactions with swing components will be available (it is likely that refreshes will not be done correctly too).
If you do a few updates (setIcon) in a very short time, Swing usually refreshes the component after the last of them, which means that only last image will be visible.
Have a look here: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

multiple dispatchEvent calls on a JButton

I have a customized JButton in a Java Swing application.
it changes it appearances according to mouse event.
MouseEvent.MOUSE_ENTERED - will trigger the hover image for the button. MouseEvent.MOUSE_PRESSED - will trigger the pressed image.
MouseEvent.MOUSE_RELEASED - will change the foreground to gray and render the button disabled.
This is working fine with actual mouse clicks.
I want to add a support for pressing the ENTER key.
simply calling button.doClick() did not go through the hover-press-release cycle, but simply jumped to the release event.
So I have this short and effective code for doing this.
InputMap im = workspacePnl.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = workspacePnl.getActionMap();
im.put(KeyStroke.getKeyStroke("ENTER"), "connect");
am.put("connect", new ConectAction());
private class ConectAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent ev) {
simulateClick();
}
and the problematic code:
public void simulateClick() {
MouseEvent evt;
evt = new MouseEvent(connectBtn,
MouseEvent.MOUSE_ENTERED, 1, 0, 0, 0, 1, false);
connectBtn.dispatchEvent((AWTEvent) evt);
//CommonUtil.sleep(300);
evt = new MouseEvent(connectBtn,
MouseEvent.MOUSE_PRESSED, 8, 0, 0, 0, 1, false);
connectBtn.dispatchEvent((AWTEvent) evt);
//CommonUtil.sleep(300);
evt = new MouseEvent(connectBtn,
MouseEvent.MOUSE_RELEASED, 20, 0, 0, 0, 1, false);
connectBtn.dispatchEvent((AWTEvent) evt);
}
I am trying to make the ENTER press go through the same route: trigger a MOUSE_ENTERED event which will alter the button's appearance for hover, followed by MOUSE_PRESSED and MOUSE_RELEASED.
But I only see the last event effect. it is as if i'm only firing the last event alone which lacks the liveliness for an interactive software.
I tried (as can be seen commented out) to have the thread go to sleep after each event firing, but it has no effect.
If I try to fire each of the other two events they are noticeable on the screen by themselves. it's the batching together that messes things up.
How can I fire a series of dispatchEvents one by one which will all be noticed by the user? how can i make the program wait for the current dispatchEvent to work it's magic before striding on to the next on?
Any help or insights would be greatly appreciated.
How can I fire a series of dispatchEvents one by one which will all be
noticed by the user? how can i make the program wait for the current
dispatchEvent to work it's magic before striding on to the next on?
Mouse and Key Event are correctly implemented in ButtonComponents, don't use MouseListener, to use events from ButtonModel, by using ChangeListener, for example
MouseEvent.MOUSE_ENTERED - will trigger the hover image for the button.
Make use of the roll over support supplied by the button, see JButton#setRolloverEnabled & JButton#setRolloverIcon
MouseEvent.MOUSE_PRESSED - will trigger the pressed image.
Is a little more difficult, but you can use listener to the ButtonModel for changes and update the icon based on you requirements
MouseEvent.MOUSE_RELEASED - will change the foreground to gray and render the button disabled.
Should probably be achieved through the use a ActionListener
I am trying to make the ENTER press go through the same route:
JButton#doClick will go through the isArmed and isPressed states of the model automatically, which will trigger the state changes provided by the previous comments...
With the mouse...
With the keyboard...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton();
try {
btn.setIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash01.png"))));
btn.setRolloverIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash02.png"))));
btn.setRolloverEnabled(true);
// btn.setSelectedIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash03.png"))));
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
if (btn.getModel().isPressed()) {
try {
btn.setIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash03.png"))));
} catch (IOException ex) {
ex.printStackTrace();
}
} else {
try {
btn.setIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash01.png"))));
} catch (IOException ex) {
ex.printStackTrace();
}
}
System.out.println("Armed: " + btn.getModel().isArmed());
System.out.println("Enabled: " + btn.getModel().isEnabled());
System.out.println("Pressed: " + btn.getModel().isPressed());
System.out.println("Rollover: " + btn.getModel().isRollover());
System.out.println("Selected: " + btn.getModel().isSelected());
}
});
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(btn, gbc);
add(new JTextField("Stealer of focus"), gbc);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
}
});
InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke("ENTER"), "connect");
am.put("connect", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ev) {
System.out.println("click");
btn.doClick();
}
});
}
}
}
It's batching together because your whole code is running on the EventDispatchingThread (EDT).
When you call simulateClick() from actionPerformed() call it on a new thread instead of the same thread (which will be EDT).

Hide a JFrame and display it again through a click on the dock icon

My application has a JFrame and checks every x seconds if something changed. So I would like to hide my JFrame via setVisible(false) on a click on the close button and redisplay it when the icon in the dock (I'm using Mac OS, but it should work the same way with the Windows task bar) is clicked. You know: many applications do this temporary hiding.
Have you got any ideas how to do this? How to listen on these click events?
Here is a little sample, how to hide/open window in the tray.
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class Test {
public static void main(String[] args) throws Exception {
final JFrame frm = new JFrame("Test");
Image im = Toolkit.getDefaultToolkit().getImage("c:\\icons\\icon1.png");
final TrayIcon tri = new TrayIcon(im);
tri.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
frm.setVisible(true);
try {
SystemTray.getSystemTray().remove(tri);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
frm.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
try {
SystemTray.getSystemTray().add(tri);
} catch (Exception ex) {
ex.printStackTrace();
}
frm.setVisible(false);
}
});
frm.setSize(100, 100);
frm.setVisible(true);
}
}
Use the com.apple.eawt or java.awt.Desktop packages to listen to Events that occur when the application is closed, hidden or reactivated.
Particularly com.apple.eawt.AppReOpenedEvent is cast when the Dock Icon is clicked. When you handle the event with com.apple.eawt.AppReOpenedListener, set the frame visible again:
#Override
public void appReOpened(AppReOpenedEvent arg0) {
invalidate(); // Suppose these are optional, but make sure the layout is up to date
pack();
validate();
setVisible(true);
}

Categories