Странное поведение JFrame

У меня есть следующая программа, которая имеет очень странное и нежелательное поведение при запуске. Предполагается, что у него есть две кнопки: «Пуск» и «Стоп», но когда я нажимаю «Пуск», прямо под «Пуск» появляется другая кнопка. Вот принт-скрин того, о чем я говорю:

введите здесь описание изображения

Что я делаю не так и как решить эту неприятную проблему?

Вот код:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;

public class TwoButtonsTest {

    JFrame frame;
    Timer timer;
    boolean isClicked;

    public static void main(String[] args) {
    TwoButtonsTest test = new TwoButtonsTest();
    test.go();
    }

    public void go() {
    frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(500, 500);

    JButton startButton = new JButton("Start");
    startButton.addActionListener(new StartListener());
    JButton stopButton = new JButton("Stop");
        stopButton.addActionListener(new StopListener());

    final DrawPanel myDraw = new DrawPanel();

    frame.getContentPane().add(BorderLayout.CENTER, myDraw);
    frame.getContentPane().add(BorderLayout.NORTH, startButton);
    frame.getContentPane().add(BorderLayout.SOUTH, stopButton);


    frame.setVisible(true);

    timer = new Timer(50, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
            myDraw.repaint();
        }
        });
    }

    class StartListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        //needs to be implemented
        if(!isClicked) {
        }
        isClicked = true;
        timer.start();
    }
    }

    class StopListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        //needs to be implemented
        timer.stop();
        isClicked = false;
    }
    }

    class DrawPanel extends JPanel {
    public void paintComponent(Graphics g) {

        int red = (int)(Math.random()*256);
        int blue = (int)(Math.random()*256);
        int green = (int)(Math.random()*256);

        g.setColor(new Color(red, blue, green));

        Random rand = new Random();
        // following 4 lines make sure the rect stays within the frame
        int ht = rand.nextInt(getHeight());
        int wd = rand.nextInt(getWidth());

        int x = rand.nextInt(getWidth()-wd);
        int y = rand.nextInt(getHeight()-ht);

        g.fillRect(x,y,wd,ht);
    }
    } // close inner class
}

Также я пытаюсь заставить кнопку «Пуск» делать две вещи. Один из них, конечно, запустить анимацию, но когда нажимаю кнопку «Стоп» и снова нажимаю «Пуск», я хочу, чтобы он, так сказать, очистил экран и снова начал анимацию. Любые советы по этому поводу?


person nico_c    schedule 11.12.2012    source источник


Ответы (3)


Вы не вызываете super.paintComponent(Graphics g) в переопределенном методе paintComponent(..), который вы должны использовать для соблюдения цепочки рисования и, таким образом, рисования других компонентов.

Этот вызов также должен быть первым вызовом в методе:

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

    //do painting here
}  

Может возникнуть проблема, что рисунки не являются постоянными. У вас должен быть способ хранить рисунки и каждый раз перерисовывать. Наиболее распространенным является ArrayList, который будет содержать объекты для рисования (таким образом, вы не можете добавлять в список удаление и т. д.), Затем вы будете перебирать список и перерисовывать каждый объект в paintComponent. См. мой ответ здесь для пример.

  • Также не забывайте создавать компоненты Swing и управлять ими в потоке отправки событий :

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
             //create UI and components here
        }
    });
    
  • Не вызывайте setSize(..) для JFrame, лучше переопределите getPreferredSize() из JPanel и верните соответствующую высоту, подходящую для всех компонентов, чем вызовите JFrame#pack() до установки JFrame видимого (но после добавления всех компонентов).

  • Нет необходимости в getContentPane().add(..), начиная с Java 6+. add(..) по умолчанию используется contentPane.

  • Не объявляйте повторно Random, т.е. Random r=new Random() каждый раз, когда вызывается paintComponent, так как это сделает распределение значений менее случайным, а инициируйте его один раз при создании класса и вызывайте методы для экземпляра.

Вот фиксированный код (с реализованными выше исправлениями):

введите здесь описание изображения

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;

public class TwoButtonsTest {

