Java: change JPanel while saving game - java

I'm making a small game and I've already implemented a save function in which the game is saved (by writing information to a new XML file). The saving takes a couple of seconds and I want to do the following: while the program is saving the game, I want to change the look of the JPanel, and when it is done saving, I want to go back to another page(show another JPanel).
I have the following code:
confirm.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e){
String fileNaam = saveGame.getText();
//This method changes the look of the panel
changePanel();
//This method saves the game
model.saveGame(fileNaam);
//This method takes the user back to a previous page
controller.viewTeamPage();
}
});
What happens is that the game is saved and the user is taken back to the teampage, but the panel is never changed. The changePanel() method does work, so that is not the problem but it seems like it is never executed. I was wondering if somebody knows how I can fix this.
EDIT:
private void changePanel () {
panel.removeAll();
panel.repaint();
panel.revalidate();
}
This is the method to change the look of the panel, for now I just remove everything on the panel to keep it simple.
Also, the saving is not done in a separate Thread, is that something I should look at?
EDIT 2: I fixed it by using a thread to save the game and return to the teampage after the saving is done. See the code below.
confirm.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e){
final String fileNaam = saveGame.getText();
changePanel();
Thread t = new Thread (new Runnable () {
#Override
public void run() {
model.saveGame(fileNaam);
controller.viewTeamPage();
}
});
t.start();
}
});

If you are changing the same panel and not intializing a new panel then the problem i think is that you need to call the panel.revalidate or panel.repaint i think. I made a demo for a Procedural generation project and i had to do this to make my panel change.

Call your save game method from a new thread but don't "join" or "try" to wait for this thread to finish from inside the method actionPerformed();
Make the call to controller.viewTeamPage() after the save game thread is done saving the game. One simple way of doing that would be passing the "controller" object to the constructor of your custom thread so you can make that call after saving the game.
The step 1 is very important in this case because all the calls you are making in the method actionPerformed() are being made in the UI thread, preventing the entire UI from refreshing until the method returns. Even calling repaint() alone, in changePanel(), wont be enough because it just "schedules" a refresh on you panel that will only happen after actionPerformed() returns. If you put the most time consuming call in a separate thread however, the actionPerformed() returns quickly allowing the UI to be refreshed while the game saving thread is doing its job.

Related

passing objects to another thread

i am creating a GUI application and in the background I want to run an additional task. I will paste some code, to prevent pasting a mess of code that was generated by Swing, I will leave some parts out, assume that the window.java is working as intended.
window.java:
public class window {
frame = new JFrame();
JLabel lbl1 = new JLabel("Start Counter");
frame.add(lbl1);
Thread counter = new Thread(new counter());
counter.start();
}
counter.java
public class regCheck extends window implements Runnable
{
public void run()
{
int i = 0;
while (true)
{
window.lbl1.setText(i);
try {Thread.sleep(1000);}
catch (InterruptedException e) {e.printStackTrace();}
i++;
}
}
}
what I want this example to do is create a label within a window and count upwards until the program is closed.
The easy answer here is to say "pass in the Jlabel" however in reality I have multiple things that I need to change not just a label.
the line "window.lbl1.setText(i);" does not work here, it is just to illustrate what I want to achieve.
Use the MVC pattern. Create a model that has the counter with a setValue() method that fires a listener notification. You can extend java.util.Observable to make that easier to do. Add a getValue() method to retrieve the new count. Make the setter and getter synchronized for thread safety.
Now your thread can be passed an instance of the model and call setValue() to update the value in its run() method.
Finally, your view can be passed the same instance of the model and add a listener to it. To make it easier your view can implement java.util.Observer. In the listener update() callback within the view, call the model's getValue() and use the return as the argument to setText(). Since the listener update is not being invoked from the AWT event dispatcher thread, you have to call setText() using javax.swing.SwingUtilities.invokeLater() in order to meet the thread safety requirements of Swing.
it is actually architectural question, you can pass any arguments to other thread and this thread of course could modify different labels, but I prefer another variant:
you have window, which has its objects/controls and can manipulate them
you have separate thread which increase counter
this separate thread should notify main window object about changes and window object should change it's controls accordingly, for example change text in one or several controls
one simple variant is to have interface, like ICounterHandler with one method
void onCounterChanged(int newCounterValue);
counter thread should accept ICounterHandler in constructor, save it and call this method when needed, preferable asynchronous
of course there are many other variants, but you can start with this one

