5

This question is related somewhat to the one i asked HERE. Now, i have a class "Controller" which consists of the main method and all the swing components. there is a class named "VTOL" which consists of a variable named "altitude"(i have declared this variable volatile as of now).

here is a class that consists of a thread which runs in the background:

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Vineet
 */
public class Gravity extends Thread {
    
    String altStr;
    double alt;
    Controller ctrl = new Controller();

    @Override
    public void run() {
        while (true) {
            alt=VTOL.altitude;
            System.out.println(alt);
            alt = alt-0.01;
            VTOL.altitude= (int) alt;
            altStr=new Integer(VTOL.altitude).toString();
            ctrl.lblAltitude.setText(altStr);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }
}

Firstly, the problem i was facing initially was that i couldnt update value of "altitude" it remained 0 throughout the execution of program. So i declared it as volatile (I dont know if its a good practice)

Secondly, there is a jLabel in Controller class named "lblAltitude", i wish to update its value as its changed in this thread, but somehow thats not happening. How can i do that?

Community
  • 1
  • 1
md1hunox
  • 3,815
  • 10
  • 45
  • 67
  • You are not changing `VTOL.altitude` at all. You copy it out into a `double`, subtract `0.01` and then cast that back to an `int` before copying it back in. This will not change `VTOL.altitude` because the cast will remove the fractional part, including the `-0.01`. Is `VTOL.altitude` a double? If not you may wish to make it so. You should certainly remove the cast to `int`. – OldCurmudgeon Jun 23 '12 at 18:18
  • actually VTOL.altitude is an int. my aim is to decrease vtol by factor of 0.01. i dont want it to come down altogether. so i did it. – md1hunox Jun 23 '12 at 18:19
  • 1
    In that case you need something like `alt -= alt * 0.01` don't you? If you really mean *decrease by a factor*. BTW - if it starts at 0 it still won't change even with this edit. – OldCurmudgeon Jun 23 '12 at 20:03

3 Answers3

7

A solution is to use a SwingPropertyChangeSupport object, to make altitude a "bound" property with this support object, to have your GUI listener to this model class and to thereby notify the GUI of changes in altitude.

e.g.,

import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;

public class Gravity implements Runnable {
   public static final String ALTITUDE = "altitude";
   private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
   private volatile double altitude;

   @Override
   public void run() {
      while (true) {
         double temp = altitude + 10;
         setAltitude(temp); // fires the listeners
         try {
            Thread.sleep(10);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

   }

   public double getAltitude() {
      return altitude;
   }

   public void setAltitude(double altitude) {
      Double oldValue = this.altitude;
      Double newValue = altitude;

      this.altitude = newValue;

      // this will be fired on the EDT since it is a SwingPropertyChangeSupport object
      swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
   }

   public void addPropertyChangeListener(PropertyChangeListener listener) {
      swingPcSupport.addPropertyChangeListener(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener listener) {
      swingPcSupport.removePropertyChangeListener(listener);
   }


}

For a more complete runnable example:

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;

public class GravityTestGui extends JPanel {
   private static final long ALT_SLEEP_TIME = 400;
   private static final double ALT_DELTA = 5;
   JLabel altitudeLabel = new JLabel("     ");
   private Gravity gravity = new Gravity(ALT_SLEEP_TIME, ALT_DELTA);

   public GravityTestGui() {
      add(new JLabel("Altitude:"));
      add(altitudeLabel);

      gravity.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent pcEvt) {
            if (Gravity.ALTITUDE.equals(pcEvt.getPropertyName())) {
               String altText = String.valueOf(gravity.getAltitude());
               altitudeLabel.setText(altText);
            }
         }
      });

      new Thread(gravity).start();
   }

   private static void createAndShowGui() {
      GravityTestGui mainPanel = new GravityTestGui();

      JFrame frame = new JFrame("GravityTest");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }


}

class Gravity implements Runnable {
   public static final String ALTITUDE = "altitude";
   private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
   private volatile double altitude;
   private long sleepTime;
   private double delta;

   public Gravity(long sleepTime, double delta) {
      this.sleepTime = sleepTime;
      this.delta = delta;
   }

   @Override
   public void run() {
      while (true) {
         double temp = altitude + delta;
         setAltitude(temp); // fires the listeners
         try {
            Thread.sleep(sleepTime);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

   }

   public double getAltitude() {
      return altitude;
   }

   public void setAltitude(double altitude) {
      Double oldValue = this.altitude;
      Double newValue = altitude;

      this.altitude = newValue;

      // this will be fired on the EDT since it is a SwingPropertyChangeSupport object
      swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
   }

   public void addPropertyChangeListener(PropertyChangeListener listener) {
      swingPcSupport.addPropertyChangeListener(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener listener) {
      swingPcSupport.removePropertyChangeListener(listener);
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • +1, I was completely unaware of the `SwingPropertyChangeSupport` class. What a find! But it's important to note that this class is only available in Java version 1.6 or greater. – user1329572 Jun 23 '12 at 18:25
  • @user1329572: and I did not know of that limitation, thanks for the information. If you must use 1.5, then it would be easy to queue the support's notification on the EDT. – Hovercraft Full Of Eels Jun 23 '12 at 18:31
  • @HovercraftFullOfEels : Thanks! got what i was looking for. learnt something new. – md1hunox Jun 23 '12 at 18:59
  • 2
    @vineetrok I have to correct that upto Java7 is there only one EDT, Pete ---> glad I could to run your code +1, thank you – mKorbel Jun 23 '12 at 19:16
6

Whenever you modify a Swing component, you need to ensure that this event happens in the Event Dispatch Thread (i.e. EDT).

user1329572
  • 6,176
  • 5
  • 29
  • 39
  • 2
    +1. There is a link to the rules of concurrency with Swing on each and every Swing component javadoc, but half of the Swing questions here don't respect the rules. Sigh. http://docs.oracle.com/javase/6/docs/api/javax/swing/package-summary.html#threading – JB Nizet Jun 23 '12 at 17:48
  • is having multiple EDT possible? is it the proper way to do it? in short can I do it in the above class? – md1hunox Jun 23 '12 at 17:57
  • 2
    @vineetrok: no. there is only one event thread, that's kind of the whole point of it. 1+ to user1329... – Hovercraft Full Of Eels Jun 23 '12 at 18:10
2

A third approach would be to have your Swing component know about the model, VTOL.

In Gravity, you'd update VTOL.altitude, then call repaint on the component. e.g.

while (true) {
  VTOL.altitude -= 0.01;
  VTOL.makeAnyOtherChangesHereAsWell();

  controller.repaint();
  // sleep, break etc. left as an exercise for the reader
}

Then, in the paintComponent() method (or maybe somewhere else in all the paint calls, there's a slight chance it needs to be elsewhere...) of Controller, which you know is running on the EDT

// update my widgets from the VTOL model - may want this in a method
String altStr=new Integer(VTOL.altitude).toString();
this.lblAltitude.setText(altStr);  
// may be more, e.g. ...
this.lblFuelSupply.setText(VTOL.getFuelSupply());

super.paintComponent();  // now go draw stuff...

This is a bit tighter coupled than SwingPropertyChangeSupport, but the coupling is all between very related classes, so it is "reasonable", and in some ways this may be "clearer". And the Event Dispatch Queue will combine multiple repaints so this isn't as inefficient as it first appear. If multiple threads are updating stuff and queuing up multiple repaints(), only the last repaint() actually does anything.

A disadvantage is that if your GUI has a gazillion widgets and you update all of them every time this may get a bit slow. But processors are amazingly fast nowadays.

user949300
  • 15,364
  • 7
  • 35
  • 66