    JFrame frame;
    Timer timer;
    boolean isClicked;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TwoButtonsTest test = new TwoButtonsTest();
                test.go();
            }
        });
    }
    final DrawPanel myDraw = new DrawPanel();

    public void go() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JButton startButton = new JButton("Start");
        startButton.addActionListener(new StartListener());
        JButton stopButton = new JButton("Stop");
        stopButton.addActionListener(new StopListener());


        frame.add(myDraw, BorderLayout.CENTER);
        frame.add(startButton, BorderLayout.NORTH);
        frame.add(stopButton, BorderLayout.SOUTH);


        frame.pack();
        frame.setVisible(true);

        timer = new Timer(50, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                myDraw.repaint();
            }
        });
    }

    class StartListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            //needs to be implemented
            if (!isClicked) {
            }

            myDraw.clearRects();

            isClicked = true;
            timer.start();
        }
    }

    class StopListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            //needs to be implemented
            timer.stop();
            isClicked = false;
        }
    }

    class DrawPanel extends JPanel {

        private ArrayList<MyRectangle> rects = new ArrayList<>();
        private Random rand = new Random();

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

            addRect();
            for (MyRectangle r : rects) {
                g.setColor(r.getColor());
                g.fillRect(r.x, r.y, r.width, r.height);
            }
        }

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

        public void clearRects() {
            rects.clear();
        }

        public void addRect() {
            // following 4 lines make sure the rect stays within the frame
            int ht = rand.nextInt(getHeight());
            int wd = rand.nextInt(getWidth());

            int x = rand.nextInt(getWidth() - wd);
            int y = rand.nextInt(getHeight() - ht);

            int red = (int) (Math.random() * 256);
            int blue = (int) (Math.random() * 256);
            int green = (int) (Math.random() * 256);

            rects.add(new MyRectangle(x, y, wd, ht, new Color(red, blue, green)));
        }
    } // close inner class
}

class MyRectangle extends Rectangle {

    Color color;

    public MyRectangle(int x, int y, int w, int h, Color c) {
        super(x, y, w, h);
        this.color = c;
    }

    public Color getColor() {
        return color;
    }
}
person David Kroukamp    schedule 11.12.2012
comment
Если я сделаю super.paintComponent(g), разве это не будет обновлять экран каждый раз, когда вызывается paintComponent? Это не то, чем я хочу заниматься. Или я ошибаюсь в своем понимании? - person nico_c; 11.12.2012
comment
@nickecarlo, конечно, вы хотите, чтобы экран перерисовывался, и вы можете увидеть, что произойдет, если вы этого не сделаете. Единственная проблема, которая может возникнуть, заключается в том, что ваш рисунок будет стерт, что можно исправить, сделав их постоянными, т.е. добавив все прямоугольники, которые нужно нарисовать, в ArrayList и выполнить итерацию по массиву на paintComponent, поэтому они будут перерисовываться каждый раз, когда экран - person David Kroukamp; 11.12.2012
comment
Спасибо за совет. Я также проголосовал за ваш ответ. Я попробую оба ответа и приму тот, который работает лучше всего. - person nico_c; 11.12.2012
comment
Хорошо, я принимаю ваш ответ, поскольку он объяснил причины того, как и почему, а также исправил мои плохие методы. Теперь мне просто нужно реализовать очистку прямоугольников, когда я нажимаю «Пуск» после нажатия «Стоп», и я должен быть в порядке. Я уверен, что вернусь сюда, если я не могу понять это. Большое спасибо, Дэвид. - person nico_c; 11.12.2012
comment
Ладно, хоть убей, я не могу понять, как все это прояснить. Я написал метод в классе DrawPanel и передал ссылку на ArrayList для выполнения rects.clear(); но я не могу реализовать это вообще. Если я просто добавлю rects.clear() в метод paintComponent, он отлично его очистит. Но я хочу иметь возможность очистить его, когда я нажимаю кнопку «Пуск» после нажатия кнопки «Стоп». Я новичок в качелях и кнопках, поэтому, возможно, я упускаю что-то очень ясное. - person nico_c; 12.12.2012
comment
@nickecarlo создайте общедоступный метод с именем clearRects в вашем классе JPanel, а затем, когда нажата кнопка «стоп» / «старт», вызовите метод, используя экземпляр JPanel, чтобы очистить список - person David Kroukamp; 12.12.2012
comment
спасибо, я создал метод и все остальное, но единственное, что я делал неправильно, это создание экземпляра объекта myDraw класса DrawPanel внутри метода go(). И поэтому мне пришлось снова создать экземпляр DrawPanel с помощью Stop, и это создало совершенно новый объект и AHH. Спасибо за ваше терпение со мной, я не могу поверить, что совершил такую ​​глупую ошибку и не заметил ее. - person nico_c; 12.12.2012

Я хотел бы предложить решение, но пока я его не нашел. Я могу сказать вам, что корень «проблемы» здесь заключается в том, как вы рисуете центральную часть вашего BorderLayout. Вы переопределяете всю функцию paintComponent() для этой программы, и все, что она создает, помещается в центр вашего BoarderLayout. В этом случае каждый раз, когда вы нажимаете кнопку, программа вызывает перерисовку, чтобы отрисовать изображение нажатой кнопки, но поскольку вы также добавили ЛЮБОЙ из нарисованных объектов на панель «Центр», он также прорисовывается там. Поскольку эта конкретная перерисовка не указывает местоположение, она помещается в верхний левый угол.

