LibGdx - Adding array of actors to a table, with delay - libgdx

I want to have several comets falling in the background of my UI,I have a working comet Actor that does what it is supposed to, but I am not sure how to create a continuous spawn with these comets (with a random delay between each) in a table, without scene2d/actors it would look something like:
cometTimer += delta
if(cometTimer >= interval){
addCometToArray();
cometTimer = 0;
}
With the cometArray being looped over and drawn every frame, and then removing the entity when it goes out of bounds.
The only way I know how to add Actors to a table is like this:
table().add(new DialogComet());
How would I go about adding this type of behaviour using Scene2d?

Not sure if this is what you were looking for, but the below is a small working app that shows comets "falling" from the top to bottom, using Tables and having the tables manage the comets (no separate array/data structure). I created a small Comet class that extends Actor as well, to allow for movement and placement.
"main" class:
import java.util.Iterator;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
public class StageComet implements ApplicationListener {
private static final float INTERVAL = 0.3f;
private Batch batch;
private ShapeRenderer shapeRenderer;
private OrthographicCamera camera;
private BitmapFont font;
private Table rootTable;
private Table cometTable;
private Stage stage;
private Iterator<Actor> iter;
private Comet comet;
private float cometTimer = 0;
private float delta = 0;
#Override
public void create() {
camera = new OrthographicCamera();
camera.setToOrtho(false, 960, 640);
shapeRenderer = new ShapeRenderer();
batch = new SpriteBatch();
font = new BitmapFont();
stage = new Stage();
/*
* The root table could contain main "play" actors. It is empty in this example.
*/
rootTable = new Table();
rootTable.setFillParent(true);
/*
* Usually in Scene2d I think the practice is only to have 1 root table that takes up the entire screen (above),
* but for simplicity/illustrative purposes, I created a cometTable only, set it to Fill Parent as well, and the
* getChildren() of the table will have our array of comets in play at any given time.
*/
cometTable = new Table();
cometTable.setFillParent(true);
stage.addActor(rootTable);
stage.addActor(cometTable);
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
camera.update();
batch.setProjectionMatrix(camera.combined);
delta = Gdx.app.getGraphics().getDeltaTime();
stage.act(delta); // make sure the comets "fall"
shapeRenderer.begin(ShapeType.Filled); // simple rendering of comets, they are just a circle ...
iter = cometTable.getChildren().iterator(); // Table subclasses Group, which has a snapshot array of its Actors
while ( iter.hasNext() ) {
comet = (Comet)iter.next();
shapeRenderer.circle(comet.getX(), comet.getY(), 20.0f); // Draw the comet
if ( comet.getY() < -100 ) { // Hack/hardcode, if the comet fell far enough "off stage" ...
iter.remove(); // ... remove it from the stage
}
}
shapeRenderer.end();
/*
* Sample code from original question on how to create a comet without scene2d ...
*/
cometTimer += delta;
if ( cometTimer > INTERVAL ) {
cometTable.add(new Comet()); // ... but in this case, we use scene2d
cometTimer = 0;
}
/*
* To keep track, display a simple message of # of comets on stage at any given time.
*/
batch.begin();
font.draw(batch, "Comets on stage: " + cometTable.getChildren().size, 100, 100);
batch.end();
}
/*
* I may have missed disposing something, but you get the idea ...
*/
#Override
public void dispose() {
shapeRenderer.dispose();
batch.dispose();
stage.dispose();
font.dispose();
}
#Override
public void resize(int width, int height) { }
#Override
public void pause() { }
#Override
public void resume() { }
}
And the small Comet class:
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.scenes.scene2d.Actor;
public class Comet extends Actor{
/*
* Spawn a comet at the top of the screen, in the middle
*/
public Comet() {
super();
this.setY(Gdx.app.getGraphics().getHeight());
this.setX(Gdx.app.getGraphics().getWidth()/2.0f);
}
/*
* Let the comet fall (same speed) to the bottom of the screen ...
*/
#Override
public void act (float delta) {
this.setY(this.getY() - 10);
super.act(delta);
}
}

