So I am making a game that records your reaction time after you see something pop up on the screen, but I am having trouble with getting that reaction time. I want the user to press the up arrow key once they see a blue ball and I want to record their reaction time once they pressed that button.
Here is my code:
public class Game extends JPanel
{
private JLabel start, main, time;
private ImageIcon constant, react;
final int width = 600;
final int height = 600;
private Timer replace;
private Random random;
private int randTime;
private long startTime;
private long stopTime;
private long reactionTime;
private Action upAction;
public Game()
{
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setPreferredSize(new Dimension(width, height));
setBackground(Color.black);
start = new JLabel("Click Up Arrow when you see a blue ball");
start.setForeground(Color.white);
start.setAlignmentX(Component.CENTER_ALIGNMENT);
add(start);
constant = new ImageIcon("constantCircle.png");
main = new JLabel(constant);
main.setAlignmentX(Component.CENTER_ALIGNMENT);
randomTime();
replace = new Timer(randTime, timeListener);
startTime = System.currentTimeMillis();
replace.setRepeats(false);
replace.start();
add(main);
time = new JLabel("0");
time.getInputMap().put(KeyStroke.getKeyStroke("UP"), "upAction");
time.getActionMap().put("upAction", upAction);
add(time);
}
public void randomTime()
{
random = new Random();
int max = 8000;
randTime = random.nextInt(max);
}
ActionListener timeListener = new ActionListener()
{
public void actionPerformed (ActionEvent e)
{
react = new ImageIcon("reactCircle.png");
main.setIcon(react);
}
};
public class UpAction extends AbstractAction
{
public void actionPerformed(ActionEvent e)
{
stopTime = System.currentTimeMillis();
reactionTime = stopTime - startTime;
time.setText("" + reactionTime);
}
}
}
I setup a "startTime" using System.currentTimeMillis to get the time after the ball turns blue but I am not sure if that is the correct way to do it.
I also setup a "stopTime" in the "UpAction" class where I want to get the time once the user presses up arrow but I it does not work.
if anything doesn't make sense or isn't clear enough, I'll try my best to elaborate more
I came up with the following GUI.
There are two important principles I want to explain. The first is that creating the GUI is a separate process from updating the GUI. The second is that the game process is a state machine. The game is in six separate states. Here's what I wrote to keep the states in mind.
Sequence of events
Left-click button
Wait 2 - 4 seconds to display the circle.
Capture start time
Left-click button
Capture end time.
Calculate and display reaction time.
Repeat 1 - 6.
So, for the GUI, I created a JFrame and three JPanels; an upper JPanel, a drawing JPanel, and a button JPanel.
I started the Swing application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
The JFrame has a default BorderLayout, which I used to place the three JPanels. The JFrame method calls must be executed in a specific order. This is the order I use for all my Swing applications.
The upper JPanel contains the instructions and the reaction time display. A JTextArea is great for displaying instructions. I put the JTextArea inside an inner JPanel using a FlowLayout, which I placed in the upper JPanel using a BorderLayout. Nesting layouts like this is a good way to organize the Swing components in a logical manner.
I put the reaction time Swing components in another inner JPanel, which I placed in the upper JPanel.
I created a drawing JPanel so I wouldn't have to bother with an image.
The button JPanel holds the Submit JButton.
I created two controller classes. One controller class, ButtonListener, responds to the JButton left-clicks. The other controller class, TimerListener, creates the delay for drawing the circle.
The ButtonListener state variable allows me to provide different functionality with the same ActionListener. If you wish, you can write separate ActionListener classes, one for each function.
By separating my code into view and controller classes, I could separate my concerns and focus on one part of the application at a time.
Here's the complete runnable code. I made all the classes inner classes so I could post this code as one block.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ReactionTimeGame implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ReactionTimeGame());
}
private long reactionTime;
private DrawingPanel drawingPanel;
private JTextField reactionTimeField;
public ReactionTimeGame() {
this.reactionTime = 0L;
}
#Override
public void run() {
JFrame frame = new JFrame("Reaction Time Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createUpperPanel(), BorderLayout.BEFORE_FIRST_LINE);
this.drawingPanel = new DrawingPanel();
frame.add(drawingPanel, BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.AFTER_LAST_LINE);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createUpperPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JPanel innerPanel = new JPanel(new FlowLayout());
String instructions = "This game will test your reaction time. To play "
+ "the game, left-click on the Submit button. After a random time "
+ "from 2 - 4 seconds, a circle will appear. Left-click the "
+ "Submit button again. Your reaction time will be displayed "
+ "above where the circle was.\n\n"
+ "Left-click the Submit button to start each round of the game.";
JTextArea textArea = new JTextArea(7, 40);
textArea.setEditable(false);
textArea.setText(instructions);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
innerPanel.add(textArea);
panel.add(innerPanel, BorderLayout.BEFORE_FIRST_LINE);
innerPanel = new JPanel(new FlowLayout());
JLabel label = new JLabel("Reaction Time:");
innerPanel.add(label);
reactionTimeField = new JTextField(5);
reactionTimeField.setEditable(false);
updateReactionTime();
innerPanel.add(reactionTimeField);
label = new JLabel("seconds");
innerPanel.add(label);
panel.add(innerPanel, BorderLayout.AFTER_LAST_LINE);
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JButton button = new JButton("Submit");
button.addActionListener(new ButtonListener());
panel.add(button);
return panel;
}
public void setReactionTime(long reactionTime) {
this.reactionTime = reactionTime;
}
public void drawCircle() {
drawingPanel.setDrawCircle(true);
drawingPanel.repaint();
}
public void eraseCircle() {
drawingPanel.setDrawCircle(false);
drawingPanel.repaint();
}
public void updateReactionTime() {
double time = 0.001 * reactionTime;
reactionTimeField.setText(String.format("%.3f", time));
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private boolean drawCircle;
public DrawingPanel() {
this.drawCircle = false;
this.setPreferredSize(new Dimension(300, 300));
}
public void setDrawCircle(boolean drawCircle) {
this.drawCircle = drawCircle;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (drawCircle) {
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = Math.min(getWidth(), getHeight()) * 9 / 20;
int diameter = radius + radius;
g.setColor(Color.MAGENTA);
g.fillOval(centerX - radius, centerY - radius, diameter, diameter);
}
}
}
public class ButtonListener implements ActionListener {
private int state;
private long startTime;
private final Random random;
private Timer timer;
public ButtonListener() {
this.state = 1;
this.random = new Random();
}
#Override
public void actionPerformed(ActionEvent event) {
switch (state) {
case 1:
int delay = random.nextInt(2000) + 2000;
timer = new Timer(delay, new TimerListener(this));
timer.start();
state = 2;
break;
case 2:
setEndTime(System.currentTimeMillis());
eraseCircle();
state = 1;
break;
}
}
public int getState() {
return state;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public void setEndTime(long endTime) {
long elapsedTime = endTime - startTime;
setReactionTime(elapsedTime);
updateReactionTime();
}
}
public class TimerListener implements ActionListener {
private final ButtonListener listener;
public TimerListener(ButtonListener listener) {
this.listener = listener;
}
#Override
public void actionPerformed(ActionEvent event) {
Timer timer = (Timer) event.getSource();
timer.stop();
if (listener.getState() == 2) {
listener.setStartTime(System.currentTimeMillis());
drawCircle();
}
}
}
}
Related
EDIT: leaving short code which shows issue:
Then label.setText("") is called and it has to change the text, i.e. scaler overcame 50 barrier the label dissapears for the mousewheelevent -> re-emerges on next event. Also a problem of the label not being present on the Load-up.
In actual program labels stack together in 1 spot if any of them has .setText() changing value or .setVisible changing (These two methods I need to be able to use without affecting labels positioning)
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.OverlayLayout;
public class brokenLabel extends JFrame
{
private JLayeredPane layerPane;
private JPanel breakingLabelPanel = new JPanel();
private JLabel label;
private int scaler = 50;
private int xstart = 200;
private int ystart = 200;
public static void main(String[] args)
{
#SuppressWarnings("unused")
brokenLabel program = new brokenLabel();
}
public brokenLabel()
{
// Frame setup used on the program
final JFrame frame = new JFrame("This is a slideshow");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
layerPane = new JLayeredPane();
frame.setContentPane(layerPane);
// Panel used (it's similar to what's added on the actual program)
breakingLabelPanel.setOpaque(true);
breakingLabelPanel.setLayout(new OverlayLayout(breakingLabelPanel));
breakingLabelPanel.setVisible(true);
breakingLabelPanel.setSize(getMaximumSize());
breakingLabelPanel.addMouseWheelListener(new MouseWheelListener(){
#Override
public void mouseWheelMoved(MouseWheelEvent arg0)
{
if (scaler > 5 || arg0.getWheelRotation() < 0)
scaler = scaler - 5*arg0.getWheelRotation();
setTexts();
}
});
//The testing label
label = new JLabel(new String("1"));
label.setLocation(xstart , ystart);
breakingLabelPanel.add(label);
frame.add(breakingLabelPanel,layerPane);
frame.setVisible(true);
//revalidate and repaint don't help make label visible on load-up
frame.revalidate();
frame.repaint();
}
public void setTexts(){
label.setLocation(xstart + scaler,ystart + 25);
if(scaler < 50)
{
label.setText("1");
}
else
{
label.setText("2");
}
}
}
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();
}
});
}
}
This is my graphics class, but when I create and add a JPanel as a content pane to it nothing shows up. I have done many tests to see if my content pane is visible but it still will not show.
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GraphicsMain extends JFrame{
public static final long serialVersionUID = 7610350056926018727L;
static GraphicsMain frame = new GraphicsMain();
static final int WIDTH = 1024, HEIGHT = 768;
static Listener listener = new Listener();
public static void init() {
createGUI();
}
public static void createGUI() {
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setFocusable(true);
frame.setLayout(null);
frame.setResizable(false);
frame.setSize(WIDTH, HEIGHT);
frame.setTitle("Game of Life");
frame.setContentPane(frame.createMainPanel());
frame.setVisible(true);
}
public JPanel createMainPanel() {
JPanel totalGUI = new JPanel();
totalGUI.setSize(HEIGHT, WIDTH);
totalGUI.setBackground(Color.red);
totalGUI.setLayout(null);
JPanel buttonPanel = createButtonPanel();
totalGUI.add(buttonPanel);
totalGUI.setVisible(true);
System.out.println("Is returning!");
return totalGUI;
}
public JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel();
buttonPanel.setSize(WIDTH, HEIGHT);
buttonPanel.setLocation(0, 0);
Font buttonFont = new Font("Button Font", Font.PLAIN, 12);
JButton goButton = createButton("Go", WIDTH/16, HEIGHT/12, 10, 10, listener, buttonFont, Color.black);
buttonPanel.add(goButton);
JButton clearButton = createButton("Clear", WIDTH/16, HEIGHT/12, 10 + HEIGHT/12, 10, listener, buttonFont, Color.black);
buttonPanel.add(clearButton);
JButton exitButton = createButton("Exit", WIDTH/16, HEIGHT/12, 10 + 2*HEIGHT/12, 10, listener, buttonFont, Color.black);
buttonPanel.add(exitButton);
return buttonPanel;
}
public JButton createButton(String text, int width, int height, int x, int y, ActionListener listener, Font font, Color color) {
JButton button = new JButton(text);
button.setSize(width, height);
button.setLocation(x, y);
button.addActionListener(listener);
button.setFont(font);
button.setForeground(Color.red);
return button;
}
}
this class is called by
import javax.swing.SwingUtilities;
public class GameOfLifeMain {
static boolean running = true;
static int UPS = 60;
static GameOfLifeMain main = new GameOfLifeMain();
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
main.init();
long startTime = System.nanoTime();
double ns = 1000000000.0 / UPS;
double delta = 0;
long secondTimer = System.nanoTime();
while(running) {
long now = System.nanoTime();
delta += (now - startTime) / ns;
startTime = now;
while(delta >= 1) {
main.update();
delta--;
}
main.render();
if(System.nanoTime() - secondTimer > 1000000000) {
secondTimer += 1000000000;
}
}
}
});
}
public void init() {
GraphicsMain.init();
}
public void update() {
}
public void render() {
//GraphicsMain.render();
}
}
Your problems are (at least) two fold:
As #Radiodef astutely notes, you're calling a long-running loop on the Swing event thread. Since this thread is completely responsible for the rendering of your GUI and the interacting with the user, your GUI becomes completely frozen.
You are using null layouts which make you completely responsible for the positioning and size of all components added to the null-layout using component.
I suggest:
As #Radiodef suggests, do your long-running loop in a background thread such as a SwingWorker. This will allow easier interaction between your GUI and the background process. His link is a good one: Concurrency in Swing.
Use nested JPanels, each one using its own layout to achieve the layout desired, one that will run well on any platform and on any video card setting.
Better to override your drawing JPanels getPreferredSize(...) method to have it sized correctly.
Setting a JPanel to opaque via setOpaque(true) will not help since JPanels are already opaque by default.
Edit
I stand corrected on the last point per Jan Bodnar:
"However, the default value for this property on most standard JComponent subclasses (such as JButton and JTree) is look-and-feel dependent." For example, the JPanel of the GTK look and feel is not opaque by default.
Thanks, Jan
Call setOpaque(true) on your Panel, then the background will be painted. I also noticed that you mixed up WIDTH and HEIGHT when defining the size of your panel (this might be an error).
What are several ways of detecting a key stroke without the need of focusing on the component that the event was implemented? Here's my idea on this:
Even without focusing on myComponent, upon pressing a key, the action should take part. ** Same question for the mousePressed event. A mouse click will be detected even when not clicking on the component.**
myComponent.addKeyListener( new KeyAdapter() {
#Override
public void keyPressed( KeyEvent e ){
// My action here
}
});
Upon answering Question1, can it also be done even if the application is running on background? Say I have a browser, every time I click or press a key, the given action will be executed.
I also accept suggestions to read as an answer. If your answer would be KeyBinding related, please do elaborate. All answer and comments will be greatly appreciated.
I used JNativeHooks examples here and it works perfectly fine. Any other method by just Java alone?
For the first question, regarding the KeyStroke thingy, I guess you can use KeyBinding instead of using KeyListener, that can give you the desired result, without the focus related issues of the component in question, though within the Java Dimensions.
In the example below, the focus is on the JTextField first, so if you will Press CTRL + D, then the paintAction thingy attached to the CustomPanel will work, even though the focus lies with the JTextField.
Though if you will use the setMnemonic() method for JButton, then the JButton will gain focus and will perform it's own action associated with it, which is to draw Ovals. This you can see by Pressing ALT + C, to see the desired effect. Again to perform the drawing related thingy, both the components in question don't need the focus, but still they respond to the KeyStrokes.
Here is the example code :
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class SSCCE
{
private final int WIDTH = 500;
private final int HEIGHT = 500;
private CustomPanel customPanel;
private JButton circleButton;
private JTextField tfield;
private Random random;
private int mode;
private Action paintAction = new AbstractAction()
{
#Override
public void actionPerformed(ActionEvent ae)
{
mode = random.nextInt(3);
Color color = new Color(random.nextFloat(), random.nextFloat()
, random.nextFloat(), random.nextFloat());
customPanel.setValues(random.nextInt(WIDTH),
random.nextInt(HEIGHT), random.nextInt(WIDTH),
random.nextInt(HEIGHT), color, mode);
}
};
private ActionListener buttonAction = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent ae)
{
Color color = new Color(random.nextFloat(), random.nextFloat()
, random.nextFloat(), random.nextFloat());
customPanel.setValues(random.nextInt(WIDTH),
random.nextInt(HEIGHT), random.nextInt(WIDTH),
random.nextInt(HEIGHT), color, 2);
}
};
public SSCCE()
{
random = new Random();
}
private void displayGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout(5, 5));
customPanel = new CustomPanel();
customPanel.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_D
, InputEvent.CTRL_DOWN_MASK), "paintAction");
customPanel.getActionMap().put("paintAction", paintAction);
JPanel footerPanel = new JPanel();
circleButton = new JButton("Draw Circle");
circleButton.setMnemonic(KeyEvent.VK_C);
circleButton.addActionListener(buttonAction);
tfield = new JTextField(20);
tfield.setText("USELESS, just to get the focus for itself.");
tfield.requestFocusInWindow();
footerPanel.add(tfield);
footerPanel.add(circleButton);
contentPane.add(customPanel, BorderLayout.CENTER);
contentPane.add(footerPanel, BorderLayout.PAGE_END);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String... args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
new SSCCE().displayGUI();
}
});
}
}
class CustomPanel extends JPanel
{
private final int WIDTH = 500;
private final int HEIGHT = 500;
private int mode = 0;
private Color colorShape;
private int x = 0;
private int y = 0;
private int width = 0;
private int height = 0;
public void setValues(int x, int y, int w, int h, Color color, int mode)
{
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.colorShape = color;
this.mode = mode;
repaint();
}
#Override
public Dimension getPreferredSize()
{
return (new Dimension(WIDTH, HEIGHT));
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(colorShape);
if (mode == 1)
g.fillRect(x, y, width, height);
else if (mode == 2)
g.fillOval(x, y, width, height);
}
}
Related to mousePressed() thingy, #mKorbel, had presented the whole thingy as usual in a delightful manner.
And regarding your second question, seems like you yourself had done some homework on that. Seems like either using what you showed in your question is the workaround for catching Operating System related events and transfer that to your Java Application or Java Native Interface, I guess might also can work for this.
all JComponent has method dispatchEvent,
you can to redirect mouse & key event from one JComponent to the another
for JButton to use doClick() instead
for example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class LostMouseEvent {
private JPanel panel1;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new LostMouseEvent();
}
});
}
public LostMouseEvent() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
panel1 = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 400);
}
};
JPanel panel2 = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 300);
}
};
JScrollPane pane = new JScrollPane(panel2);
panel1.setBorder(BorderFactory.createLineBorder(Color.blue));
panel2.setBorder(BorderFactory.createLineBorder(Color.green));
panel1.setLayout(new CircleLayout());
panel1.add(pane);
frame.add(panel1);
MouseListener rml = new RealMouseListener();
panel1.addMouseListener(rml);
MouseListener fml = new FakeMouseListener();
panel2.addMouseListener(fml);
frame.pack();
frame.setVisible(true);
}
});
}
private class RealMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent me) {
System.out.println(me);
Point point = me.getPoint();
System.out.println(me.getX());
System.out.println(me.getXOnScreen());
System.out.println(me.getY());
System.out.println(me.getYOnScreen());
}
}
private class FakeMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent me) {
JPanel panel2 = (JPanel) me.getSource();
MouseEvent newMe = SwingUtilities.convertMouseEvent(panel2, me, panel1);
System.out.println(newMe.getX());
System.out.println(newMe.getXOnScreen());
System.out.println(newMe.getY());
System.out.println(newMe.getYOnScreen());
panel1.dispatchEvent(me);
}
}
}
Contents
Overview
Example Code
Screenshots of Problem
1. Overview of problem
So I'm writing a GUI for a complicated program I'm developing, and I get tired of trying to get components to scale correctly when the window is resized.
At first I was using several layouts inside the jframe, and each jpanel to try and place the components correctly and scale them appropriately. Naturally, I got fed up with them, and I started trying to scale and set the x,y positions of the components dynamically (it's so much easier :D).
Basically I'm trying to divide the screen into three sections left margin (JSplitPane), center (JTabbedPane), and right margin (JSplitPane). I don't think the internal components matter at this point. The main problem is the right JSplitPane scales over the whole window despite my using setBounds() to place the x,y over on the right and set the size to 21% of the total width. It seems to interact weird with the other panels.
2. Example Code
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.Dimension;
#SuppressWarnings("deprecation")
public class test extends JFrame implements WindowListener {
/* Constants =========================================================================*/
private final double LEFT_SIZE = .21;
private final double CENTER_SIZE = .58;
private final double RIGHT_SIZE = .21;
private final int TOP_PADDING = 50;
private final int LEFT_PADDING = 4;
private final int RIGHT_PADDING = 4;
private final int BOTTOM_PADDING = 4;
private final int MIN_WIDTH = 640;
private final int MIN_HEIGHT = 480;
public static final String INIT_TITLE = "TestFrame v0.01";
/* End Constants =====================================================================*/
/* Instance Variables ================================================================*/
private int contentWidth;
private int contentHeight;
/* End Instance Variables ============================================================*/
/* Objects ===========================================================================*/
public static test window;
/* Begin Frame Design =========================================================== */
private JSplitPane left;
private JButton button1; private JButton button2;
private JTabbedPane center;
private JPanel panel1; private JPanel panel2;
private JSplitPane right;
private JButton button3; private JButton button4;
/* End Frame Design ============================================================= */
/* End Objects ====================================================================== */
/** Initializes and Places all GUI elements **/
public test ( String windowName ) {
super(windowName); //call parent constructor
this.addWindowListener(this); //adds window event functionality such as close
this.setExtendedState(this.getExtendedState() | JFrame.MAXIMIZED_BOTH); //Starts program maximized
this.setMinimumSize(new Dimension(MIN_WIDTH,MIN_HEIGHT));
this.setVisible(true);
this.setMaximumSize(this.getSize());
/* Begin Init JFrame this ------------------------------------------------------------ */
button1 = new JButton("button1");
button2 = new JButton("button2");
left = new JSplitPane(JSplitPane.VERTICAL_SPLIT, button1, button2);
left.setResizeWeight(1);
button3 = new JButton("button3");
button4 = new JButton("button4");
right = new JSplitPane(JSplitPane.VERTICAL_SPLIT, button3, button4);
right.setResizeWeight(.25);
panel1 = new JPanel();
panel2 = new JPanel();
center = new JTabbedPane();
center.addTab("Panel1", panel1);
center.addTab("Panel2", panel2);
this.add(left);
this.add(center);
this.add(right);
this.addComponentListener(new ComponentListener() {
#Override
public void componentResized (ComponentEvent e) {
window.contentWidth = window.getWidth() - window.LEFT_PADDING - window.RIGHT_PADDING;
window.contentHeight = window.getHeight() - window.TOP_PADDING - window.BOTTOM_PADDING;
window.left.setBounds ( 0, 0, (int)(window.contentWidth * window.LEFT_SIZE), window.contentHeight);
window.center.setBounds ( window.left.getWidth(), 0, (int)(window.contentWidth * window.CENTER_SIZE), window.contentHeight);
window.panel1.setBounds ( 0, 0, (int)(window.contentWidth * window.CENTER_SIZE), window.contentHeight);
window.panel2.setBounds ( 0, 0, (int)(window.contentWidth * window.CENTER_SIZE), window.contentHeight);
window.right.setBounds ( window.left.getWidth() + window.center.getWidth(), 0, (int)(window.contentWidth * window.RIGHT_SIZE), window.contentHeight);
}
public void componentHidden (ComponentEvent e) {}
public void componentMoved (ComponentEvent e) {}
public void componentShown (ComponentEvent e) {}
});
/* End Init JFrame this -------------------------------------------------------------- */
}
// window event abstracts
#Override
public void windowClosing (WindowEvent event) { window.dispose(); System.exit(0); }
public void windowClosed (WindowEvent event) {}
public void windowDeiconified (WindowEvent event) {}
public void windowIconified (WindowEvent event) {}
public void windowActivated (WindowEvent event) {}
public void windowDeactivated (WindowEvent event) {}
public void windowOpened (WindowEvent event) {}
public static void main(String[] args){
window = new test(INIT_TITLE);
window.setVisible(true);
}
}
3. Screenshots
I don't think the internal components matter at this point.
As discussed in Should I avoid the use of set[Preferred|Maximum|Minimum]Size methods in Java Swing?, nothing could be further from the truth. Correct use of layouts relies on a component's preferred size. That size is carefully calculated based on the contents. Second guessing, as shown in your example, is doomed to fail.
Instead, add components and pack() the frame. In the example below, the center panel returns an arbitrary result to show how pack() does its work.
Addendum: Two additional points helpfully adduced by #mKorbel:
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
See also this example that shows how to use setDividerLocation() in invokeLater().
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import java.awt.Dimension;
import java.awt.EventQueue;
public class Test extends JFrame {
public static final String INIT_TITLE = "TestFrame v0.02";
public static Test window;
private JSplitPane left;
private JTabbedPane center;
private JSplitPane right;
public Test(String windowName) {
super(windowName);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
left = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
new JButton("button1"), new JButton("button2"));
left.setResizeWeight(0.5);
right = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
new JButton("button3"), new JButton("button4"));
right.setResizeWeight(0.5);
center = new JTabbedPane();
center.addTab("Panel1", new MyPanel());
center.addTab("Panel2", new MyPanel());
this.add(left, BorderLayout.WEST);
this.add(center, BorderLayout.CENTER);
this.add(right, BorderLayout.EAST);
this.pack();
this.setLocationByPlatform(true);
this.setVisible(true);
}
private static class MyPanel extends JPanel {
private Dimension d = new Dimension(320, 240);
#Override
public Dimension getPreferredSize() {
return d;
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
window = new Test(INIT_TITLE);
window.setVisible(true);
}
});
}
}