Make Swing operations immediate - java

I have a method in Swing to hide and show some buttons, called setScreen(int stage), which will hide or not certain buttons depending on the stage parameter. I want to call the method, then wait a few seconds and then call it again, like in this code:
... //Here stage has been assigned to some value
setScreen(stage);
if (stage != STAGE_TWO) {
sleep(WAIT_TIME * 1000);
stage = WELCOME;
setScreen(stage);
}
The code for setScreen(int stage) is something like this:
void setScreen(int stage) {
switch (stage) {
case WELCOME:
screen.infoLabel.setText("Welcome!");
screen.startButton.setVisible(true);
break;
case STAGE_TWO:
screen.infoLabel.setText("We are in stage two!");
screen.startButton.setVisible(false);
break;
}
screen.validate();
}
Where screen is an instantiation of a class extending JFrame.
The problem here is that the first setScreen(stage) is never displayed, since the thread goes to sleep before the changes have been commited. I have tried substituting the sleep for a while loop checking the time of the system, but the effect is the same.
**EDIT: ** I have found in a recommended StackOverflow thread some information on Swing Timer that may be useful. I'll work with it and upload any useful advances I make.

You are sleeping event dispatch thread. Don't do it and use:
javax.swing.Timer timer = new javax.swing.Timer(WAIT_TIME * 1000, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (stage != STAGE_TWO) {
stage = WELCOME;
setScreen(stage);
}
}
});
timer.start();
The if condition may be for entire timer as per your requirement

Related

why i can't do anything in my application when Jbutton is pressed and performing its defined function in java?

this is how I'm trying to accomplish this:
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Hello there");
Thread.sleep(1000);
panel.updateUI();
}
});
I set Enter button as the default button so when I keep pressing it the button press maybe 100 times or more but because I'm using Thread.sleep(1000) it takes some time so I have time to type in my JtextField or even close the window but can't do anything.
also, I tried to put btnNewButton.addActionListener() in the run method of a thread but no difference.
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Hello there");
Thread.sleep(1000);
panel.updateUI();
}
});
}
});
thread.start();
// I used try catch block in real code
can anyone help me to solve the issue?
**I'm creating this application in eclipse using windowsBuilder.
This is because of something that is called EDT - the Event Dispatch Thread.
This special purpose thread is responsible for all the events in your GUI - including drawing (refreshing) and interactions (button click). This means, that exactly the same thread is used to draw your application as the one that is used to execute actionPerformed code. Since you are literally halting that thread for some amount of time, your application will be non responsive for that exact period of time.
Whatever is being executed on user interactions should be short and execute fast for the reason of not blocking the application. If you need to do some heavy stuff, there is a facility for that purpose called SwingWorker that allows you to easily do some processing in the background thread (pool) and schedule UI updates during execution (like update of progress bar)
In your second "thread" snippet, your thread is not doing anything beside adding actionListener to the button, and then it terminates (probably after less than 1ms ;)) - action callback is still executed by the EDT. If you would start that custom thread from inside of run method - then it would be indeed in parallel and GUI would not be frozen - but again, SwingWorker is the way to go here.
Not entirely sure what your code is supposed to do, but if you want to press the button and then 1000ms later it will check your field you should do something like this:
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
System.out.println("before 1000ms");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after 1000ms");
System.out.println("Reading the text field ...");
}
});
thread.start();
System.out.println("after the thread");
Output:
after the thread
before 1000ms
after 1000ms
Reading the text field ...

How can I pause/continue scheduled task?