JButtons AWT control java

I have two basic questions.
I have a GUI project with Java Swing. When I put buttons on the frame and I double clicked them, I had the code of the actionPerformed, but it is blocked.
How can I put there a button and then use it on a actionListener?
My Project is about Server-client (multithread and sockets)
I call one method to reiceve one string that we can write on a JtextField and it stays on a while cicle with PrintWriter and a getOutputStream.
Something like:
do{
...
}while(thisstring!=null || thisstring!="exit")
So.. when I write something and press the button to send it, it stays on the cicle and the button blocks. How can I unblock the button to write something else?
Edit:
I understood the EDT problem, but I can't solve it.
I tried use the Timer but without success, something like that:
int delay = 1000; //milliseconds
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
//My action calling the Thread class with the while cicle that has the PrintWriter
}
};
new Timer(delay, listener).start();
How can I handle this to do the timer when I press the button?
How can I stay on the that cicle (read the comment line) to send the information by OutputStream every time that one user enter something on the text field?
I know that for example for a console application I use a BufferedReader and then I use the ReadLine() to wait for anything sent from the console, but with GUI interface it freezes all time..
There is a fundamental concept in Java GUI development surrounding which thread in which the developer implements user-interaction processing such as button clicks.
In short, you need to perform your processing outside of the thread that calls your action handling method. This single thread is known as the Event Dispatch Thread (EDT), and if you have logic that runs much more than a few milliseconds, it will prevent the UI from continuing to draw things like the button releasing, etc.
You'll want to move your long-running, socket code off the EDT. Doing so will allow the button to release and let the user interact with other controls (or even the same button).
To avoid duplicating other discussions on the topic, I direct you to this pretty good one. Additionally, this article gives a short overview of threading concepts in Swing.
Regards,
ScottH
According to your comment you have some naming issues there. You need a class that implements the ActionListener-interface like so: class YourListenerClass implements ActionListener, but you could also do that via an anonymous class like new ActionListener {
public void actionPerformed(ActionEvent e) {
//your code for your button here
}
});
when you set your ActionListener.
The crucial thing is that you need to name your method the correct way. It MUST be public void actionPerformed(ActionEvent e) and you definitely have to implement the ActionListener-interface.
The next thing is that you have to register your listener in your button like:
yourButton.addActionListener(new YourListenerClass);
or
insert an anonymous class like I showed to you before.
The 2nd thing sounds like an multithreading issue like I mentioned in my comment. I didnt follow scotth's link, but according to his description this might be a source you want to read to solve any further blocking issues.
EDIT:
Well, at first I didn't want to explain it, because it's quite a chunk of code, but as the problem persists I want to add something about SwingWorkers in my answer.
If you have long running code, it wont help to use a Timer as the code invoked by it will also be on the EDT as it's triggered by an event.
Instead of that you could use a SwingWorker to solve this. This needs some extra code, though.
Here's a simple approach you could follow:
public class WorkingHard{
SwingWorker<String, String> worker;
JButton yourButton = ...;
...
//do some cool stuff, as register those listeners!
...
public void actionPerformed(ActionEvent evt){
if(evt.getSource().equals(yourButton);
// Construct a new SwingWorker
worker = new SwingWorker<String, Void>(){
#Override
protected String doInBackground(){
//do your reading in this method, it will be executed in an own thread
String readText = "i will be read".
/*your reading algorithm, you could also call publish(...) to post your results,
e.g. likewise), then you also have to override process(...). this process will be
thread save, too*/
readText += ... ;
...
return readText;
}
#Override
protected void done(){
try {
//do sth. with your result, now thread safe.
someGuiElement.setText(get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
// Execute the SwingWorker; the GUI will not freeze
worker.execute();
}
}
If you want to know more about those workers... there several threads dealing about it, e.g. this one.

Start a game from within an action listener

I have a Blackjack game that I've made in Java and I want to signal the start of the game by clicking a button. All my action listeners work just fine and all that, but the problem lies in that I can't figure out how to start the game without it running completely within the actionPerformed method. Obviously, a function continuously running within the actionPerformed method will effectively disable the rest of my GUI. Here's a code snippet....
go.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// START GAME SOMEHOW but must run outside of action listener
}
});
Obviously, a function continuously running within the actionPerformed method will effectively disable the rest of my GUI.
This is a valid observation and shows that you have understand the fundamental rule when working with Swing.
Your game is most likely event driven (correct me if I'm wrong) so the action performed by the button should just set the program in a new state, waiting for further events. This is nothing that should be time consuming, and is typically done directly by the EDT.
Of course, if you want to do a fancy start-new-game animation, that needs to be performed in a separate thread, in which case you simply start the animation thread (I would recommend using a SwingWorker though) from within the actionPerformed method, and then return.
In code, I imagine it would look something like this:
go.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Remove the menu components
someJPanel.removeAll();
// Show the game table
someJPanel.add(new GamePanel());
someJPanel.revalidate();
someJPanel.repaint();
// done. Wait for further user actions.
}
});
You game should probably start in its own thread and manage that itself (hard to say), but to get you going you could start your game in a new "external" thread, something like this in your actionPerformed:
public void actionPerformed(ActionEvent e) {
Thread thread = new Thread("Game thread") {
public void run() {
startGame(); //or however you start your game
}
};
thread.start();
}
I believe that you want to extend javax.swing.SwingWorker.
The non-ui start-up functionality would run in doInBackground and the done method would be called when it finishes to update the ui.
There's even an example in the javadoc Class Description to update a progressbar with the status of what's happening in start-up.

