-3

Question: Write JFrame that when you press the "start" button draws, and keep drawing random colored and sized filled ovals until the "stop" button is pressed. Problem: loop inside the actionPerformed method() Doesn't Work. The Code:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class p6 extends JFrame implements ActionListener
{
    String str;
    JButton start,stop;
    int h=0,w=0;
    p6()
    {
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(1500,1000);
        start= new JButton("Start");
        stop= new JButton("Stop");
        
        setLayout(null);
        
        start.setBounds(500, 50, 100,30);
        stop.setBounds(610, 50, 100,30);
        
        add(start);
        add(stop);
        
        start.addActionListener(this);
        stop.addActionListener(this);
    }
    public void actionPerformed(ActionEvent ae)
    {
        String c=ae.getActionCommand();
        if(c=="Start")
        {
            while(c!="Stop")
            {
                h+=20;
                w+=20;
            }           
            repaint();
        }
        str=" "+h+" "+w;
    

    }
        public void paint(Graphics g)
        {
            super.paintComponents(g);
            g.drawString(str, 100, 100);
            //g.drawOval(100, 100, 100, 100);
            g.drawOval((int)Math.random()*2000,(int) Math.random()*2000, w,h);
        }
        public static void main(String[] args) 
        {
            new p6();
        }
    }
  • 1
    I would suggest that you look into defining a timer and a callback function that draws a new shape each time it is called. I found an article that presents this pretty cleanly: https://www.informit.com/articles/article.aspx?p=1998555&seqNum=3 – CryptoFool Nov 12 '22 at 00:47
  • 2
    Don't use `==` or `!=` for reference types, unless you want to test if they are / are not the same Object. An instance of `String` is a reference type. Instead, use `.equals` or one of the other comparison methods in [`String` API](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html) – Old Dog Programmer Nov 12 '22 at 01:04
  • 1
    Why do you claim you can't use a loop inside an `actionPerformed` method? Do you get an error message? If so, edit the question, and paste a copy of the error message. Do you get unexpected behavior? If so, edit the question and describe what actually happens. – Old Dog Programmer Nov 12 '22 at 01:07
  • 2
    [How do I compare strings in Java?](https://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java) and you probably want to use a [Swing `Timer`](https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html) instead of a loop – MadProgrammer Nov 12 '22 at 01:13
  • 1
    @OldDogProgrammer *Why do you claim you can't use a loop inside an actionPerformed method?* - well you can but the GUI can't repaint itself until the loop finishes executing so you won't see any animation of objects being painted. I would suggest you should be using a Timer to schedule the animation. The Start button would start the Timer and each time the Timer fires you paint an object. The Stop button would top the Timer. – camickr Nov 12 '22 at 01:14
  • 1
    Your painting logic is wrong. Don't override paint(). Don't invoke paintComponents(). Instead you should be overriding paintComponent(...) and invoke super.paintComponent(...) – camickr Nov 12 '22 at 01:16
  • 1
    @camickr the point is that the question doesn't explain what the problem is. – tgdavies Nov 12 '22 at 01:16
  • 1
    You shouldn't really be overriding `paint` of top level containers, but putting that aside, why are you calling `super.paintComponents `? Do you have any idea of all the work which has just been "skipped"? I think you're in for a bit of a deep learning dive, starting with [Concurrency in Swing](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) which will help explain why the `while-loop` in the `actionPerformed` method won't work – MadProgrammer Nov 12 '22 at 01:17
  • 1
    And [Painting in AWT and Swing](https://www.oracle.com/java/technologies/painting.html) and [Performing Custom Painting](https://docs.oracle.com/javase/tutorial/uiswing/painting/index.html) to get a better understand of how paint works and how you're suppose to work with it – MadProgrammer Nov 12 '22 at 01:18

2 Answers2

1

Let's start with How do I compare strings in Java?. This is pretty basic Java 101 and something you REALLY need to understand.

The move onto Concurrency in Swing. Swing is a single threaded. This means that any long running or blocking operations executed within the context of the Event Dispatching Thread will cause the app to "hang" and prevent any further possible updates or interaction.

Swing is also not thread safe, which means that you should only ever update the UI or state the UI depends on, from within the context of the Event Dispatching Thread.

This might seem like a dilemma, but Swing also provides a useful tool to help work with these constraints, see How to user Swing Timer for more details.

Swing also has a well defined and documented painting process, see Painting in AWT and Swing and Performing Custom Painting to get a better understand of how painting works in Swing and how you're suppose to work with it

It is generally recommended not to override the paint method of top level containers (like JFrame). These tend to be composite components and trying to paint on them tends to end up not working very well.

enter image description here

Image from How to Use Root Panes

And you really should make the time to learn how to use layout managers, see Laying Out Components Within a Container. They will save many hours of frustration. The following examples makes use of both a BorderLayout and CardLayout.

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {
    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        private CardLayout cardLayout;
        private JPanel showPane;
        private DrawingPane drawingPane;

        public TestPane() {
            setLayout(new BorderLayout());

            cardLayout = new CardLayout();
            showPane = new JPanel(cardLayout);

            showPane.add(new EmptyPane(), "Empty");
            drawingPane = new DrawingPane();
            showPane.add(drawingPane, "Drawing");

            cardLayout.show(showPane, "Empty");

            add(showPane);

            JButton startButton = new JButton("Start");
            JButton stopButton = new JButton("Stop");
            stopButton.setEnabled(false);

            JPanel actionsPane = new JPanel();
            actionsPane.add(startButton);
            actionsPane.add(stopButton);

            add(actionsPane, BorderLayout.SOUTH);

            startButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    startButton.setEnabled(false);
                    stopButton.setEnabled(true);

                    drawingPane.start();
                    cardLayout.show(showPane, "Drawing");
                }
            });
            stopButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    startButton.setEnabled(true);
                    stopButton.setEnabled(false);

                    drawingPane.stop();
                    cardLayout.show(showPane, "Empty");
                }
            });
        }
    }

    public class EmptyPane extends JPanel {
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }
    }

    public class DrawingPane extends JPanel {

        private int h = 0, w = 0;
        private Timer timer;

        private Random rnd = new Random();

        public DrawingPane() {
        }

        public void start() {
            if (timer == null) {
                timer = new Timer(500, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        h += 20;
                        w += 20;
                        repaint();
                    }
                });
            }
            timer.start();
        }

        public void stop() {
            if (timer != null) {
                timer.stop();
            }
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);

            int x = 0;
            int y = 0;
            if (w < getWidth() && h < getHeight()) {
                x = rnd.nextInt(0, getWidth() - w);
                y = rnd.nextInt(0, getHeight() - w);
            }

            g2d.drawOval(x, y, w, h);
            g2d.dispose();
        }

    }
}

Why make use of CardLayout?

Based on the original code, when not painting, nothing is shown. So I made use of a CardLayout to switch to an "empty" state. Now, it wouldn't be very hard to derive a model to hold the state information and share that between the "empty" and "running" states and show something different on the "empty" state. Equally, we could make use of the glassPane, but I think we're drifting of course.

The purpose is to demonstrate ONE possible solution for showing different states.

Why use two different panes?

First, we don't "need" EmptyPane, we could get away with just an instance of JPanel, but, if you wanted to display something else when not drawing, this is one approach you could take.

The original code had a if statement which means that when not drawing, nothing is painted, so why waste the time and instead just show a "empty" pane instead.

Again, this is just a demonstration of one possible solution.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

You have to add these lines before actionPerformed method,

 start.setActionCommand("start");
 stop.setActionCommand("stop");
Mad_Oum
  • 1
  • 1