0

I am trying to create a program that paints a rectangle (actually two rectangles) that moves up and down depending on a random number generated every 50ms. essentially, the program adds a random number between -5 and 5 over and over again until it hits a threshold limit of either -85 and 85. I have tried to make the paint() method work but i never see the rectangle. I have attached my base program without any of the paint stuff. I am just starting so I am sure there are much easier ways to do everything. basically, I want dialNum to dictate the Y value of a horizontal rectangle so that it moves every time dialNum is updated. Right now i have it displaying the int dialNum, but that will be replaced by the rectangle. (same for counterNum)

    public class runDrones {
    public static int counterNum = 0;
    public static int dialNum = 0;



    public runDrones() {

        final JFrame display = new JFrame();
        display.setSize(800,400);
        display.setLayout(new GridLayout());
        display.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        display.setLocationRelativeTo(null);
        display.setVisible(true);



        Thread viewDial = new Thread(){

            public void run() {
                try {
                    runDial();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
            public void runDial() throws InterruptedException {
                JLabel dialStatus = new JLabel("OOPS");
                dialStatus.setFont(new Font("Verdana",1,86));
                display.add(dialStatus);

                while(dialNum >=-85 && dialNum <= 60){
                    dialNum += getRandom();
                    dialStatus.setText(Integer.toString(dialNum));
                    Thread.sleep(50);
                }

                dialStatus.setText("ALARM!!");      

            }   
        };

        Thread viewCounter = new Thread(){

            public void run(){
                try {
                    runCounter();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            public void runCounter() throws InterruptedException {
                JLabel counterStatus = new JLabel("OOPS");
                counterStatus.setFont(new Font("Verdana",1,86));
                display.add(counterStatus);                 

                while(counterNum >=-75 && counterNum <= 75){
                    counterNum += getRandom();
                    counterStatus.setText(Integer.toString(counterNum));
                    Thread.sleep(50);

                }

                counterStatus.setText("ALARM!!");
            }
        };
        viewDial.start();
        viewCounter.start();
    }
    public static int getRandom(){
        int firstNum = new Random().nextInt(5) + 1;
        int secondNum = new Random().nextInt(5) + 1;
        return firstNum - secondNum;
    }



}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
fox'
  • 13
  • 4
  • Swing is NOT thread safe, this means you should never update or modify the UI in anyway other then from the context of the event dispatching thread. See [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/) for more details. For animation in Swing, consider using a [`javax.swing.Timer`](http://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html). You may also want to have a read through [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) – MadProgrammer Jun 17 '14 at 05:13

3 Answers3

1

Swing is NOT thread safe, this means you should never update or modify the UI in anyway other then from the context of the event dispatching thread. See Concurrency in Swing for more details.

For animation in Swing, consider using a javax.swing.Timer.

The following example uses to javax.swing.Timers instead of the Threads. Normally, I prefer to use a single Timer, as it reduces the overhead on the EDT, but I wanted to create a demonstration which mimicked your own example...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class RunDrones {

    public static int counterNum = 0;
    final JFrame display;
    private final JLabel counterStatus;
    private final JLabel dialStatus;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                new RunDrones();
            }
        });
    }

    public RunDrones() {

        display = new JFrame();
        display.setLayout(new GridLayout());

        dialStatus = new JLabel("OOPS");
        dialStatus.setFont(new Font("Verdana", 1, 86));
        display.add(dialStatus);

        counterStatus = new JLabel("OOPS");
        counterStatus.setFont(new Font("Verdana", 1, 86));
        display.add(counterStatus);

        display.setSize(800, 400);
        display.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        display.setLocationRelativeTo(null);
        display.setVisible(true);

        Timer dialTimer = new Timer(50, new Dial());
        dialTimer.setInitialDelay(0);

        Timer counterTimer = new Timer(50, new Counter());

        dialTimer.start();
        counterTimer.start();
    }

    public static int getRandom() {
        int firstNum = new Random().nextInt(5) + 1;
        int secondNum = new Random().nextInt(5) + 1;
        return firstNum - secondNum;
    }

    public class Dial implements ActionListener {

        private int dialNum;

        @Override
        public void actionPerformed(ActionEvent e) {

            if (dialNum >= -85 && dialNum <= 60) {
                dialNum += getRandom();
                dialStatus.setText(Integer.toString(dialNum));
            } else {
                ((Timer) e.getSource()).stop();
                dialStatus.setText("ALARM!!");
            }
        }

    }

    public class Counter implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            if (counterNum >= -75 && counterNum <= 75) {
                counterNum += getRandom();
                counterStatus.setText(Integer.toString(counterNum));
            } else {
                ((Timer) e.getSource()).stop();
                counterStatus.setText("ALARM!!");
            }
        }

    }

}

You may also want to have a read through Performing Custom Painting

Updated with rough example

This is a rough example with painting of a rectangle...Now, personally, if I was doing this, I would establish some kind of range model and generate a percentage value from it which could be used to display the rectangle over a given viewable area, it would just make life easier, but that's me...

