Используйте несколько потоков для обновления графического интерфейса - PullRequest
0 голосов
/ 22 июня 2019

Я учусь работать с потоками Java, поэтому я решил создать простую программу для прыгающих шариков. Тем не менее, программа показывает несколько потоков, но только один использует размер окна, другие шары ограничены одной областью.

Я попытался установить размер для JPanel каждого шара и различные макеты, которые не работали.

BouncingBall.java

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import javax.swing.*;

public class BouncingBall extends JFrame {

    ArrayList<Ball> balls = new ArrayList<Ball>();

    //GUI Elements
    JLabel lblCount;
    JButton btn= new JButton("Stop");

    BouncingBall() {
//        setDefaultLookAndFeelDecorated(true);
        setTitle("BouncingBall");

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 200);
        for (int i = 0; i < 5; i++) {
            balls.add(new Ball());
        }
        setLayout(new FlowLayout());
        setContentPane(balls.get(0));
        balls.get(0).init();
        for (Ball b : balls
        ) {
            System.out.println(b.getHeight());

            if (b != balls.get(0)) {
                b.init();
                balls.get(0).add(b);
            }
        }

        this.add(btn,BorderLayout.SOUTH);
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (Ball b :balls
                ) {
                    b.stopMoving();
                }
            }
        });
        addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                for (Ball b :balls
                     ) {
                    b.startMoving();
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
            }

            @Override
            public void mouseReleased(MouseEvent e) {
            }

            @Override
            public void mouseEntered(MouseEvent e) {
            }

            @Override
            public void mouseExited(MouseEvent e) {
            }
        });
        this.setVisible(true);
    }

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

Ball.java


import javax.swing.*;
import java.awt.*;

public class Ball extends JPanel implements Runnable {
    // Box height and width
    int width;
    int height;

    // Ball Size
    float radius = 5;
    float diameter = radius * 2;

    // Center of Call
    float X = radius + 50;
    float Y = radius + 20;

    // Direction
    float dx;
    float dy;

    //Vars
    int count = 0;
    float[] colorHSB = new float[3];
    boolean moving = false;

    //Thread
    Thread t;

    Ball() {

        dx = (float) Math.random() * 10;
        dy = (float) Math.random() * 10;
        width = getWidth();
        height = getHeight();

        for (int i = 0; i < 3; i++) {
            colorHSB[i] = (float) Math.random() * 255;
        }
        t = new Thread(this);

    }

    void init() {
        t.start();
    }

    public void run() {
        while (true) {

            width = getWidth();
            height = getHeight();
            if (moving){
                X = X + dx;
                Y = Y + dy;
            }

            if (X - radius < 0) {
                dx = -dx;
                X = radius;
                addCount();
            } else if (X + radius > width) {
                dx = -dx;
                X = width - radius;
                addCount();
            }

            if (Y - radius < 0) {
                dy = -dy;
                Y = radius;
                addCount();
            } else if ((Y + radius) > height) {
                dy = -dy;
                Y = height - radius;
                addCount();
            }
            repaint();

            try {
                Thread.sleep(50);
            } catch (InterruptedException ex) {
            }
        }
    }

    public void startMoving() {
        moving = true;
    }

    public void stopMoving(){
        moving=false;
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.getHSBColor(colorHSB[0], colorHSB[1], colorHSB[2]));
        g.fillOval((int) (X - radius), (int) (Y - radius), (int) diameter, (int) diameter);
    }

    public void addCount() {
        count++;
        System.out.println(count);
    }
}

Фото запущенной программы

enter image description here

он должен показывать все шары, подпрыгивающие вокруг рамы, используя преимущество всего окна.

1 Ответ

1 голос
/ 23 июня 2019

Мой ответ основан на модели MCV . Это распределяет обязанности между моделью, представлением и контроллером.
Каждый (M, V и C) становится четко определенным классом с одной ответственностью. Сначала количество классов и отношения между ними могут выглядеть загадочно. После изучения и понимания структуры вы понимаете, что она фактически разделяет «проблему», которую вы пытаетесь решить, на более мелкие и более простые в обращении части.