Javax Swing Timer Help

I am having some problems concerning starting javax.swing.Timer after a mouse click. I want to start the timer to perform some animation after the user clicks on a button but it is not working.
Here are the code snippets:
public class ShowMe extends JPanel{
private javax.swing.Timer timer;
public ShowMe(){
timer = new javax.swing.Timer(20, new MoveListener());
}
// getters and setters here
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// some code here to perform the animation
}
}
}
This is the class which contains a button so that when the user clicks on the button the timer starts to begin the animation
public class Test{
// button declarations go here and registering listeners also here
public void actionPerformed(ActionEvent e) {
if(e.getSource() == this.btnConnect){
ShowMe vis = new ShowMe();
vis.getTimer().start();
}
}
}
I want to start the timer to begin the animation but it is not working.
Need help how to make a timer start after button click.
Thanks.
You must call the start() method of the timer to start it.
public ShowMe(){
timer = new javax.swing.Timer(20, new MoveListener());
timer.start();
}
EDIT:
I have not seen that start() is being called in the Test class...
Next step would be to add some logging/printing to the MouseListener class to check if it is being called or not
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("MouseListener activated"); // TODO delete this line
// some code here to perform the animation
}
}
If it's running (I can't find any reason why not in the posted code), the problem is as Ash wrote above:
You created a new instance assigned to vis and started its Timer, but you have not added that instance to any visible container.
(maybe you added another instance of ShowMe earlier in the code...)
Some things to try:
Check that your panel is visible, e.g. make the background color red.
Check that the animation is being updated. For example, if you are animating by drawing different frames in a paint() method, then you will need to call repaint() in your timer, after updating the variables controlling animation. Alternatively, if animation is done by changing layout properties (e.g. to move a component around) then a call to validate() will be needed.
Using swing timer can get you started, but it's really the bare underpinnings. There are also libraries avaialbe that will allow you to go further with less effort:
animated transitions
Trident animation library
I know this question is a bit old, but I don't think you got an answer.
I believe the problem is that the ShowMe class and its Timer is being garbage collected, and hence fails to do what you think it should.
You are creating a new local ShowMe variable that goes out of scope as soon as the actionPerformed method completes. The Timer and its ActionListener are local to the ShowMe class instance, so when the actionPerformed method completes, they are also available for GC.
I'm not sure what the ShowMe class is doing. It appears to be a JPanel, so I assume it is something you want to display. It sounds like in your Test class (or real class), it might be better to have a ShowMe data member that you can just call start one when the button is clicked, instead of creating a new one every time.
Your usage of the Timer class seems to be correct. Maybe the problem lies in the MoveListener.
Did you remember to use the paintImmediately() method to repaint your animation?
If you use just repaint() you won't see a smooth animation, since repeated calls to repaint() are reduced to one call.