I'm new to JAVA and trying to learn some concurrency concepts.
I have a simple GUI class that pops-up a window with 1 button which I want to use for pause/continue.
Also, I have a class that extends TimerTask, it looks like below and start with the GUI:
public class process extends TimerTask {
public void run() {
while(true) { /*some repetitive macro commands.. */ }
}
}
Real question is, how can I pause the task onClick of the button and also continue onClick of the button if already paused?
I have taken a step to use a boolean to flag the button as it changes from pause to continue on each click.
But then I had to type a lot of while(button); for busy waiting inside the while()...
Do you think I can make like Thread.sleep() or something but from outside the task thread?
OLD ANSWER
Basically, there is no support for pause and resume on TimerTask, you can only cancel, check here
perhaps you might want to read about threading as that's the alternative I know of that has an interrupt and start features and then you can keep track of the progress of what you're doing to resume where it stopped.
So, I will suggest you go through this link, because you need to understand threading not just copy a code to use, there is a sample code there that will definitely solve your problem also.
Note that running an endless while loop will basically cause your program not to respond, unless the system crashes. At a certain point, the data becomes an overload and the program will overflow. This means it will fail.
.
NEW ANSWER
So, response to the new question, I was able to run a tiny little program to demonstrate how you can achieve something that looks like multithreading when working with SWING.
To rephrase your question: You want to run an indefinite task like let say we're playing a song, and then onclick of a button to pause the song, on click again should continue the song?, if so, I think below tiny program might work for you.
public class Test{
static JLabel label;
static int i = 0;
static JButton action;
static boolean x = false; //setting our x to false initialy
public static void main(String[] args) {
JFrame f=new JFrame();//creating instance of JFrame
label = new JLabel("0 Sec"); //initialized with a text
label.setBounds(130,200,100, 40);//x axis, y axis, width, height
action=new JButton("Play");//initialized with a text
action.setBounds(130,100,100, 40);//x axis, y axis, width, height
f.add(action);//adding button in JFrame
f.add(label);
action.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
if(x){
x = false; //Update x here
action.setText("Play");
action.revalidate();
}else{
x = true; //Update x here also
action.setText("Pause");
action.revalidate();
if(x){ //Using x here to determind whether we should start our child thread or not.
(new Thread(new Child())).start();
}
}
}
});
f.setSize(500, 700);//500 width and 700 height
f.setLayout(null);//using no layout managers
f.setVisible(true);//making the frame visible
}
}
class Child implements Runnable{
#Override
public void run() {
while (x) {
//You can put your long task here.
i++;
label.setText(i+" Secs");
label.revalidate();
try {
sleep(1000); //Sleeping time for our baby thread ..lol
} catch (InterruptedException ex) {
Logger.getLogger("No Foo");
}
}
}
}

Mouse Clicks being cached?