person Matthew Frederick    schedule 11.12.2012

Я решил вашу проблему с кнопкой на моем компьютере с Windows XP, вызвав SwingUtilities.

Я отформатировал ваш Java-код.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
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.SwingUtilities;
import javax.swing.Timer;

public class TwoButtonsTest implements Runnable {

    JFrame frame;
    Timer timer;
    boolean isClicked;

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

    @Override
    public void run() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);

        JButton startButton = new JButton("Start");
        startButton.addActionListener(new StartListener());
        JButton stopButton = new JButton("Stop");
        stopButton.addActionListener(new StopListener());

        final DrawPanel myDraw = new DrawPanel();

        frame.getContentPane().add(BorderLayout.CENTER, myDraw);
        frame.getContentPane().add(BorderLayout.NORTH, startButton);
        frame.getContentPane().add(BorderLayout.SOUTH, stopButton);

        frame.setVisible(true);

        timer = new Timer(50, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                myDraw.repaint();
            }
        });
    }

    class StartListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            // needs to be implemented
            if (!isClicked) {
            }
            isClicked = true;
            timer.start();
        }
    }

    class StopListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            // needs to be implemented
            timer.stop();
            isClicked = false;
        }
    }

    class DrawPanel extends JPanel {
        @Override
        public void paintComponent(Graphics g) {
            int red = (int) (Math.random() * 256);
            int blue = (int) (Math.random() * 256);
            int green = (int) (Math.random() * 256);

            g.setColor(new Color(red, blue, green));

            Random rand = new Random();
            // following 4 lines make sure the rect stays within the frame
            int ht = rand.nextInt(getHeight());
            int wd = rand.nextInt(getWidth());

            int x = rand.nextInt(getWidth() - wd);
            int y = rand.nextInt(getHeight() - ht);

            g.fillRect(x, y, wd, ht);
        }
    } // close inner class
}

Чтобы очищать экран при нажатии кнопки Start, вам придется добавить несколько методов в класс DrawPanel.

Вот один из способов сделать это.

class DrawPanel extends JPanel {
        protected boolean eraseCanvas;

        public void setEraseCanvas(boolean eraseCanvas) {
            this.eraseCanvas = eraseCanvas;
        }

        @Override
        public void paintComponent(Graphics g) {
            if (eraseCanvas) {
                g.setColor(Color.WHITE);
                g.fillRect(0,  0, getWidth(), getHeight());
            } else {
                int red = (int) (Math.random() * 256);
                int blue = (int) (Math.random() * 256);
                int green = (int) (Math.random() * 256);

                g.setColor(new Color(red, blue, green));

                Random rand = new Random();
                // following 4 lines make sure the rect stays within the frame
                int ht = rand.nextInt(getHeight());
                int wd = rand.nextInt(getWidth());

                int x = rand.nextInt(getWidth() - wd);
                int y = rand.nextInt(getHeight() - ht);

                g.fillRect(x, y, wd, ht);
            }
        }
    } // close inner class
person Gilbert Le Blanc    schedule 11.12.2012
comment
Спасибо. Я попробую и приму ответ, если он сработает для меня. - person nico_c; 11.12.2012
comment
-1 Я бы не считал это правильным. Кажется, случайное использование SwingUtilities на самом деле даже имеет значение, поскольку даже при вызове EDT, если super.paintComponent не вызывается, эта аномалия все равно может возникнуть. - person David Kroukamp; 11.12.2012
comment
@David Kroukamp: super.paintComponent не требуется, если у расширенного класса JPanel нет дочерних элементов JComponent. Поскольку OP использует расширенный класс JPanel в качестве холста, супервызов не требуется. - person Gilbert Le Blanc; 11.12.2012
comment
@GilbertLeBlanc с моим кодом в моем ответе, удаление вызова super.paintComponent вызывает ту же аномалию, что и изображение ОП в сообщении, поэтому я поддерживаю свой -1, извините. - person David Kroukamp; 11.12.2012
comment
@David Kroukamp: супервызов заставляет JPanel окрашиваться в белый цвет перед рисованием случайного прямоугольника. Удаление супервызова приводит к тому, что случайные прямоугольники закрашиваются друг над другом. Все, что делает супервызов, — это вызывает метод JPanel paintComponent, который окрашивает фон JPanel в белый цвет. - person Gilbert Le Blanc; 11.12.2012
comment
@GilbertLeBlanc см. ответ Мэтью Фредерикса, в котором все сказано. и не нужно изобретать очистку экрана, когда super.paintComponent сделает это за нас - person David Kroukamp; 11.12.2012