What is the correct way of manipulating Swing components at program startup?

I'm creating an application in Swing using NetBeans. I would like to be able to manipulate some components during its startup (just once), after the window's made visible, for example update a progress bar. To this end, I have the app's main class, called MainWindow:
public class MainWindow extends JFrame
{
public MainWindow()
{
initComponents(); // NetBeans GUI builder-generated function for setting
// up the window components
}
public void Init()
{
loadLabel.setText("Loading....");
loadProgressBar.setValue(20);
doSomething();
loadProgressBar.setValue(40);
doSomething();
loadProgressBar.setValue(80);
doSomething();
loadProgressBar.setValue(100);
loadLabel.setVisible(false);
loadProgressBar.setVisible(false);
}
/* .... */
public static void main(String args[])
{
java.awt.EventQueue.invokeLater(new Runnable()
{
public void run()
{
mainHandle = new MainWindow();
mainHandle.setVisible(true);
mainHandle.Init();
}
});
}
}
The problem is that the effect of the statements for updating the progress bar (or manipulating any other GUI component) within the Init() function can't be observed. If the Init() function is called from within main() as shown above, the window appears, but is empty, the Init() function executes and returns, only afterwards the window draws its contents but any changes made by Init() aren't visible because the window was empty and inactive the whole time. I also tried calling init from the windowOpened() AWT event, which executes after the window is fully drawn, but amazingly putting any statements for manipulating components there seems to have no effect, or rather they are put in a queue, and executed rapidly at some point in succession, so only the effect of the last one (hiding of the elements) can be observed. The only way I managed to get it working was to remove the whole invokeLater(new Runnable()...) mantra and put the new MainWindow(), setVisible(), Init() sequence directly in main(), which I guess is very ugly and breaks the concept of the gui running in a threaded manner. What is the right way to do this? Where do I put code to be executed first thing when the gui is ready to be manipulated, execute the statements once and return control to the main event loop?
I guess at the moment this is working in such a way, that while the Init() function is operating, any operations on the gui components are suspended (the drawing thread isn't separate and waits for Init() to finish before the manipulations are executed). Maybe I should make Init() a new thread... only how and what kind?
Thanks.
You could change the EventQueue.invokeLater() to invokeAndWait(), and move the call to init() out to a second EventQueue.invokeLater() call.
If (as looks to be the case) doSomething() takes a noticable amount of time, a better idea is to move the Init code into the body of a SwingWorker. This could be executed from the MainWindow() constructor or after the setVisible() call in main and is the idiomatic way to have a responsive GUI (in case the user gets bored waiting and wants to quit) and display some visible signs of progress.
See the process and publish methods for details on how to update the progress bar between doSomething() calls.
You may also want to look into ProgressMonitors for another alternative that would deal with the dialog box etc for you.
There are several things you can do:
For windows (such as JFrame or JDialog) you can attach WindowListener and do your manipulations in windowOpened method.
Override addNotify method and do your control manipulations there.
Attach HierarchyListener and do your manipulations whenever displayability of component changed.
Always make sure your do your component manipulations on EDT. Use SwingUtilities.invokeLater for simple UI updates or SwingWorker for long running tasks

Categories

Resources