I have a java swing application with a login screen. The login screen has a submit button for pressing after the user's credentials have been entered. When the button is pressed, the a wait cursor is thrown up over the window using its glass pane. There is also a default mouse adapter that does nothing for any mouse action.
private final static MouseAdapter mouseAdapter =
new MouseAdapter() {};
/** Sets cursor for specified component to Wait cursor */
public static void startWaitCursor(JComponent component) {
log.debug("startWaitCursor()");
RootPaneContainer root =
((RootPaneContainer) component.getTopLevelAncestor());
Component glass = root.getGlassPane();
glass.setCursor(WAIT_CURSOR);
glass.addMouseListener(mouseAdapter);
glass.setVisible(true);
//force repaint of glass pane in 20ms for more responsive GUI
glass.repaint(20);
}
public static void stopWaitCursor(JComponent component) {
log.debug("stopWaitCursor()");
RootPaneContainer root =
((RootPaneContainer) component.getTopLevelAncestor());
Component glass = root.getGlassPane();
glass.setCursor(DEFAULT_CURSOR);
glass.removeMouseListener(mouseAdapter);
//force repaint of glass pane in 20ms for more responsive GUI
glass.repaint(20);
glass.setVisible(false);
}
I had assumed that this setup protected me against multiple clicks/keypresses while the backend methods were taking place. I found out that this was not the case. So in the ButtonListener.actionPerformed, I put some logic like the following:
static boolean waiting = false;
class ButtonListener implements ActionListener {
ButtonListener() {
super();
}
public void actionPerformed(ActionEvent e) {
log.info("LoginWindow.ButtonListener.actionPerformed()");
LoginWindow.this.repaint(50);
if (!waiting) {
try {
waiting = true;
verifyLogin();
} finally {
waiting = false;
}
}
}
}
I found that this protected me against keypresses, but not mouse clicks! If I repeatedly press the submit button while verifyLogin() is executing, the mouse clicks seem to be being cached somewhere, and after verify login finishes, each mouse click is processed!
I am extremely puzzled about what is going on here. Does someone have an idea?
Update:
Hmm, by following the methodology suggested by Cyrille Ka: i.e. executing the verifyLogin() method in a separate thread and disabling the button, I now only get TWO events after multiple mouse clicks but the second one still annoys.
Code is now:
public void actionPerformed(ActionEvent e) {
loginButton.setEnabled(false);
log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
LoginWindow.this.repaint(50);
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
verifyLogin();
loginButton.setEnabled(true);
}});
}
but the second event still gets in. My log shows me that the second event took place about 280 ms after the first, but did not execute until 4 seconds later, in spite of the fact that setEnabled() was the first thing the actionPerformed() event did.
2013-11-13 10:33:57,186 [AWT-EventQueue-0] INFO
c.a.r.s.c.g.LoginWindow -
LoginWindow.ButtonListener.actionPerformed(). Event occurred at Nov 13
2013 10:33:57.175 2013-11-13 10:34:01,188 [AWT-EventQueue-0] INFO
c.a.r.s.c.g.LoginWindow -
LoginWindow.ButtonListener.actionPerformed(). Event occurred at Nov 13
2013 10:33:57.453
I suppose I could do a hack and discard events over a second old or something, but that feels ugly. This should not be so difficult, I keep thinking.
Update 2:
comment from JComponent.java for setEnabled()
* <p>Note: Disabling a lightweight component does not prevent it from
* receiving MouseEvents.
Since all of the Swing components are lightweight, and setEnabled does not prevent the component from receiving mouse events, what does prevent this?
I had assumed that this setup protected me against multiple clicks/keypresses while the backend methods were taking place. I found out that this was not the case.
The section from the Swing tutorial on The Glass Pane gives an example of how you might do this. Don't remember if it only handles MouseEvents or KeyEvents as well.
In any case you can also check out Disabled Glass Pane, which does handle both events.
I presume verifyLogin() is blocking until the login is done. By doing this, you are just blocking the Swing event dispatcher thread. The events from the OS still are queuing to be sent to your GUI when the thread will be available.
There are two ways to prevent your user clicking repeatidly:
Just disable the button: button.setEnabled(false); and enable it back when the process is finished.
Launch a modal dialog (for example with a wait animation) and remove it when the process is finished.
Edit: In general, you should return quickly from event listeners, since you don't want to block all your GUI, only certain part, and in any case it makes your app feel sluggish (the window won't repaint in the meantime if it is moved or other stuff). Use Thread to launch a task running a verifyLogin() and disable your button in the meantime.
This works:
class ButtonListener implements ActionListener {
long previousEventEnd;
public void actionPerformed(ActionEvent e) {
if (e.getWhen() <= previousEventEnd ) {
log.tracef("discarding stale event, event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
return;
}
log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
LoginWindow.this.repaint(50);
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
verifyLogin();
previousEventEnd = System.currentTimeMillis();
}
});
}
}
I have to admit I'm astonished. I usually defend Java to its detractors. Here I have no defense at this point. This should not be necessary.

MultiThreading Swing Event Dispatcher Thread