Мяч может быть простым примером модели. На самом деле это pojo, в котором хранится вся информация, необходимая для рисования шара:

//a model representing ball
class Ball  {

    //Ball attributes
    private static final int SIZE = 10;  //diameter
    private int x, y;  // Position
    private final Color color;
    private Observer observer;  //to be notified on changes

    Ball() {

        Random rnd = new Random();
        color = new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
    }

    Color getColor() {
        return color;
    }

    int getSize(){
        return SIZE;
    }

    synchronized int getX() {
        return x;
    }

    synchronized void setX(int x) {
        this.x = x;
        notifyObserver();
    }

    synchronized int getY() {
        return y;
    }

    synchronized void setY(int y) {
        this.y = y;
        notifyObserver();
    }

    void registerObserver(Observer observer){
        this.observer = observer;
    }

    void notifyObserver(){
        if(observer == null) return;
        observer.onObservableChanged();
    }
}

Обратите внимание, что вы можете зарегистрировать Observer в Ball. Observer определяется как:

//listening interface. Implemented by View and used by Ball to notify changes
interface Observer {
    void onObservableChanged();
}

Он используется Ball для уведомления наблюдателя о том, что произошло изменение. * Ball также имеет несколько synchronized методов получения и установки, поэтому к его атрибутам может обращаться более одного потока.

Мы также должны определить Model, еще одно pojo, которое является классом, который инкапсулирует всю информацию, необходимую для представления:

//view model: hold info that view needs
class Model {

    private final ArrayList<Ball> balls;
    private final int width, height;

    Model(){
        balls = new ArrayList<>();
        width = 300; height = 200;
    }

    boolean addBall(Ball ball){
        return balls.add(ball);
    }

    List<Ball> getBalls() {
        return new ArrayList<>(balls); //return a copy of balls
    }

    int getWidth() {
        return width;
    }

    int getHeight() {
        return height;
    }
}

A View, как следует из названия, это просто:

class View {

    private final BallsPane ballsPane;

    View(Model model){
        ballsPane = new BallsPane(model);
    }

    void createAndShowGui(){
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.add(ballsPane);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
    }

    Observer getObserver(){
        return ballsPane;
    }
}

class BallsPane extends JPanel implements Observer {

    private final Model model;

    BallsPane(Model model){
        this.model = model;
        setPreferredSize(new Dimension(model.getWidth(), model.getHeight()));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for(Ball b : model.getBalls()){
            g.setColor(b.getColor());
            g.fillOval(b.getX(), b.getY(), b.getSize(), b.getSize());
        }
    }

    @Override
    public void onObservableChanged() {
        repaint(); //when a change was notified
    }
}

Обратите внимание, что View (на самом деле BallsPane) реализует Observer. Он будет наблюдать (или слушать) изменения в Ball и реагировать на каждое изменение, вызывая repaint().

Поскольку каждый Ball имеет synchronized позиции (x, y) геттеров и сеттеров, вы можете изменить эти атрибуты:

class BallAnimator implements Runnable{

    private final Ball ball;
    private final int maxX, maxY;
    private final Random rnd;
    private boolean moveRight = true,  moveDown = true;
    private static final int STEP =1, WAIT = 40;

    BallAnimator(Ball ball, int maxX, int maxY) {
        this.ball = ball;
        this.maxX = maxX;
        this.maxY = maxY;
        rnd = new Random();
        ball.setX(rnd.nextInt(maxX - ball.getSize()));
        ball.setY(rnd.nextInt(maxY - ball.getSize()));
        new Thread(this).start();
    }

