I have a Java swing application that creates and displays a tabbedpane and creates an update thread. The update thread when triggered needs to add a set of tabs with content but I am getting an exception now and again "Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException".
Is this something to do with adding tabs in a different thread? If so how do I add a tab to it in a thread safe way please??
Here is an example application that illustrates the problem
public class Example extends JFrame implements Runnable {
private final JTabbedPane tabbedPane;
private final Rectangle bounds;
public Example() {
super("Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
bounds = new Rectangle(0, 0, 500, 500);
setBounds(bounds);
setLayout(null);
tabbedPane = new JTabbedPane();
tabbedPane.setBounds(bounds);
JPanel jp = new JPanel();
jp.setLayout(null);
jp.setBounds(bounds);
jp.add(tabbedPane);
add(jp);
new Thread(this).start();
}
#Override
public void run() {
while (true) {
for (int i = 0; i < 3; i++) {
tabbedPane.addTab("NEW" + i, new JPanel());
repaint();
}
try {
Thread.sleep(50);
} catch (InterruptedException ex) {
Logger.getLogger(Example.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public static void main(String[] args) {
Example e = new Example();
e.setVisible(true);
}
}
Swing is not thread safe...
This means that you should never try to create or modify the UI from outside the context of the Event Dispatching Thread.
The likely issue is you've run into some kind of race condition. Take a look at Concurrency in Swing and How to use Swing Timers for more details
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.Timer;
public class Example extends JFrame {
private final JTabbedPane tabbedPane;
private final Rectangle bounds;
public Example() {
super("Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
bounds = new Rectangle(0, 0, 500, 500);
setBounds(bounds);
// setLayout(null);
tabbedPane = new JTabbedPane();
tabbedPane.setBounds(bounds);
// JPanel jp = new JPanel();
// jp.setLayout(null);
// jp.setBounds(bounds);
// jp.add(tabbedPane);
// add(jp);
add(tabbedPane);
Timer timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 3; i++) {
tabbedPane.addTab("NEW" + tabbedPane.getTabCount(), new JPanel());
}
tabbedPane.revalidate();
}
});
timer.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Example e = new Example();
e.setVisible(true);
}
});
}
}
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
Take a look at Laying Out Components Within a Container for more details
Related
I created swing application that do some operations in performedAction methode, but when i create a thread that shows the progressBar, the progressBar will not visible during the performedAction methode, but at the end the progressBar will be visible with a value of 100% directly
main class:
import javax.swing.*;
import javax.swing.plaf.ButtonUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class main {
public static void main(String[] args) {
JFrame win = new JFrame("Test");
FlowLayout layout = new FlowLayout();
Button b1 = new Button("Click ");
win.add(b1);
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
window win = new window();
win.start();
try
{
Thread.sleep(2000);
}
catch(InterruptedException e5){}
}
});
win.setLayout(layout);
win.setSize(500, 300);
win.setLocationRelativeTo(null);
win.setVisible(true);
win.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
window class:
import javax.swing.*;
import java.awt.*;
public class window extends Thread{
public window(){
}
#Override
public void run() {
JFrame fen = new JFrame("New window");
FlowLayout layout = new FlowLayout();
fen.setLayout(layout);
Button b2 = new Button();
fen.setVisible(true);
fen.setSize(100, 100);
fen.setLocationRelativeTo(null);
fen.add(b2);
try
{
for(int i = 0; i <= 100; i++)
{
b2.setLabel("Button " + i);
Thread.sleep(10);
}
}
catch(InterruptedException e2){
}
}
}
Yes, it is safe to create a new Thread inside of an actionPerformed handler.
But, no thread other than the Event Dispatching Thread (EDT) must interact with the Swing components.
To create animations, or delays inside of Swing, you must use a javax.swing.Timer. Executing a Thread.sleep() on the EDT is never allowed, and won't do want you hope it will.
To return from a spawned thread to the EDT, you must use SwingUtilities.invokeLater() or SwingUtilities.invokeAndWait().
Using a SwingWorker is the preferred way to execute background tasks in Swing. It handles communication and publication of partial and final results from the background task to the EDT for displaying in the GUI components.
The following is a translation of your code to a working example, using a SwingWorker. Instead of AWT Button's, the Swing JButton is used. The program is created using invokeAndWait to ensure the main window construction occurs on the EDT. A lambda function is used, but you can replace this with new Runnable() { } inner class, if desired. The "Button 0" through "Button 100" progress reports are published from the SwingWorker background task, for processing in the EDT. Multiple results can be generated at by the background thread before the EDT has a chance to process them; here, we take only the last result to display in the button.
public class Main {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeAndWait(() -> {
JFrame win = new JFrame("Test");
win.setLayout(new FlowLayout());
JButton b1 = new JButton("Click");
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
DoWork work = new DoWork();
work.execute();
}
});
win.add(b1);
win.setSize(500, 300);
win.setLocationRelativeTo(null);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setVisible(true);
});
}
}
class DoWork extends SwingWorker<Void, String> {
JFrame fen;
JButton b2;
DoWork() {
fen = new JFrame("New window");
fen.setLayout(new FlowLayout());
b2 = new JButton();
fen.add(b2);
fen.setSize(100, 100);
fen.setLocationRelativeTo(null);
fen.setVisible(true);
fen.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i <= 100; i++) {
publish("Button " + i);
Thread.sleep(10);
}
return null;
}
#Override
protected void process(List<String> data) {
String last = data.get(data.size() - 1);
b2.setText(last);
}
#Override
protected void done() {
fen.dispose();
}
}
There are still a number of improvements that can be made to this code. A JLabel instead of a JButton for displaying the results, a JDialog for the progress window, or perhaps better a ProgressMonitor. These are left as an exercise to the student.
I am writing a GUI Builder and want the user to be able to change the LookAndFeel of the GUI he builds. The LookAndFeel should only be changed for the Components inside the editor area. The rest of the Application should remain with the SystemLookAndFeel.
The great problem is, that the LookAndFeel is implemented as a Singleton and changing the LookAndFeel multiple times during the Application causes a lot of bugs.
I started experimenting with Buttons:
I tried setting the ButtonUI to MetalButtonUI, but they didn't render properly. So I debugged the default paintComponent method of JButton and saw that the ButtonUI still needed the UIDefaults, which were not complete since they were the WindowsUIDefaults.
My current solution is to set the MetalLookAndFeel, save the UIDefaults, then change the LookAndFeel to SystemLookAndFeel and save those UIDefaults aswell and everytime I draw a Button inside the editor I swap the UIDefaults.
Here is the Code:
public class MainClass{
public static Hashtable systemUI;
public static Hashtable metalUI;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
metalUI = new Hashtable();
metalUI.putAll(UIManager.getDefaults());
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
systemUI = new Hashtable();
systemUI.putAll(UIManager.getDefaults());
} catch (Exception e) {
e.printStackTrace();
}
/* ...
* Generate JFrame and other stuff
* ...
*/
}
});
}
}
public class MyButton extends JButton {
public MyButton(String text) {
super(text);
ui = MetalButtonUI.createUI(this);
}
#Override public synchronized void paintComponent(Graphics g) {
UIManager.getDefaults().putAll(Application.metalUI);
super.paintComponent(g);
UIManager.getDefaults().putAll(Application.systemUI);
}
}
As you can see here the result is pretty good. On the left is the MetalLaF as it should look and on the right, how it gets rendered in my application. The gradient is painted correctly, but the Border and the Font aren't.
So I need to know why not all elements of the LaF are beeing applied to the Button and how to fix that.
-
Edit:
I found an ugly solution. The LookAndFeel has to be changed before Button creation, because the Graphics object will be created in the Constructor. After the super constructor was called you can change the LookAndFeel back.
Next you need to change the LookAndFeel before the Component is painted/repainted. The only point I got it working was in paintComponent before super is called. You can change it back after super is called.
Code:
import javax.swing.*;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
public class MainClass {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(f.getExtendedState() | JFrame.EXIT_ON_CLOSE);
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
}
f.setLayout(new FlowLayout());
f.add(new MyButton("MetalButton"));
f.add(new JButton("SystemButton"));
f.pack();
f.setVisible(true);
}
});
}
}
class MyButton extends JButton {
public MyButton(String text) {
super(text);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
ui = MetalButtonUI.createUI(this);
}
#Override public synchronized void paintComponent(Graphics g) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
super.paintComponent(g);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
}
}
Edit 2:
Do not use this unless absolutely necessery!
This is extremely unstable. After a lot of testing I found it less buggy when I just swapped the UIDefaults instead of the whole LookAndFeel, but I do not recommend doing any of those.
Edit 3:
The best solution I found was using JavaFX as a GUI. I inserted a swing node into the Editor area and now can modify the Look and Feel of the swing components as often as I want without any noticeable side effects.
Rant:
If you can always choose JavaFX if you want to modify the style of your application. CSS makes it as easy as possible without any side effects ever!
Much Thanks
Jhonny
Disclarimer
Swing's Look And Feel isn't designed to be switched after it's first initalised, it's actually a kind of fluky side effect that it's possible. Some look and feels and some components might not like you doing this and may not behave as they might other wise under normal conditions.
Possible solution
For the love of sanity, DON'T change the UI defaults in the paintComponent method (don't change the state of the UI at all from within any paint method EVER, painting paints the current state only), that's just asking for no end of trouble.
Instead, when required use UIManager.setLookAndFeel(,,,) and SwingUtiltiies#updateComponentTreeUI and pass in the most top level container
For example...
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LookAndFeelSwitcher {
public static void main(String[] args) {
new LookAndFeelSwitcher();
}
public LookAndFeelSwitcher() {
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());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 2, 2, 2);
add(new JLabel("I have a bad feeling about this"), gbc);
add(new JTextField("When this blows up in your face, don't blame me"), gbc);
UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
DefaultComboBoxModel model = new DefaultComboBoxModel(lafs);
JComboBox cb = new JComboBox(model);
cb.setRenderer(new LookAndFeelInfoListCellRenderer());
add(cb, gbc);
String name = UIManager.getLookAndFeel().getName();
for (int index = 0; index < model.getSize(); index++) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) model.getElementAt(index);
if (info.getName().equals(name)) {
model.setSelectedItem(info);
break;
}
}
cb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) cb.getSelectedItem();
String className = info.getClassName();
try {
UIManager.setLookAndFeel(className);
SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(TestPane.this));
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
}
});
}
public class LookAndFeelInfoListCellRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof UIManager.LookAndFeelInfo) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) value;
value = info.getName();
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
}
}
I'm trying to make a program thats based upon 5 Jsplitpanes and internal components, but they don't seem to get along well. Since when I run the application all the split panes are concentrated on the upper left corner
The code is really simple:
projectExplorer = new ProjectExplorer();
editorExplorer = new EditorExplorer();
favDownload = new FavDownload();
favDownloadExplorer = new FavDownloadExplorer();
//Define the listeners
addComponentListener(new ResizeListener());
horTopPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
horTopPane.setLeftComponent(projectExplorer);
horTopPane.setRightComponent(editorExplorer);
horBotPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
horBotPane.setLeftComponent(favDownload);
horBotPane.setRightComponent(favDownloadExplorer);
verticalPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
verticalPane.setTopComponent(horTopPane);
verticalPane.setBottomComponent(horBotPane);
setContentPane(verticalPane);
new Timer().schedule(new TimerTask() {
#Override
public void run() {
resizeComponents();
}
}, 100);
the resizeComponents method is
public void resizeComponents(){
int width = getWidth();
int height = getHeight();
horTopPane.setMinimumSize(new Dimension(width, (height/2)+(height/4)));
horTopPane.setSize(horTopPane.getMinimumSize());
horBotPane.setMinimumSize(new Dimension(width, (height/8)));
horBotPane.setSize(horBotPane.getMinimumSize());
projectExplorer.setMinimumSize(new Dimension(width/8, horTopPane.getHeight()));
projectExplorer.setSize(projectExplorer.getMinimumSize());
editorExplorer.setMinimumSize(new Dimension(width/2, horTopPane.getHeight()));
editorExplorer.setSize(editorExplorer.getMinimumSize());
favDownload.setMinimumSize(new Dimension(width/8, horBotPane.getHeight()));
favDownload.setSize(favDownload.getMinimumSize());
favDownloadExplorer.setMinimumSize(new Dimension(width/2, horBotPane.getHeight()));
favDownloadExplorer.setSize(favDownloadExplorer.getMinimumSize());
}
it started just as setSize, but for some reason it didn't resize them at all, then I putted setMinimumSize and it didn't resize it either, but when i move the elements a bit on the program they all go to its minimum size, am I missing something?
I've already made a few Java GUI programs, but this one just doesn't want to work properly.
What did I do wrong?
To start with, don't mess with setMinimum/Maximum/PreferredSize, have a look at Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? for more details
Next, don't update the state of UI components from any thread other than the Event Dispatching Thread, Swing is not thread safe. java.util.Timer will trigger event notification within it's own thread context.
Next, consider overriding (at least), getPreferredSize from your custom components and return a default value, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SplitPaneTest {
public static void main(String[] args) {
new SplitPaneTest();
}
public SplitPaneTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private final ProjectExplorer projectExplorer;
private final EditorExplorer editorExplorer;
private final FavDownload favDownload;
private final FavDownloadExplorer favDownloadExplorer;
private final JSplitPane horTopPane;
private final JSplitPane horBotPane;
private final JSplitPane verticalPane;
public TestPane() {
projectExplorer = new ProjectExplorer();
editorExplorer = new EditorExplorer();
favDownload = new FavDownload();
favDownloadExplorer = new FavDownloadExplorer();
horTopPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
horTopPane.setLeftComponent(projectExplorer);
horTopPane.setRightComponent(editorExplorer);
horBotPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
horBotPane.setLeftComponent(favDownload);
horBotPane.setRightComponent(favDownloadExplorer);
verticalPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
verticalPane.setTopComponent(horTopPane);
verticalPane.setBottomComponent(horBotPane);
setLayout(new BorderLayout());
add(verticalPane);
}
}
protected abstract class AbstractPane extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class ProjectExplorer extends AbstractPane {
public ProjectExplorer() {
setBackground(Color.RED);
}
}
public class EditorExplorer extends AbstractPane {
public EditorExplorer() {
setBackground(Color.BLUE);
}
}
public class FavDownload extends AbstractPane {
public FavDownload() {
setBackground(Color.MAGENTA);
}
}
public class FavDownloadExplorer extends AbstractPane {
public FavDownloadExplorer() {
setBackground(Color.CYAN);
}
}
}
If you have to modify the divider's location consider using JSplitSpane#setDividerLocation(double) or JSplitSpane#setDividerLocation(int)
Take a look at How to Use Split Panes for more details
I'm still very new to java programming, so please help me to correct any mistakes I might have overlooked or give tips on how to improve this program.
Okay, so a lot of problems have been solved, and now I have a CardLayout, but I still have questions about how I should make my pipes show inside it.
When I tried to add in my refresh rate timer and my speed timer, I have problems about how I need to declare and initialize boolean variables.
Also, when I compile and run this game, I get files such as Game$1.class. Is there a way for me to clean this up, and could someone explain why this happens? Do these have an affect on the finished product? (When the game is compiled and packaged into a JAR.)
I want to set playerIsReady to true when the play button is clicked. And from there, when the if statement is true, then switch to a panel that displays the pipes, and start moving the pipe across the screen. Preferably 3 instances of that pipe, each starting at different times, but whatever you can help with is fine.
Some of this code needs work, so I have commented some parts out and left notes.
My other questions about this game can be found here.
This is my current code
Game.java
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.SwingUtilities;
public class Game {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
final JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
/*if (playerIsReady) {
Timer speed = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
pipes.move();
}
});
speed.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
pipes.repaint();
}
});
refresh.start();
}*/
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Pipes.java
// What import(s) do I need for ArrayList?
public class Pipes {
List<Pipe> pipes = new ArrayList<Pipe>();
public Pipes() {
pipes.add(new Pipe(50, 100));
pipes.add(new Pipe(150, 100));
pipes.add(new Pipe(250, 100));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for ( Pipe pipe : pipes ){
pipe.drawPipe(g);
}
}
}
PipeObject.java
import java.awt.Graphics;
public class PipeObject {
//Declare and initialiaze variables
int x1 = 754; //xVal start
int x2 = 75; //pipe width
//total width is 83
int y1 = -1; //yVal start
int y2 = setHeightVal(); //pipe height
int gap = 130; //gap height
public void drawPipe(Graphics g) {
g.clearRect(0,0,750,500); //Clear screen
g.drawRect(x1,y1,x2,y2); //Draw part 1
g.drawRect(x1-3,y2-1,x2+6,25); //Draw part 2
g.drawRect(x1-3,y2+25+gap,x2+6,25); //Draw part 3
g.drawRect(x1,y2+25+gap+25,x2,500-y2-49-gap); //Draw part 4
}
public void move() {
x1--;
}
public int getMyX() { //To determine where the pipe is horizontally
return x1-3;
}
public int getMyY() { //To determine where the pipe is vertically
return y2+25;
}
public int setHeightVal() { //Get a random number and select a preset height
int num = (int)(9*Math.random() + 1);
int val = 0;
if (num == 9)
{
val = 295;
}
else if (num == 8)
{
val = 246;
}
else if (num == 7)
{
val = 216;
}
else if (num == 6)
{
val = 185;
}
else if (num == 5)
{
val = 156;
}
else if (num == 4)
{
val = 125;
}
else if (num == 3)
{
val = 96;
}
else if (num == 2)
{
val = 66;
}
else
{
val = 25;
}
return val;
}
}
The best way to approach this is using a CardLayout.
Notes
A button with an ActionListener is far better than a MouseListener over a rectangle.
The button will show focus when the mouse is pointed at it, or the component is tabbed to via the keyboard.
The button is keyboard accessible.
The button has facility to support multiple icons built in (e.g. for 'initial look', focused, pressed etc.)
White space in the GUI is provided around the menu panel and game by adding an EmptyBorder
The button is made larger by setting a margin.
Adjust margins, borders and preferred size according to need. These sizes were set by me so as not to make the screenshots too large.
See more tips in the code comments.
Code
Here is the MCTaRE (Minimal Complete Tested and Readable Example) that produced the above screenshots.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class PipesGame {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
class Pipes extends JPanel {
Pipes() {
setBackground(Color.BLACK);
setForeground(Color.WHITE);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Pipes game appears here..", 170, 80);
}
#Override
public Dimension getPreferredSize() {
// adjust to need
return new Dimension(500,150);
}
}
"Is there a way for me to add my GameMenu jpanel to my jframe, and then replace it with the Pipes jpanel?"
As other have suggested, for this you want a CardLayout. It is very simple to you. Personally, I always wrap my CardLayout in a JPanel rather than the JFrame, just force of habit.
What you want to do is have a mainPanel that will have the CardLayout
CardLayout card = new CardLayout();
JPanel mainPanel = new JPanel();
Then you want to add your panels to the mainPanel. What the CardLyaout does is layer the panels, making just one visible at a time. The first one you add, will the in the foreground. Also when you add the panel, you'll also want to issue it a key it can be called from. The key, can be any String you like.
mainPanel.add(gameMenu, "menu");
mainPnael.add(pipes, "pipe");
Now gameMenu is the only panel shown. To show pipes, all you do is use this method
public void show(Container parent, String name) - Flips to the parent that was added to this layout with the specified name, using addLayoutComponent. If no such component exists, then nothing happens.
So you'd use, card.show(mainPanel, "pipes");
Whatever even you want to trigger the showing of pipes, just add that line in that event handler. You could add a button or something to the GameMenu that will allow movement to the Pipes panel.
This works with a mouse click on the menu. You can change it later, to a click on some button or whatever you want.
I added a MouseListener to the Game class. When the user presses the mouse on the menu JPanel, it adds the Pipes JPanel to JFrame and calls the pack method.
Game.java:
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Game {
GameMenu menu = new GameMenu();
Pipes game;
boolean start = false;
JFrame f;
Rectangle2D menuRect = new Rectangle2D.Double(20, 20, 60, 40);
public Game() {
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(menu);
f.setTitle("Pipe Game");
f.setResizable(false);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
menu.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
Point click = new Point(e.getX(), e.getY());
System.out.println("Clicked on the Panel");
if(menuRect.contains(click))
{
System.out.println("Clicked inside the Rectangle.");
start = true;
menu.setVisible(false);
game = new Pipes();
f.add(game);
f.pack();
Timer timer = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
game.move();
}
});
timer.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
game.repaint();
}
});
refresh.start();
}
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game();
}
});
}
}
I am working on an application that executes some functions that run for long. To let the user aware that the processing is taking place, I needed a label that can display some label that can represent that. So, I created a small widget for such a label.
The program below runs find and I get the output as I wanted.
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* This is an extension to a JLabel that can be used to display an ongoing progress.
* #author Ankit Gupta
*/
public class ProgressLabel extends JLabel {
/**
* The prefix label to which periods are added.
*/
private String startLabel;
/**
* The label to display end of an operation.
*/
private String endLabel;
/**
* Flag to indicate whether the animation is running or not.
*/
private boolean running = false;
//list to hold intermediate labels
List<String> intermediateLabels;
public ProgressLabel(String slbl, String elbl) {
this.startLabel = slbl;
this.endLabel = elbl;
//initialize all the labels to be used once as creating them again and again is expensive
intermediateLabels = new ArrayList<String>();
intermediateLabels.add(startLabel+".");
intermediateLabels.add(startLabel+"..");
intermediateLabels.add(startLabel+"...");
intermediateLabels.add(startLabel+"....");
}
public void done(){
running = false;
}
public void start(){
running = true;
new LabelUpdateThread().start();
}
private class LabelUpdateThread extends Thread{
int i;
public LabelUpdateThread(){
i=0;
}
#Override
public void run(){
while(running){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
setText(intermediateLabels.get((i++)%3));
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {}
}
setText(endLabel);
}
}
public static void main(String []args) throws InterruptedException{
final JFrame frame = new JFrame("Testing ProgressLabel");
JPanel panel = new JPanel();
ProgressLabel progressLabel = new CZProgressLabel("Searching", "Done");
panel.add(progressLabel);
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(500,500));
frame.pack();
progressLabel.start();
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
frame.setVisible(true);
}
});
Thread.sleep(5000);
progressLabel.done();
}
}
However, when I tried to include this in the application, it did not work as expected. I created a small panel with a button and in the actionPerfomed() code for the button I used the ProgressLabel's start() and done() methods as before but this time, the label just did not update to Done until the length process finished. Here is another piece of code using the ProgressLabel with actionPerformed() :
public class SearchPanel extends JPanel {
private JTextArea queryBox;
private JButton searchBtn;
private ProgressLabel progressLabel;
private JSeparator queryAreaSeparator;
public SearchPanel() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
//First Row
gbc.gridy = 0;
gbc.gridwidth = 2;
gbc.gridx = 0;
queryBox = new JTextArea();
queryBox.setRows(25);
queryBox.setColumns(25);
this.add(queryBox, gbc);
//Second Row
gbc.gridy = 1;
gbc.gridwidth = 1;
progressLabel = new ProgressLabel("Searching", "Done");
this.add(progressLabel, gbc);
gbc.gridx = 1;
searchBtn = new JButton("Search");
this.add(searchBtn, gbc);
searchBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
progressLabel.start();
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
//the above sleep() call will be replace by some time-consuming process. It is there just for testing now
progressLabel.done();
}
});
gbc.gridx = 0;
}
/**
* function to test CZSemanticSearchLabel
*/
public static void main(String[] args) throws InterruptedException {
final JFrame frame = new JFrame();
CZSemanticSearchPanel panel = new CZSemanticSearchPanel();
frame.add(panel);
frame.setPreferredSize(new Dimension(500, 500));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Thread.sleep(10000);
frame.dispose();
final JFrame frame1 = new JFrame("Testing ProgressLabel");
JPanel panel1 = new JPanel();
CZProgressLabel progressLabel = new CZProgressLabel("Searching", "Done");
panel1.add(progressLabel);
frame1.add(panel1);
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame1.setPreferredSize(new Dimension(500, 500));
frame1.pack();
progressLabel.start();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame1.setVisible(true);
}
});
Thread.sleep(5000);
progressLabel.done();
}
}
I believe that I have screwed something with Swing's Event dispatch model. But, I cannot figure what? Can someone tell me what is wrong with this code and how do I correct it?
I don't know about your actual code, but your sample code is flawed...
In your ActionListener you are doing this...
progressLabel.start();
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
//the above sleep() call will be replace by some time-consuming process. It is there just for testing now
progressLabel.done();
This will STOP the Event Dispatching Thread, preventing any repaint requests from the been handled (ie no screen updates) for 10 seconds...this will also make your application look like it's "hung".
I updated you ActionListener to read like this (note I added a isRunning method which returns the running member from the label)
if (progressLabel.isRunning()) {
progressLabel.done();
} else {
progressLabel.start();
}
And it works fine.
You might like to read through Currency in Swing for some more ideas.
Also, as already suggested, SwingWorker may be a better approach
Instead of implementing this yourself with threading, you can use SwingWorker: Simple Background Tasks that is made for such things, and the linked example is very similar to your problem.
Your start() doesn't execute your LabelUpdateThread().run() but your LabelUpdateThread().start().