I already have a post related to the multithreading issue but I have some new questions+code. I have a multiball game project and it requires me to parse an XML file to obtain information about the ball(like size, speed, initial position etc). Now, I wanted to create a different thread to parse the XML file, but I cannot figure out a way to do it. Here is my code:
main() starts here:
public class BounceBallApp extends JFrame{
public BounceBallApp()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("BounceBallApp");
setSize(300,300);
setVisible(true);
add(new BallWorld());
validate();
}
public static void main(String[] args)
{
/*Create main GUI in the Event Dispatch Thread*/
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new BounceBallApp(); //main frame
}
});
}
}
Within the constructor for BallWorld(), I have an inner class BallContainer(), which contains a Start button:
jbtStart.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e)
{
//Populate the ballList arraylist
if(filePathField.getText().equals(" "))
{
JOptionPane.showMessageDialog(null, "Please input the XML file","Information", JOptionPane.INFORMATION_MESSAGE);
}
else
{
XMLFilePath = filePathField.getText();
ballList = new BallList(XMLFilePath);//I want to put this in a thread
JOptionPane.showMessageDialog(null,"Game started!","Bouncing Balls",JOptionPane.INFORMATION_MESSAGE);
for(Ball ball:ballList.ballsArrayList)
{
timer.setDelay(1000/ball.getSpeed()); //change the delay of the timer to the ball's speed
timer.start(); //start the timer
bTimer = true; //timer is now on
}
}
}
});
}
Now the problem is that if I put the parsing process in another thread, then I have to wait for the ballsArrayList to fill before I can continue with the application. I was thinking of using invokeAndWait() but I read that that method cannot be called from the Event Dispatch Thread. So, How can I achieve this? Or is it even worthwhile?
Also, I wanted to move the calculation for moving the ball (calculating the x,y coords) to a thread, but again, I don't know how to implement it.
for(Ball ball:ballList.ballsArrayList)
{
ball.draw(g);
ball.move(ballContainerWidth,ballContainerHeight,buttonPanel.getHeight());
}
public void move(int ballContainerWidth,int ballContainerHeight,int buttonPanelHeight)
{
if((this.getX()+this.getsize()+this.getDx()) > ballContainerWidth)
{
this.setDx(-Math.abs(this.getDx()));
}
//the height/depth to which the balls can bounce is the (main ball container height) minus (button panel height)
if((this.getY()+this.getsize()+this.getDy()) > ballContainerHeight-buttonPanelHeight)
{
this.setDy(-Math.abs(this.getDy()));
}
if((this.getX()-this.getsize()) < 0 )
{
this.setDx(Math.abs(this.getDx()));
}
if((this.getY()-this.getsize()) < 0 )
{
this.setDy(Math.abs(this.getDy()));
}
int newX = (int)Math.round((this.getX()+this.getDx()));
int newY = (int)Math.round((this.getY()+this.getDy()));
this.setX(newX);
this.setY(newY);
}
Sorry for the long post, but multithreading is all new to me. I am a bit confused about it.
Initial loading of the files
I personally would opt for one of the following approaches
Increase the start-up time of your program by parsing all the files during start-up. For a few XML files this overhead might be very small. If it takes too long, you can consider showing a splash screen
Load the XML files when the start button is pressed, but show a progress bar until the loading is done. Start the game afterwards. A SwingWorker can help you with this. Examples can be found in the Swing documentation or here on SO.
Updating of the ball position
If the calculation is as easy as what is shown here, I would simply use a javax.swing.Timer to update the position on regular time intervals, and do the calculation on the Event Dispatch Thread.
If you want to do the calculation on a background thread just for the exercise, I would still opt for a calculation of the position on a background thread. The calculation should be using local variables which are only know to that background thread. Once the new position is calculated, update the position of the ball on the Event Dispatch Thread using SwingUtilities#invokeLater. This allows you to access the position during the paint operation without having to worry about threading issues. Probably easier then messing around with locks.

JLabel doesn't change back color

Part of my function looks like this
jLabel2.setBackground(Color.YELLOW);
jLabel2.setText("Status : Idle");
boolean ok=cpu21.RestartSSH();
if(ok){
jLabel2.setBackground(Color.GREEN);
jLabel2.setText("Status : Run");
}
Before I enter in function label is Green and Run, but when I come in function it doesn't chabge color to Yellow ( function RestartSSH is executing 5-6 sec, but during that time labels doesn't change colors and captures ). Where I make mistake in painting ?
Make your JLabel opaque so you can set its background colour.
Perform RestartSSH in a separate thread, or your GUI won't respond to events.
Example:
final JLabel jLabel2 = new JLabel("HELLO");
jLabel2.setOpaque(true);
jLabel2.setBackground(Color.YELLOW);
jLabel2.setText("Status : Idle");
//perform SSH in a separate thread
Thread sshThread = new Thread(){
public void run(){
boolean ok=cpu21.RestartSSH();
if(ok){
//update the GUI in the event dispatch thread
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jLabel2.setBackground(Color.GREEN);
jLabel2.setText("Status : Run");
}
});
}
}
};
sshThread.start();
(Update: added call to SwingUtilities.invokeLater)
JLabels is opaque by default, so their's background isn't painted by default. Try with:
jLabel2.setOpaque(true);
or maybe you have to call repaint after changing the color:
jLabel2.repaint();
I suspect that your restartSSH() method is blocking the event dispatch thread. One approach is to use a SwingWorker, as suggested in this example. This would allow you to show the progress of the restart process and set the label appropriately when done.

Categories

Resources