    @Override
    public void run() {

        while(true){

            int dx = moveRight ? STEP : -STEP ;
            int dy = moveDown  ? STEP : -STEP ;

            int newX = ball.getX() + dx;
            int newY = ball.getY() + dy;

            if(newX + ball.getSize()>= maxX || newX <= 0){
                newX = ball.getX() - dx;
                moveRight = ! moveRight;
            }

            if(newY +ball.getSize()>= maxY || newY <= 0){
                newY = ball.getY() - dy;
                moveDown = ! moveDown;
            }

            ball.setX(newX);
            ball.setY(newY);

            try {
                Thread.sleep(WAIT);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
}


Добавление контроллера и соединение всего этого: BouncingBalls действует как контроллер. Он «связывает» различные части решения.
Для удобства и простоты весь следующий код можно скопировать в один файл с именем BouncingBalls.java и запустить.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class BouncingBalls{

    BouncingBalls(int numOfBalls) {

        Model model = new Model();
        View view = new View(model);;

        for (int i = 0; i < numOfBalls; i++) {
            Ball b = new Ball(); //construct  a ball
            model.addBall(b);    //add it to the model 
            b.registerObserver(view.getObserver());  //register view as an observer to it 
            new BallAnimator(b, model.getWidth(), model.getHeight()); //start a thread to update it 
        }

        view.createAndShowGui();
    }

    public static void main(String[] args) {
        new BouncingBalls(5);
    }
}

//listening interface. Implemented by View and used by Ball to notify changes
interface Observer {
    void onObservableChanged();
}

class View {

    private final BallsPane ballsPane;

    View(Model model){
        ballsPane = new BallsPane(model);
    }

    void createAndShowGui(){
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.add(ballsPane);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
    }

    Observer getObserver(){
        return ballsPane;
    }
}

class BallsPane extends JPanel implements Observer {

    private final Model model;

    BallsPane(Model model){
        this.model = model;
        setPreferredSize(new Dimension(model.getWidth(), model.getHeight()));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for(Ball b : model.getBalls()){
            g.setColor(b.getColor());
            g.fillOval(b.getX(), b.getY(), b.getSize(), b.getSize());
        }
    }

    @Override
    public void onObservableChanged() {
        repaint(); //when a change was notified
    }
}

//view model: hold info that view needs
class Model {

    private final ArrayList<Ball> balls;
    private final int width, height;

    Model(){
        balls = new ArrayList<>();
        width = 300; height = 200;
    }

    boolean addBall(Ball ball){
        return balls.add(ball);
    }

    List<Ball> getBalls() {
        return new ArrayList<>(balls); //return a copy of balls
    }

    int getWidth() {
        return width;
    }

    int getHeight() {
        return height;
    }
}

//a model representing ball
class Ball  {

    //Ball attributes
    private static final int SIZE = 10;  //diameter
    private int x, y;  // Position
    private final Color color;
    private Observer observer;  //to be notified on changes

    Ball() {

        Random rnd = new Random();
        color = new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
    }

    Color getColor() {
        return color;
    }

    int getSize(){
        return SIZE;
    }

    synchronized int getX() {
        return x;
    }

    synchronized void setX(int x) {
        this.x = x;
        notifyObserver();
    }

    synchronized int getY() {
        return y;
    }

    synchronized void setY(int y) {
        this.y = y;
        notifyObserver();
    }

    void registerObserver(Observer observer){
        this.observer = observer;
    }

    void notifyObserver(){
        if(observer == null) return;
        observer.onObservableChanged();
    }
}

class BallAnimator implements Runnable{

    private final Ball ball;
    private final int maxX, maxY;
    private final Random rnd;
    private boolean moveRight = true,  moveDown = true;
    private static final int STEP =1, WAIT = 40;

    BallAnimator(Ball ball, int maxX, int maxY) {
        this.ball = ball;
        this.maxX = maxX;
        this.maxY = maxY;
        rnd = new Random();
        ball.setX(rnd.nextInt(maxX - ball.getSize()));
        ball.setY(rnd.nextInt(maxY - ball.getSize()));
        new Thread(this).start();
    }

    @Override
    public void run() {

        while(true){

            int dx = moveRight ? STEP : -STEP ;
            int dy = moveDown  ? STEP : -STEP ;

            int newX = ball.getX() + dx;
            int newY = ball.getY() + dy;

            if(newX + ball.getSize()>= maxX || newX <= 0){
                newX = ball.getX() - dx;
                moveRight = ! moveRight;
            }

            if(newY +ball.getSize()>= maxY || newY <= 0){
                newY = ball.getY() - dy;
                moveDown = ! moveDown;
            }

            ball.setX(newX);
            ball.setY(newY);

            try {
                Thread.sleep(WAIT);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
}


enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...