I want to have titlescreen and open a game by clicking a button. When i call the play()-method of my project normally, it draws the first level. But when i call it with a button, a white screen appeares and I dont know why. Here is a code snippet:
package com.company;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Game implements ActionListener {
private String csvFile;
private JFrame titlescreen;
private JButton startButton;
public Game(String csvFile){
this.csvFile = csvFile;
}
public void prepare(){
titlescreen = new JFrame("Titlescreen");
titlescreen.setSize(100,100);
startButton = new JButton("Start Game");
startButton.addActionListener(this);
titlescreen.add(startButton);
titlescreen.setVisible(true);
}
public void play(){
titlescreen.setVisible(false);
if(csvFile!= null) {
LevelBuilder levelBuilder = new LevelBuilder(csvFile, ";", ",");
AllObstacles ao = new AllObstacles();
ao.addObstaclesOfString(levelBuilder.getLvlString(), ";", ",");
Goal g = new Goal(125, 125, 250, 250);
PlayGame c = new PlayGame(ao, g, levelBuilder);
c.create();
c.play();
}
}
#Override
public void actionPerformed(ActionEvent e) {
startButton.dispatchEvent(e);
play();
}
}
Can someone help me with my problem?
Please see you have play() method used inside of play() method. Please try to change ones name. Maybe you are just calling wrong method inside of actionPerformed() method.
If that won't work, you can try deleting whole play(){...} and actionPerformed(){...} methods from your snippet and delete line:
startButton.addActionListener(this);
Then you can just add this into your prepare() function:
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(csvFile!= null) {
titlescreen.dispose();
LevelBuilder levelBuilder = new LevelBuilder(csvFile, ";", ",");
AllObstacles ao = new AllObstacles();
ao.addObstaclesOfString(levelBuilder.getLvlString(), ";", ",");
Goal g = new Goal(125, 125, 250, 250);
SwingUtilities.invokeLater(() -> new PlayGame(ao, g, levelBuilder);
}
});
It will add to your button new ActionListener that:
disposes titlescreen (you do not need it running in background)
runs new PlayGame object in new thread (to avoid lags especially if you have any timer in your game)
Moreover:
c.create()
c.play()
you are using those two methods everytime you create new PlayGame object, so you should put them into constructor of PlayGame class, so:
public PlayGame(){
//your current code
create();
play();
}
Related
I want to set custom cursor in my java swing app, and then edit it.
I set a custom cusrsor after showing window (in "Window" class).
Later in code (in the other class), I want to chainge it again, so i call this updateCursor() funcion (in "Window" class again), and it and it won't work. There is no errors or warnings, but the cursor isn't changing - just stays the same. I tried, and I can't find answer anywhere. I appreciate any help.
This is full code - Window.java:
import MainMenu;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
public class Window {
public static final int WIDTH = 817, HEIGHT = 640;
JFrame frame = new JFrame("");
public void open() {
frame.pack();
frame.setVisible(true);
frame.setResizable(false);
frame.setSize(WIDTH - 33, HEIGHT - 25);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setFocusable(true);
frame.requestFocus();
frame.setFocusTraversalKeysEnabled(true);
frame.addKeyListener(new InputManager());
frame.addMouseListener(new InputManager());
frame.add(new MainMenu());
frame.add(new Game());
loadCursors();
updateCursor(0);
}
public static final int NORMAL = 0, ACTIVE = 1, INACTIVE = 2;
Cursor cursor_normal, cursor_active, cursor_inactive;
public void loadCursors() {
try {
cursor_normal = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_normal)), new Point(0, 0), "custom cursor (normal)");
cursor_active = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_active)), new Point(0, 0), "custom cursor (active)");
cursor_inactive = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_inactive)), new Point(0, 0), "custom cursor (inactive)");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void updateCursor(int cursorType) {
switch (cursorType) {
case NORMAL -> frame.setCursor(cursor_normal);
case ACTIVE -> frame.setCursor(cursor_active);
case INACTIVE -> frame.setCursor(cursor_inactive);
}
}
}
MainMenu.java:
import Window;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class MainMenu extends JPanel implements KeyListener {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
// testing
new Window().updateCursor(Window.ACTIVE);
}
#Override
public void keyReleased(KeyEvent e) {
}
}
You can't do ...
new Window().updateCursor(Window.ACTIVE);
and magically expect the other instance of Window to be updated, in fact, you don't need to do this at all.
This is going to create another instance/copy of Window, which is not present on the screen and it will have no effect on the instance which is been displayed.
You could call setCursor directly on the instance MainMenu.
Now, if you want to "centralise" the functionality, I would start by creating a "manager" class, for example...
public class CursorManager {
public enum CusorType {
NORMAL, ACTIVE, INACTIVE;
}
private Cursor cursorNormal, cursorActive, cursorInactive;
public CursorManager() throws IOException {
cursorNormal = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_normal)), new Point(0, 0), "custom cursor (normal)");
cursorActive = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_active)), new Point(0, 0), "custom cursor (active)");
cursorInactive = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_inactive)), new Point(0, 0), "custom cursor (inactive)");
}
public void setCursor(CusorType cursorType, Component comp) {
switch (cursorType) {
case NORMAL ->
comp.setCursor(cursorNormal);
case ACTIVE ->
comp.setCursor(cursorActive);
case INACTIVE ->
comp.setCursor(cursorInactive);
}
}
}
I would then create this instance of the manager during the initialisation phase of your code
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
CursorManager cursorManager = new CursorManager();
//.. Every thing else...
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
And then pass this instance to every class that might need it...
// You'll need to update Window to accept this parameter
new Window(cursorManager).open();
And...
public class MainMenu extends JPanel implements KeyListener {
private CursorManager cursorManager;
private MainMenu(CursorManager cursorManager) {
this.cursorManager = cursorManager;
}
#Override
public void keyPressed(KeyEvent e) {
// testing
cursorManager.setCursor(CursorManager.CusorType.ACTIVE, this);
}
//...
}
This is commonly known as "dependency injection" and is VERY powerful
Feedback
Just as a side note, if I was doing something like this, it would be very different, but I tried to keep it simple.
We're generally discouraged from extending from top level containers like JFrame, as stated, JFrame is not a simple component and you're not actually adding any new functionality to the class and in the process, locking yourself into a single use, there by reducing re-usability. Better to start with a JPanel as your base component and simply create an instance of JFrame or `JDialog or what ever top level container you want to use, when you need it
KeyListener is a poor choice for monitoring keyboard input (seriously, just do a search for "my key listener won't work". Instead, take a look at How to Use Key Bindings
You are creating a new instance of your Window class in your keyPressed(..) method, therefore your cursor update does not work:
new Window().updateCursor(Window.ACTIVE);
You need to pass your existing Window instance to your MainMenu class and call this instance.
Here's a working example:
Main App:
public class MyApp extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MyApp app = new MyApp();
app.setVisible(true);
}
});
}
private MyApp() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 600);
// Create cursors
Cursor c1 = Util.getCursor("c1.png");
Cursor c2 = Util.getCursor("c2.png");
setCursor(c1);
JButton button1 = new JButton("Change to Cursor1");
button1.setActionCommand("c1");
button1.addActionListener(new MyActionListener(this));
JButton button2 = new JButton("Change to Cursor2");
button2.setActionCommand("c2");
button2.addActionListener(new MyActionListener(this));
add(button1, BorderLayout.NORTH);
add(button2, BorderLayout.SOUTH);
}
}
ActionListener (handles the button clicks):
public class MyActionListener implements ActionListener {
private JFrame jFrame;
public MyActionListener(JFrame jFrame) {
this.jFrame = jFrame;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()){
case "c1":
jFrame.setCursor(Util.getCursor("c1.png"));
System.out.println("switch to c1");
break;
case "c2":
jFrame.setCursor(Util.getCursor("c2.png"));
System.out.println("switch to c2");
break;
}
}
}
Util (read cursor image):
public class Util {
public static Cursor getCursor(String fileName) {
BufferedImage img = null;
try {
img = ImageIO.read(Util.class.getResourceAsStream(fileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
return Toolkit.getDefaultToolkit().createCustomCursor(img, new Point(0, 0), fileName);
}
}
I have been trying to create a 'catch me if you can' game: when I start it, it randomly chooses where to allocate a 'click me' button. I am not supposed to be able to click the button, the text should be re-assigned to another button before I am able to do that.
It works for a while but then it throws the following error: "java.awt.AWTEventMulticaster.mouseMoved".
I have been trying to fix the problem with removeListener() method but I don't seem to be able to find a solution. Any comments?
Here's my code:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.lang.*;
public class Game extends JFrame {
//Panels
private JPanel mainPanel = new JPanel();
// Buttons
private JButton[] buttons = new JButton[9];
private JButton theChosenButton = new JButton();
// other
private int random = 0;
public Game() {
this.setTitle("Catch me if you can");
mainPanel.setLayout(new GridLayout(3, 3));
// creates buttons
for(int i = 0; i < 9 ; i++) {
buttons[i] = new JButton();
mainPanel.add(buttons[i]);
}
// Add everything to frame
this.getContentPane().add(mainPanel);
this.setSize(400, 400);
this.setVisible(true);
}
// generates random number between 1 and 9 to be used
public int clickMeGenerator(){
random = (int) Math.floor(Math.random() * 9);
return random;
}
// randomly assigns clickMeGenerator to a button
// add mouseMoved listener to the chosen button
public void assign(){
int randomButton = this.clickMeGenerator();
theChosenButton = buttons[randomButton];
theChosenButton.addMouseMotionListener(new MouseHover());
theChosenButton.setText("Click me");
}
public void removeListener() {
theChosenButton.removeMouseMotionListener(new MouseHover());
//}
}
// inner class
class MouseHover implements MouseMotionListener {
public void mouseMoved(MouseEvent e) {
theChosenButton.setText("");
Game.this.assign();
}
public void mouseDragged(MouseEvent e) {
}
}
} // end of class
Test class:
public class GameTest {
public static void main (String args[]) {
Game myGame = new Game();
myGame.assign();
}
}
Thank you so much for your help!
Just for clarity, the "actual" error is ...
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
at java.desktop/java.awt.AWTEventMulticaster.mouseMoved(AWTEventMulticaster.java:337)
at java.desktop/java.awt.AWTEventMulticaster.mouseMoved(AWTEventMulticaster.java:337)
So looking through the code...
public void assign() {
int randomButton = this.clickMeGenerator();
theChosenButton = buttons[randomButton];
theChosenButton.addMouseMotionListener(new MouseHover());
theChosenButton.setText("Click me");
}
You are repeatedly add a new MouseMotionListener to you buttons, over and over again, and...
public void removeListener() {
theChosenButton.removeMouseMotionListener(new MouseHover());
//}
}
is pointless, as you're trying to remove a new instance of MouseHover from the button, but it will never have been applied in the first place.
The first thing I would do is create an instance of MouseHover as an instance field in Game
private MouseHover mouseHover = new MouseHover();
and use it when calling addMouseMotionListener and removeMouseMotionListener.
I would then, remove the listener from the "currently" active button before adding it to the next one.
Personally, I would do this in the assign method
public void assign() {
int randomButton = this.clickMeGenerator();
if (theChosenButton != null) {
theChosenButton.removeMouseMotionListener(mouseHover);
}
theChosenButton = buttons[randomButton];
theChosenButton.addMouseMotionListener(mouseHover);
theChosenButton.setText("Click me");
}
I would also ensure that assign is called from within the Event Dispatching Thread when the class is first created, as the UI has been realised by the end of the constructor of Game, meaning the first call to assign is outside of the context of the EDT, which is not recommended.
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Game myGame = new Game();
myGame.assign();
}
});
}
I have a problem with one of my frames not looking as it should, when it is called upon the press of a button.
The frame looks as if it was rendered improperly, the label text in it is shortened, however when i move the same line of code outside the action listener, it works as it should.
I have a sort of main menu, with two buttons, only the Generate Menu works at the moment, it looks like this:
https://i.imgur.com/k1Ne5v9.png
The code for the action listener:
runMenuButt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Generate Menu pressed");
mF.dispose();
MenuGenerator.generateTheMenu();
}
});
The result looks wrong: https://i.imgur.com/n86y4CD.png
The frame is also unresponsive, clikcing X does not, while it should close the frame and the application.
However changing the code to:
runMenuButt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Generate Menu pressed");
//mF.dispose();
}
});
MenuGenerator.generateTheMenu();
Produces correct look: https://i.imgur.com/TFbkmAO.png
The code for the "Main menu"
public static void openMainMenu() {
Font menuFont = new Font("Courier",Font.BOLD,16);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mF.setSize(465,230);
mF.setLocation(dim.width/2-mF.getSize().width/2, dim.height/2-mF.getSize().height/2);
mF.getContentPane().setBackground(Color.WHITE);
Color blueSteel = new Color(70,107,176);
JPanel p = new JPanel();
p.setSize(600,50);
p.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
p.setLocation((mF.getWidth() - p.getWidth()) /2, 20);
p.setBackground(blueSteel);
JLabel l = new JLabel("Welcome to the menu GENERATORRRR");
l.setFont(menuFont);
l.setForeground(Color.WHITE);
p.add(l, gbc);
JButton runMenuButt = new JButton("Generate Menu");
runMenuButt.setLocation(20 , 90);
JButton manageRecipButt = new JButton("Manage Recipients");
manageRecipButt.setLocation(240 , 90);
menuUtilities.formatButton(runMenuButt);
menuUtilities.formatButton(manageRecipButt);
mF.setResizable(false);
mF.setLayout(null);
mF.add(runMenuButt);
mF.add(manageRecipButt);
mF.add(p);
mF.setVisible(true);
runMenuButt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Generate Menu pressed");
//mF.dispose();
}
});
MenuGenerator.generateTheMenu();
manageRecipButt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "Not supported yet", "Function not yet available",JOptionPane.INFORMATION_MESSAGE);
}
});
//System.out.println(mF.getContentPane().getSize());
}
And the status bar:
public class StatusBar {
private static JLabel statusLabel= new JLabel("Starting");
private static JFrame statusFrame = new JFrame("Generation Status");
public static void createStatusBar() {
Font menuFont = new Font(Font.MONOSPACED,Font.BOLD,20);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
statusFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
statusFrame.setSize(700,100);
JPanel p = new JPanel();
p.setPreferredSize(new Dimension(100,100));
statusLabel.setFont(menuFont);
p.add(statusLabel);
statusFrame.add(p,BorderLayout.CENTER);
statusFrame.setLocation(dim.width/2-statusFrame.getSize().width/2, dim.height/2-statusFrame.getSize().height/2);
statusFrame.setVisible(true);
}
public static void setStatusBar(String statusText) {
statusLabel.setText(statusText);
statusLabel.paintImmediately(statusLabel.getVisibleRect());
statusLabel.revalidate();
}
public static void closeStatusBar(){
statusFrame.dispose();
}
}
I create the bar with this line:
StatusBar.createStatusBar();
Why does the status bar not render properly when the MenuGenerator.generateTheMenu(); is called from the action listener?
Here is minimal code that reproduces this behavior for anyone who would like to test it: It also uses class for the StatusBar, which is already posted.
public class MinimalClass {
private static JFrame mF = new JFrame("Main Menu");
public static void main(String[] args) {
openMainMenu();
}
public static void openMainMenu() {
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mF.setSize(465,230);
mF.setLocation(dim.width/2-mF.getSize().width/2, dim.height/2-mF.getSize().height/2);
mF.getContentPane().setBackground(Color.WHITE);
JButton runMenuButt = new JButton("Generate Menu");
runMenuButt.setLocation(20 , 90);
runMenuButt.setSize(200 , 85);
mF.setResizable(false);
mF.setLayout(null);
mF.add(runMenuButt);
mF.setVisible(true);
runMenuButt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Generate Menu pressed");
mF.dispose();
generateTheMenu();
}
});
}
public static void generateTheMenu() {
System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");
String rawMenuOutput = "";
try {
rawMenuOutput= getMenuInJavaNow();
} catch (Exception e){
System.out.println("Something went terribly wrong");
}
System.out.println(rawMenuOutput);
}
public static String getMenuInJavaNow() throws IOException {
String rawMenuOutput = "Restaurant Menu" ;
rawMenuOutput = rawMenuOutput + "Test line";
String []menuOtpArr = new String [3];
try {
StatusBar.createStatusBar();
TimeUnit.SECONDS.sleep(2);
StatusBar.setStatusBar("Test1");
TimeUnit.SECONDS.sleep(2);
menuOtpArr[0]="Test line";
StatusBar.setStatusBar("Test2");
TimeUnit.SECONDS.sleep(2);
menuOtpArr[1]="Test line";
StatusBar.setStatusBar("Test3");
TimeUnit.SECONDS.sleep(2);
menuOtpArr[2]="Test line";
StatusBar.setStatusBar("Test4");
TimeUnit.SECONDS.sleep(2);
StatusBar.closeStatusBar();
} catch (Exception e) {
}
for (int i=0;i < menuOtpArr.length;i++) {
rawMenuOutput = rawMenuOutput + "\n\n" +menuOtpArr[i];
}
return rawMenuOutput;
}
}
Thank you for your time
statusLabel.paintImmediately(statusLabel.getVisibleRect()); seems to masking a larger issue.
The problem is, Swing is single threaded (and NOT thread safe). This means that when you call TimeUnit.SECONDS.sleep(2); from within getMenuInJavaNow, which is called by generateTheMenu, which is called by the ActionListener, it's been called within the context of the Event Dispatching Thread.
This is putting the EDT to sleep, meaning that it isn't processing layout or paint requests (properly)
Start by having a read of Concurrency in Swing for more details
Now, you have a larger issue, how to solve it. For the answer to that question, we require a lot more context then is currently available.
The getMenuInJavaNow seems to be returning some values, to what end I'm not sure.
"A" solution, would be to use a SwingWorker (see Worker Threads and SwingWorker for more details). It provides the ability to execute long running tasks in the background, but also provides the means for sync updates back to the UI, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class MinimalClass {
private static JFrame mF = new JFrame("Main Menu");
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
openMainMenu();
}
});
}
public static void openMainMenu() {
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mF.setLocationRelativeTo(null);
mF.getContentPane().setBackground(Color.WHITE);
JButton runMenuButt = new JButton("Generate Menu");
runMenuButt.setMargin(new Insets(25, 25, 25, 25));
JPanel buttons = new JPanel(new GridLayout(0, 2));
buttons.add(runMenuButt);
mF.add(buttons);
mF.pack();
mF.setVisible(true);
runMenuButt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Generate Menu pressed");
mF.dispose();
generateTheMenu();
}
});
}
public static void generateTheMenu() {
System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");
StatusBar.createStatusBar();
SwingWorker<String, String> worker = new SwingWorker<String, String>() {
#Override
protected String doInBackground() throws Exception {
String rawMenuOutput = "Restaurant Menu";
rawMenuOutput = rawMenuOutput + "Test line";
String[] menuOtpArr = new String[3];
try {
TimeUnit.SECONDS.sleep(2);
publish("1234567890123456789012345678901234567890");
TimeUnit.SECONDS.sleep(2);
publish("This is a test");
TimeUnit.SECONDS.sleep(2);
publish("More testing");
TimeUnit.SECONDS.sleep(2);
publish("Still testing");
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
}
for (int i = 0; i < menuOtpArr.length; i++) {
rawMenuOutput = rawMenuOutput + "\n\n" + menuOtpArr[i];
}
return rawMenuOutput;
}
#Override
protected void done() {
StatusBar.closeStatusBar();
}
#Override
protected void process(List<String> chunks) {
StatusBar.setStatusBar(chunks.get(chunks.size() - 1));
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (worker.getState() == SwingWorker.StateValue.DONE) {
try {
String result = worker.get();
System.out.println(result);
StatusBar.closeStatusBar();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
}
}
});
worker.execute();
}
public static class StatusBar {
private static JLabel statusLabel = new JLabel("Starting");
private static JFrame statusFrame = new JFrame("Generation Status");
public static void createStatusBar() {
Font menuFont = new Font(Font.MONOSPACED, Font.BOLD, 20);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
statusFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
statusFrame.setSize(700, 100);
JPanel p = new JPanel();
p.setBackground(Color.RED);
// p.setPreferredSize(new Dimension(100, 100));
statusLabel.setFont(menuFont);
p.add(statusLabel);
statusFrame.add(p, BorderLayout.CENTER);
statusFrame.setLocationRelativeTo(null);
statusFrame.setVisible(true);
}
public static void setStatusBar(String statusText) {
statusLabel.setText(statusText);
}
public static void closeStatusBar() {
statusFrame.dispose();
}
}
}
Observations...
static is not your friend, especially in cases like this. You really, really, really need to learn to live without it.
setLayout(null) is not doing you any favours, especially in the long run. Take the time to go through Laying Out Components Within a Container and start making proper use of layout managers, they might seem "complicated", but they will save you from a lot of hair loss
Avoid using setPreferred/Minimum/MaximumSize where ever possible, you are robing the component of the ability to provide useful rendering hints which may change across platforms and rendering pipelines
Just a quick follow up question, what is the difference between done and addPropertyListener ? Is there any? Isnt it redundant to use both?
The example here is pretty basic, for me I've used done to handle what the SwingWorker "knows" needs to be done, it doesn't however, know what is to be done with the result.
I've used the PropertyChangeListener to deal with that instead - the point - it's an example.
And I also noticed, that I dont need to actually publish, as calling StatusBar.setStatusBar(""); works as well. Is it necessary to use publish?
In a word YES. Swing is NOT thread safe, calling StatusBar.setStatusBar("") directly can lead to some weird and unexpected results. publish pushes the call into the Event Dispatching Thread, making it safe to update the UI from within.
I have the code for generating the String I want to set as the StatusBar Title in another class, not in the generateTheMenu, therefore it is more convenient for me to simply call .setStatusBar. The not minimal code I have is actually something like this
This is where things like interfaces come in really handy. You "string" generating class "could" either return the resulting text OR you could pass a reference to a interface implementation which is used to "display" it. This way, your SwingWorker could act as a consumer for the String and pass it through the publish method.
There are a number of really important concepts to understand.
You want to decouple your code. This makes it easier to change certain parts of the code without affecting the other parts
You want to be able to "code to interface, not implementation". This goes hand in hand with the first comment. Basically, you want to "hide" the implementation details as much as possible - lots of different reasons for it, but it helps keep your code lean, helps make the follow more understandable and stops one part of the code from accessing another it really has no responsibility to do so (is the string generation really responsible for updating the status bar? IMHO - not really)
There is also a swagger of design patterns available to make solving issues easier. I've already mentioned the concept of "produce/consumer", but this is just one
The "Event Dispatching Thread"
The Event Dispatch Thread
Java Event-Dispatching Thread explanation
Swing threading and the event-dispatch thread
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
class Demo implements ActionListener
{
JFrame f;
JButton b;
DisplayDialog dialog;
public Demo()
{
f = new JFrame();
f.setSize(200,200);
b = new JButton("Click me");
f.add(b);
dialog = new DisplayDialog();
b.addActionListener(this);
f.setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
Object o = e.getSource();
if(o==b)
{
dialog.display("Hello");
dialog.display("Hello");
dialog.display("Hello");
dialog.display("Hello");
dialog.display("Hello");
}
}
public static void main(String args[])
{
Demo d = new Demo();
}
class DisplayDialog implements ActionListener
{
JDialog dg;
JLabel l;
JButton b;
Font myfont;
public DisplayDialog()
{
dg = new JDialog(f,"Alert!",true);
dg.setSize(300,150);
l = new JLabel("Message");
b = new JButton("OK");
myfont = new Font("Serif",Font.BOLD,12);
l.setFont(myfont);
dg.add(l);
dg.add(b,"South");
dg.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
b.addActionListener(this);
}
public void actionPerformed(ActionEvent e)
{
Object o = e.getSource();
if(o==b)
{
dg.setVisible(false);
}
}
public void display(String str)
{
if(dg.isVisible())
dg.setVisible(false);
l.setText(str);
dg.setVisible(true);
}
}
}
When the button is clicked, 5 Dialog box appears. Now I want to make such a change in the DisplayDialog class so that every time display method is called,the previously opened dialogs are closed and the latest one is the only one that is visible.
I tried doing this :
public void display(String str)
{
if(dg.isVisible())
dg.setVisible(false);
l.setText(str);
dg.setVisible(true);
}
but it didn't do anything.
Also,I am very surprised that multiple dialog boxes are appearing even though there is only one instance of the object. From my understanding, setVisible(true) should not do anything if the dialog is already visible.
This whole situation is very confusing to me.
The thing is that you cannot do this in single thread (like you are preseting it in your demo) - setVisible will always block (wait till dialog closes) and another display call will not be invoked till then.
Try to run it in multi frame env (like you have stated that your app is) but change display to
public void display(String str){
l.setText(str);
if(dg.isVisible()){
dg.setVisible(true);
}
}
This way dialog will not be displayed if it is already visible, but text will be changed. The condition for it to work is that is is not consecutinve calls to display like in demo, but from different threads. There is no other way around. setVisible will alwys block.
In our code that is linked by a JButton in a previous frame, part of it that is inside of braces does not run.
I can tell because the JFrame.EXIT_ON_CLOSE is not working because the program does not terminate. If the braces are taken away, other errors appear.
How do we make it so that this code will run?
public class PeopleCreator extends PeopleMove {
public static final JFrame PeopleFrame = new JFrame("The Lovely Couple");
ImageIcon girl = new ImageIcon();// pic of girl
ImageIcon boy = new ImageIcon();// pic of boy
String name = JOptionPane.showInputDialog("What is your name?");
JButton girlDialogue1 = new JButton("Hey " + name
+ "! Hey can you get something to drink");
// This code below does not run.
{
PeopleMove.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PeopleMove.add(girlDialogue1, BorderLayout.SOUTH);
girlDialogue1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent z) {
JButton boyDialogue1 = new JButton("");// girls first text
{
PeopleMove.add(boyDialogue1);
boyDialogue1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent y) {
JButton girlDialogue2 = new JButton("");// boys
// first
// text
PeopleMove.add(girlDialogue2);
girlDialogue2
.addActionListener(new ActionListener() {
public void actionPerformed(
ActionEvent x) {
JButton boyDialogue2 = new JButton(
"");
PeopleMove.add(boyDialogue2);
boyDialogue2
.addActionListener(new ActionListener() {
public void actionPerformed(
ActionEvent v) {
PeopleMove.pack();
PeopleMove
.setVisible(true);
}
});
}
});
}
});
}
}
});
}
}
Code in your braces will be executed every time you create new instance of PeopleCreator with the new keyword (it is appended to every constructor of current class). I am guessing that you are not creating new peopleCreator anywhere but you are refering to static field holding JFrame.