Related

LibGDX Stage and Actor, Events and Actor properties

I'm just starting android game development with LibGdx framework.
I read many online tutorial so far and the more I read the more I got confused: ApplicationListener, ApplicationAdapter, Stages, Game, Screens, Actors, Sprites, Images... not mentioning Input and Gesture listeners of all king).
I finally understood what kind of "model" I should use for the game I have in mind (a kind of puzzle game): Game, Screens, Stage and Actor.
So here is my first code.
This is the main application (Game):
package com.my.game1;
import com.badlogic.gdx.Game;
public class MyGame extends Game {
#Override
public void create () {
setScreen(new StarterScreen());
}
}
This is the main screen class:
package com.my.game1;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
public class StarterScreen implements Screen {
private Stage stage;
private float screenW, screenH;
private Tess tessera;
#Override
public void show() {
tessera = new Tess("image.png");
stage = new Stage();
screenW = stage.getViewport().getWorldWidth();
screenH = stage.getViewport().getWorldHeight();
Gdx.input.setInputProcessor(stage);
stage.addActor(tessera);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0,0,0,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
stage.draw();
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void pause() {
// TODO Auto-generated method stub
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void hide() {
dispose();
}
#Override
public void dispose() {
stage.dispose();
}
}
And the following is the class that extends Actor:
package com.my.game1;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
public class Tess extends Actor {
private Texture texture;
private boolean selected = false;
public Tess (String img) {
this.texture = new Texture(Gdx.files.internal(img));
this.setBounds(0f, 0f, this.texture.getWidth(), this.texture.getHeight());
this.setOrigin(this.texture.getWidth() / 2, this.texture.getHeight() / 2);
this.setScale(0.25f);
this.addListener(new ActorGestureListener() {
public void tap(InputEvent event, float x, float y, int pointer, int button) {
((Tess)event.getTarget()).toggleSelect();
((Tess)event.getTarget()).setColor(0.5f, 0f, 0.5f, 1f);
}
});
}
#Override
public void draw(Batch batch, float alpha){
batch.draw(texture, 0, 0);
}
public void finalize() {
this.texture.dispose();
}
public void toggleSelect(){
this.selected = !this.selected;
if (this.selected == true)
this.setColor(0.5f, 0f, 0.5f, 1f);
else
this.setColor(0f, 0f, 0f, 0f);
}
}
The screen shows correctly the actor, but I cannot set the Actor's position or its scale, nor the "tap" event seems to get detected; and the color doesn't change.
What I did wrong?
Several things were wrong. First, just on the side, you don't want to call dispose() from the Screen's hide() method. hide() can be called simply when the screen is turned off, or when the app is switched to the background, and disposing of the Screen during that would cause serious issues on resume.
With that out of the way, here's what your Actor should have looked like:
package com.my.game1;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Touchable;
public class Tess extends Actor {
private Sprite sprite;
private boolean selected = false;
public Tess (String img) {
this.sprite = new Sprite(new Texture(Gdx.files.internal(img)));
this.setTouchable(Touchable.enabled);
this.setBounds(this.sprite.getX(), this.sprite.getY(), this.sprite.getWidth(), this.sprite.getHeight());
this.setOrigin(this.sprite.getWidth() / 2, this.sprite.getHeight() / 2);
this.setScale(0.25f);
this.addListener(new ActorGestureListener() {
#Override
public void tap (InputEvent event, float x, float y, int pointer, int button) {
((Tess)event.getTarget()).toggleSelect();
}
});
}
#Override
public void draw(Batch batch, float alpha){
sprite.draw(batch);
}
#Override
public void positionChanged(){
sprite.setPosition(getX(), getY());
}
public void toggleSelect(){
this.selected = !this.selected;
if (this.selected == true)
sprite.setColor(0.5f, 0f, 0.5f, 1f);
else
sprite.setColor(0f, 0f, 0f, 0f);
}
}
First thing changed: you should use a Sprite, not a Texture, to handle color, drawing and transformations easily. Texture is possible, but is not as straightforward as Sprite is.
Next, you need to call setTouchable(Touchable.enabled) inside the actor to actually enable hit detection. Without this, no touch events are passed to the Actor.
After that, with setBounds(), you need to use sprite.getX() and sprite.getY(), to utilize the Sprite's positional values. Setting them to any arbitrary number seems to disable any touch capacity for that Actor.
Another thing, if all of that had been OK, is that you were setting the color twice for each touch, once based on the selected field, and then immediately after straight to the dark purple, so I removed the second set and just used your toggle method.
Next, since we have a Sprite now, we can use the draw() method attached to the Sprite itself and feed it the Batch, instead of calling the Batch's draw.
Finally, when you want to change the position of the image, call setPosition on the actor itself, and utilize an override of the positionChanged() method to set the Sprite's position based on the Actor's new position.

Pause Menu is "moving down" & improving the code

I have a problem with my pause screen. I made a simple Splash screen, followed by the main menu, where you can start or end the game, followed by a random picture. If the user presses Esc it switches to the pause screen, which is very similar to the main menu. Only difference is that it doesn't generate a new picture if the user clicks on "Continue", instead it just renders the game screen again. But if I press Esc after continuing again, the pause menu appears lower on the screen than it should. If I repeat pressing Continue and then Escape, the buttons eventually moved out of the displayed screen. I didn't find a solution yet, so I made an account here, since this site helped me a lot so far.
Furthermore I want to know if there are things I could improve. I just started with libGDX, so there probably are a lot of things I could've done better, and I want to know that. SO if you have a few improvements, I would be glad to read them :)!
This is the code:
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
public class GameScreen implements Screen{
private Texture[] monsterTextures = {new Texture(Gdx.files.internal("Ressources/DemonHunter.jpg")), new Texture(Gdx.files.internal("Ressources/WingedDemon.jpg")),
new Texture(Gdx.files.internal("Ressources/Viking.jpg")), new Texture(Gdx.files.internal("Ressources/DemonWarrior.jpg"))};
private Image[] monsterImages = {new Image(monsterTextures[0]), new Image(monsterTextures[1]), new Image(monsterTextures[2]), new Image(monsterTextures[3])};
private Stage gameStage = new Stage(), pauseStage = new Stage();
private Table table = new Table();
private Skin menuSkin = new Skin(Gdx.files.internal("skins/menuSkin.json"),
new TextureAtlas(Gdx.files.internal("skins/menuSkin.pack")));
private TextButton buttonContinue = new TextButton("Continue", menuSkin),
buttonExit = new TextButton("Exit", menuSkin);
private Label title = new Label ("Game", menuSkin);
private int randomMonster;
public static final int GAME_RUNNING = 0;
public static final int GAME_PAUSING = 1;
public static final int GAME_PAUSED = 2;
private int gamestatus = 0;
#Override
public void show() {
randomMonster = 0 + (int)(Math.random() * ((3-0) + 1));
gameStage.addActor(monsterImages[randomMonster]);
}
#Override
public void render(float delta) {
if(Gdx.input.isKeyJustPressed(Keys.ESCAPE)) pauseGame();
if(gamestatus == GAME_RUNNING) {
Gdx.gl.glClearColor(0,0,0,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
gameStage.act();
gameStage.draw();
}
if(gamestatus == GAME_PAUSING) {
buttonContinue.addListener(new ClickListener(){
public void clicked(InputEvent event, float x, float y) {
Gdx.gl.glClearColor(0,0,0,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
gamestatus = GAME_RUNNING;
}
});
buttonExit.addListener(new ClickListener(){
public void clicked(InputEvent event, float x, float y) {
Gdx.app.exit();
}
});
table.add(title).padBottom(40).row();
table.add(buttonContinue).size(150, 60).padBottom(20).row();
table.add(buttonExit).size(150, 60).padBottom(20).row();
table.setFillParent(true);
pauseStage.addActor(table);
Gdx.input.setInputProcessor(pauseStage);
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
pauseStage.act();
pauseStage.draw();
gamestatus = GAME_PAUSED;
}
if(gamestatus == GAME_PAUSED) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
pauseStage.act();
pauseStage.draw();
}
}
public void pauseGame() {
gamestatus = GAME_PAUSING;
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void pause() {
pauseGame();
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void hide() {
// TODO Auto-generated method stub
}
#Override
public void dispose() {
for(int i = 0; i < monsterTextures.length; i++) {
monsterTextures[i].dispose();
}
gameStage.dispose();
pauseStage.dispose();
menuSkin.dispose();
}
}
Thanks, Joshflux
I think your render() method is doing things it shouldn't. Like creating the clickListener and also adding buttons to the table (and possibly some other items in there).
The render method gets called every "frame". You don't want to be recreating this, say 60 times a second. You want to do it once (like when you create the particular screen) and then just draw (render) it every frame.
Restructure your code to do the "Creation" stuff once. The render() method should just draw it. I think you continually adding items to your table each frame may be what is causing the buttons to move off the screen.

Hangman Game Background Image Not Efficient?

I'm making a Hangman game and it seems that my code doesn't provide me much freedom with using layouts. I added an image to my JFrame then I added a JPanel to my image which I'm using for all the JLabels and JTextFields but it seems to me that its inefficient because in order to change the layout of my JTextFields or JLabels I have to change the layout of my image which messes up the entire looks of the game. How can I make this code more efficient and give myself more freedom to change the layouts of my JLabels and JTextFields without messing everything up? Thanks for the help in advanced.
/*PACKAGE DECLARATION*/
package Game;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
/************************
* GAME MECHANICS CLASS *
* **********************/
public class GameStructure {
/* INSTANCE DECLARATIONS */
private String []wordList = {"computer","java","activity","alaska","appearance","article",
"automobile","basket","birthday","canada","central","character","chicken","chosen",
"cutting","daily","darkness","diagram","disappear","driving","effort","establish","exact",
"establishment","fifteen","football","foreign","frequently","frighten","function","gradually",
"hurried","identity","importance","impossible","invented","italian","journey","lincoln",
"london","massage","minerals","outer","paint","particles","personal","physical","progress",
"quarter","recognise","replace","rhythm","situation","slightly","steady","stepped",
"strike","successful","sudden","terrible","traffic","unusual","volume","yesterday"};
private int []length = new int [64];
private JTextField tf;//text field instance variable (used)
private JLabel jl2;//label instance variable (used)
private JLabel jl3;//label instance (working on)
private String letter;
/*****************
* LENGTH METHOD *
* ***************/
public void length(){
jl3 = new JLabel();
int j = 0;
for(j = 0; j<64; j++) {
length[j] = wordList[j].length();//gets length of words in wordList
}//end for
int l = 0;
String line = "";
//create line first then put into .setText
for(int m = 0; m<length[l]; m++) {
line += "__ ";
l++;
}//end for
jl3.setText(line);
}//end length method
/*****************
* WINDOW METHOD *
* ***************/
public void window() {
LoadImageApp i = new LoadImageApp();//calling image class
JFrame gameFrame = new JFrame();//declaration
JPanel jp = new JPanel();
//JPanel jp2 = new JPanel();//jpanel for blanks
JLabel jl = new JLabel("Enter a Letter:");//prompt with label
jl.setFont(new Font("Rockwell", Font.PLAIN, 20));//set font
tf = new JTextField(1);//length of text field by character
jl2 = new JLabel("Letters Used: ");
tf.setFont(new Font("Rockwell", Font.PLAIN, 20));//set font
jl2.setFont(new Font("Rockwell", Font.PLAIN, 20));//set font
jp.add(jl);//add label to panel
jp.add(tf);//add text field to panel
jp.add(jl2);//add letters used
gameFrame.add(i); //adds background image to window
i.add(jp); // adds panel containing label to background image panel
gameFrame.setTitle("Hangman");//title of frame window
gameFrame.setSize(850, 600);//sets size of frame
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//exit when 'x' button pressed
gameFrame.setIconImage(new ImageIcon("Hangman-Game-grey.png").getImage());//set the frame icon to an image loaded from a file
gameFrame.setLocationRelativeTo(null);//window centered
gameFrame.setResizable(false);//user can not resize window
gameFrame.setVisible(true);//display frame
}//end window method
/*********************
* USER INPUT METHOD *
* *******************/
public void userInput() {
tf.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {//when enter key pressed
JTextField tf = (JTextField)e.getSource();
letter = tf.getText();
jl2.setText(jl2.getText() + letter + " ");//sets jlabel text to users entered letter
}//end actionPerformed method
});
}//end userInput method
}//end GameMechanics class
/*PACKAGE DECLARATION*/
package Game;
/***********************
* IMPORT DECLARATIONS *
* *********************/
import java.awt.BorderLayout;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
/***************
* IMAGE CLASS *
* *************/
public class LoadImageApp extends JPanel {
private static final long serialVersionUID = 1L;
private ImageIcon image;
/***********************
* PAINT IMAGE METHOD *
* *********************/
public void paintComponent (Graphics g) {
//setLayout(new BorderLayout());
super.paintComponent(g);
image = new ImageIcon("hangman.png");//image name & type
image.paintIcon(this, g, 270, 20);
}//end paintComponent method
}//end LoadImageApp class
/*PACKAGE DECLARATION*/
package Game;
/*******************
* GAME MAIN CLASS *
* *****************/
public class GameMain {
/***************
* MAIN METHOD *
* *************/
public static void main (String []args) {
GameStructure game = new GameStructure();//declaration
game.length();
game.window();
game.userInput();
}//end main method
}//end GameMain class
Some suggestions:
Don't override a JPanel's paint(...) method, but rather its paintComponent(Graphics g) method, not unless you need to change how it renders its child components or its borders (you don't). Also by doing this you gain some Swing graphics advantages including automatic double buffering.
Never read in an image into the paint or paintComponent method. These methods are one of the main determinants of how responsive your GUI appears to the user, and so you never want to do file I/O in the method. And also, why have code that inefficiently re-reads the same image in whenever paint or paintComponent is called? Why not simply store the image or ImageIcon in a variable once, and be done with it?
Learn and use the layout managers
JPanels that go over drawing or image rendering JPanels often should be non-opaque - so be sure to call setOpaque(false) on them, and also on some other overlying Swing components.
_________________________
Edit
For example, here is my SSCCE that shows an example of getting an image (here off of the internet) in a class constructor. Also note that my SSCCE will work on any computer connected to the internet since it does not require image files, unlike yours. Also code not related to displaying the GUI has been cut out making the remaining code more pertinent to the problem. Consider doing this next time you post an SSCCE.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
class GameStructure {
private JTextField tf;
private JLabel jl2;
public void window() {
LoadImageApp loadImageApp = new LoadImageApp();
JFrame gameFrame = new JFrame();
JPanel jp = new JPanel();
jp.setOpaque(false); //!!
jp.setBorder(BorderFactory.createTitledBorder("jp"));
JLabel jl = new JLabel("Enter a Letter:");
jl.setFont(new Font("Rockwell", Font.PLAIN, 20));
tf = new JTextField(1);
jl2 = new JLabel("Letters Used: ");
tf.setFont(new Font("Rockwell", Font.PLAIN, 20));
jl2.setFont(new Font("Rockwell", Font.PLAIN, 20));
jp.add(jl);
jp.add(tf);
jp.add(jl2);
gameFrame.add(loadImageApp);
loadImageApp.add(jp);
gameFrame.setTitle("Hangman");
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// gameFrame.setIconImage(
// new ImageIcon("Hangman-Game-grey.png").getImage());
gameFrame.setResizable(false);
gameFrame.pack();
gameFrame.setLocationRelativeTo(null);
gameFrame.setVisible(true);
}
}
class LoadImageApp extends JPanel {
private static final long serialVersionUID = 1L;
private static final int PREF_W = 850;
private static final int PREF_H = 600;
private BufferedImage img;
public LoadImageApp() {
// just used as an example public image
String spec = "https://duke.kenai.com/"
+ "SunRIP/.Midsize/SunRIP.png.png";
URL url;
try {
url = new URL(spec);
img = ImageIO.read(url);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
}
}
}
public class GameMain {
public static void main(String[] args) {
GameStructure game = new GameStructure();
game.window();
}
}

Issues with ActionListener (Java)

I am trying to implement action listener on two buttons in JFrame, but the issue is one of the two button is performing both the functions; but i've not configured it to do so. Please find the sample code:-
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class MyChangingCirlce implements ActionListener{
JButton colorButton, labelButton;
JLabel myLabel;
MyDrawPanel mdp;
JFrame frame;
public static void main(String [] args)
{
MyChangingCirlce mcc = new MyChangingCirlce();
mcc.createFrame();
}
public void createFrame()
{
frame = new JFrame();
colorButton = new JButton("Changing Colors");
labelButton = new JButton("Change Label");
myLabel = new JLabel("BA");
mdp = new MyDrawPanel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.CENTER, mdp);
frame.getContentPane().add(BorderLayout.SOUTH,colorButton);
frame.getContentPane().add(BorderLayout.EAST,labelButton);
frame.getContentPane().add(BorderLayout.WEST,myLabel);
colorButton.addActionListener(this);
labelButton.addActionListener(this);
frame.setSize(300,300);
frame.setVisible(true);
} // end of createFrame Method
public void actionPerformed(ActionEvent e)
{
if(e.getSource()== colorButton)
{
frame.repaint();
}
else
{
myLabel.setText("AB");
}
} //end of interface method...
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyDrawPanel extends JPanel{
public void paintComponent(Graphics g)
{
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue= (int) (Math.random() * 255);
Color randomColor = new Color(red,green,blue);
g.setColor(randomColor);
g.fillOval(20,70,100,100);
}
}
You think the button triggers both the if and else statement but that is not the case. If you would adjust your code in the following way:
add a setColor, changeColor or something similar to your MyDrawPanel class
adjust the MyDrawPanel#paintComponent method to use a fixed color instead of a random color, and only adjust the color through the method created in the first step
your color change button should use the method created in the first step to adjust the color of the MyDrawPanel
The thing is that paintComponent can be called by Swing itself. It is not only called when you call repaint (which is a good thing, or all code you write for Swing components would be filled with repaint calls).
Side note: when overriding the paintComponent method I would recommended to call super.paintComponent as well

Need the height of an invalidated Swing component

The basic setup is this: I have a vertical JSplitPane that I want to have a fixed-size bottom component and a resizing top component, which I accomplished by calling setResizeWeight(1.0). In this application there is a button to restore the "default" window configuration. The default height of the window is the desktop height, and the default divider location is 100 pixels from the bottom of the split pane.
To set the divider location to 100px, I take the JSplitPane height - 100. The problem is, just before this I resize the JFrame, and since the code is in a button callback, the JSplitPane has been invalidated but not yet resized. So the divider location is set incorrectly.
Here is a SSCCE. Click the button twice to see the problem. The first click will resize the window, but the divider location remains the same (relative to the bottom of the window). The second click properly moves the divider, since the window size didn't change.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction("Resize to Default") {
#Override
public void actionPerformed(ActionEvent e) {
restoreDefaults();
}
}),BorderLayout.PAGE_END);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
}
I have thought of a few ways I might get around this, but they all seem sort of hackish. So far the best idea I've had has been to call f.validate() in between setting the frame size and setting the divider location, but I'm concerned there might be side effects to forcing validation early.
The other option I thought of is to use EventQueue.invokeLater() to put the call to set the divider location at the end of the event queue. But that seems risky to me - I'm assuming the JSplitPane will have been validated at that point, and I'm concerned that may be a faulty assumption to make.
Is there a better way?
Took a while (probably due to being early morning here :-) to understand the problem, so just to make sure I got it:
the size of the bottom component can be whatever the user decides at all times
when resizing the frame all height change should happen to the top component
there's an option to restore to default sizes, independent of any setting before
"default" means the bottom component must have a fixed height of xx
If so, the solution is to separate the frame resizing from the sizing the bottom component. Your second option is dead on: resize the frame and wrap the bottom comp resize into a invokeLater (EventQueue or SwingUtilities, doesn't matter).
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
sp.setDividerLocation(sp.getSize().height - 100);
}
});
}
That's guaranteed to work as expected, because the invokeLater puts the request as last after all already queued events:
/**
* Causes <i>doRun.run()</i> to be executed asynchronously on the
* AWT event dispatching thread. This will happen after all
* pending AWT events have been processed. [...]
* If invokeLater is called from the event dispatching thread --
* for example, from a JButton's ActionListener -- the <i>doRun.run()</i> will
* still be deferred until all pending events have been processed.
You could create a custom action class that handles the button click and the resize event. This approach would look like this:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
CustomListener resizeViaButtonListener = new CustomListener("Resize to Default");
f.getContentPane().add(new JButton(resizeViaButtonListener), BorderLayout.PAGE_END);
f.addComponentListener(resizeViaButtonListener);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
class CustomListener extends AbstractAction implements ComponentListener {
CustomListener(String actionDescription) {
super(actionDescription);
}
private boolean resizedViaButtonClick = false;
#Override
public void actionPerformed(ActionEvent arg0) {
resizedViaButtonClick = true;
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100);
// you need this also here because if the component is not resized when clicking the button
// it is possible that the divider location must be changed. This happens when the user clicks
// the button after changing the divider but not resizing the frame.
}
#Override
public void componentResized(ComponentEvent e) {
if ( resizedViaButtonClick ) {
resizedViaButtonClick = false;
sp.setDividerLocation(sp.getSize().height - 100);
}
}
#Override
public void componentHidden(ComponentEvent e) { /* do nothing */ }
#Override
public void componentMoved(ComponentEvent e) { /* do nothing */ }
#Override
public void componentShown(ComponentEvent e) { /* do nothing */ }
}
}
This way the code that is responsible for handling the logical task of setting the standard size will be in one single and easy to understand class.
nothing complicated, basic Swing Rules
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SSCCE sSCCE = new SSCCE();
}
});
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction(
"Resize to Default") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(sp.getLastDividerLocation());
restoreDefaults();
}
}), BorderLayout.PAGE_END);
f.setPreferredSize(new Dimension(400, 300));
f.pack();
f.setVisible(true);
}
void restoreDefaults() {
//EventQueue.invokeLater(new Runnable() {
// #Override
// public void run() {
f.setPreferredSize(new Dimension(f.getWidth(),
getDesktopRect(f.getGraphicsConfiguration()).height));
f.pack();
sp.setDividerLocation(sp.getSize().height - 100);
// Does not work on first button press
// }
//});
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top,
size.width - (insets.left + insets.right),
size.height - (insets.top + insets.bottom));
}
}
but I think pack() may be better than validate()
I generally try to avoid invoking setPreferredSize() on any component. I would rather let the layout manager do its job. In this case this would mean setting the size of the frame and let the BorderLayout take all the available space.
void restoreDefaults() {
// f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
f.setSize(f.getWidth(), bounds.height);
f.validate();
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}