accessing variables and swing components through different threads
Asked Answered
S

3

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?

Swordbill answered 23/6, 2012 at 17:42 Comment(3)
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.Blowup
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.Swordbill
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.Blowup
P
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);
   }
}
Petard answered 23/6, 2012 at 18:12 Comment(4)
+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.Legpull
@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.Petard
@HovercraftFullOfEels : Thanks! got what i was looking for. learnt something new.Swordbill
@vineetrok I have to correct that upto Java7 is there only one EDT, Pete ---> glad I could to run your code +1, thank youUnwholesome
L
6

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

Legpull answered 23/6, 2012 at 17:44 Comment(3)
+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. docs.oracle.com/javase/6/docs/api/javax/swing/…Rambutan
is having multiple EDT possible? is it the proper way to do it? in short can I do it in the above class?Swordbill
@vineetrok: no. there is only one event thread, that's kind of the whole point of it. 1+ to user1329...Petard
S
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.

Starstarboard answered 23/6, 2012 at 18:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.