Bar

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class RunDrones {

    public static int counterNum = 0;
    final JFrame display;
    private final JLabel counterStatus;
    private RectanglePane dialPane;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                new RunDrones();
            }
        });
    }

    public RunDrones() {

        display = new JFrame();
        display.setLayout(new GridLayout());

        dialPane = new RectanglePane();
        display.add(dialPane);

        counterStatus = new JLabel("OOPS");
        counterStatus.setFont(new Font("Verdana", 1, 86));
        display.add(counterStatus);

        display.setSize(800, 400);
        display.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        display.setLocationRelativeTo(null);
        display.setVisible(true);

        Timer dialTimer = new Timer(50, new Dial(dialPane));
        dialTimer.setInitialDelay(0);

        Timer counterTimer = new Timer(50, new Counter());

        dialTimer.start();
        counterTimer.start();
    }

    public static int getRandom() {
        int firstNum = new Random().nextInt(5) + 1;
        int secondNum = new Random().nextInt(5) + 1;
        return firstNum - secondNum;
    }

    public class RectanglePane extends JPanel {

        private int yPos;

        public void setYPos(int yPos) {
            this.yPos = yPos;
            repaint();
        }

        public int getYPos() {
            return yPos;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g); 

            g.setColor(Color.BLACK);
            int y = (getHeight() / 2) - 85;
            int x = (getWidth() - 20) / 2;
            g.drawRect(x, y, 40, 165);

//            y = (getHeight() + getYPos()) / 2;
            y = (getHeight() / 2) + getYPos();
            g.setColor(getForeground());
            g.fillRect(x, y, 40, 40);

        }

    }

    public class Dial implements ActionListener {

        private int dialNum;
        private RectanglePane pane;

        public Dial(RectanglePane pane) {
            this.pane = pane;
            pane.setForeground(Color.GREEN);
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            if (dialNum >= -85 && dialNum <= 60) {
                dialNum += getRandom();
                pane.setYPos(dialNum);
            } else {
                pane.setForeground(Color.RED);
                ((Timer) e.getSource()).stop();
            }
        }

    }

    public class Counter implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            if (counterNum >= -75 && counterNum <= 75) {
                counterNum += getRandom();
                counterStatus.setText(Integer.toString(counterNum));
            } else {
                ((Timer) e.getSource()).stop();
                counterStatus.setText("ALARM!!");
            }
        }

    }

}

Example of a simple RangeModel

public class RangeModel {

    private final int min;
    private final int max;
    private int value;

    public RangeModel(int min, int max) {
        this.min = min;
        this.max = max;
    }

    public int getMin() {
        return min;
    }

    public int getMax() {
        return max;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int newValue) {
        newValue = Math.max(getMin(), newValue);
        newValue = Math.min(getMax(), newValue);
        this.value = newValue;
    }

    public double getProgress() {

        int range = getMax() - getMin();
        int normalValue = getValue() - getMin();

        return (double)normalValue / ((double)range - 1);

    }

}

And an example use...

RangelModel model = new RangelModel(-85, 85);
for (int i = model.getMin(); i < model.getMax(); i++) {
    model.setValue(i);
    System.out.println(NumberFormat.getPercentInstance().format(model.getProgress()));
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • thank you very much. going to require some analysis of exactly what is going on, but I appreciate it. – fox' Jun 17 '14 at 06:38
  • regarding this.. "...establish some kind of range model and generate a percentage value from it which could be used to display the rectangle over a given viewable area.." I understand what you are saying and I agree, but is there a particular Lesson in the docs that describe HOW in more detail? – fox' Jun 18 '14 at 04:24
  • No, not really, but you could take a look at [`BoundedRangeModel`](http://docs.oracle.com/javase/7/docs/api/javax/swing/BoundedRangeModel.html), which is used by the `JSlider` or you could check the update to the question for a quickly hacked out version... – MadProgrammer Jun 18 '14 at 04:40
0

Ok, about painting:

  1. You cannot paint in negative x OR y coords - they must be positive (can't go to -85)

  2. The solution to your problem: To paint again on the screen, you want your thread to call:

    repaint();

Which is an inherited method in the case you extended the JFrame or any other JComponent. If not, call it on the JFrame object you already have (not sure if that's possible - repaint may be a protected method)

Dean Leitersdorf
  • 1,303
  • 1
  • 11
  • 22
  • You can use negative coordinates with a suitable transform, for [example](http://stackoverflow.com/a/9373195/230513). – trashgod Jun 17 '14 at 10:50
0

As MadProgrammer mentioned, for animation in Swing, consider using a javax.swing.Timer instead of Thread.

As for the code posted in the question, you can display a rectangle as follows:

  1. Create a Class extending JPanel, and override the paintComponent

    class RectanglePanel extends JPanel{
    
      int y; //<<---The Y coordinate, which will be modified externally
    
      public void setY(int y) {  //<<---The setter method to change the Y-coord
        this.y = y;
        repaint();  //<<---To repaint the rectangle after the Y coordinate is changed
      }
    
      protected void paintComponent(Graphics g) {  //<<---Override the paintComponent 
       super.paintComponent(g);
       g.setColor(Color.black);  //<<---set the color of rectangle
       g.drawRect(20, y, 100, 60);  //<<---use the y-value to paint the rect
      }
    }
    

    2.Create an object of the class and add it to the JFrame

    public static int counterNum = 0;
    public static int dialNum = 0;
    RectanglePanel panel = new RectanglePanel(); //<<---Create the object here
    
    //<<---rest of the code -->> 
    
    display.setLayout(new GridLayout());
    display.add(panel);    //<<---Add the object to the JPanel before setVisible
    display.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    

    3.Change the Y value periodically

    counterNum += getRandom();
    counterStatus.setText(Integer.toString(counterNum));
    panel.setY(counterNum); //<<---Change the Y value of rectangle
    
Community
  • 1
  • 1
Infinite Recursion
  • 6,511
  • 28
  • 39
  • 51