简体   繁体   English

使用多个线程更新GUI

[英]Use multiple threads to update GUI

I'm learning to work with java threads, so I decided to make a simple bouncing balls program. 我正在学习使用Java线程,因此我决定制作一个简单的弹跳球程序。 However, the program shows multiple threads but only one takes advantage of the window size, other balls are restricted to one area. 但是,该程序显示了多个线程,但是只有一个线程利用了窗口大小,其他的球被限制在一个区域内。

I tried setting the size for each balls' JPanel and different layouts which didn't work. 我尝试为每个球的JPanel设置大小,并设置不同的布局,但无效。

BouncingBall.java 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 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);
    }
}

A photo of the program running 程序运行的照片

在此处输入图片说明

it should show all the balls bouncing around the frame taking advantage of the whole window. 它应该显示利用整个窗口在框架周围反弹的所有球。

My answer is based on the MCV model . 我的答案基于MCV模型 This splits responsibilities between the Model, the View, and the Controller. 这在模型,视图和控制器之间划分了责任。
Each one (M,V and C) becomes a well defined single-responsibility class. 每个(M,V和C)都成为定义明确的单一职责类别。 At first the number of classes, and the relations between them may look puzzling. 首先,类的数量以及它们之间的关系可能令人困惑。 After studying and understanding the structure you realize that it actually divides the "problem" you are trying to solve into smaller and easier to handle parts. 在学习并理解了结构之后,您会意识到它实际上将您要解决的“问题”划分为更小且更易于处理的零件。

The ball can be a simple example of a Model. 球可以是模型的简单示例。 It is actually a pojo that holds all the information the view needs to draw a ball: 实际上,它是一个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();
    }
}

Note that you can register an Observer to a Ball . 请注意,您可以将Observer注册到Ball An Observer is defined by: Observer的定义如下:

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

It is used by a Ball to notify the observer that a change has occurred. Ball使用它通知观察者已发生更改。 A Ball has also some synchronized getters and setters so it attributes can be accesses by more than one thread. 一个Ball还具有一些synchronized getter和setter,因此它的属性可以被多个线程访问。

We should also define a Model , another pojo which is the class that encapsulates all the information the view needs: 我们还应该定义一个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 , as its name suggests is just that: 顾名思义, 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
    }
}

Note that the View (actually BallsPane ) implements Observer . 注意, View (实际上是BallsPane )实现了Observer It will observe (or listen to) changes in Ball , and respond to every change by invoking repaint() . 它将观察(或监听) Ball变化,并通过调用repaint()来响应每个变化。

Since each Ball has synchronized position (x,y) getters and setters, you can alter those attributes: 由于每个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();
            }
        }
    }
}


Adding a Controller and putting it all together: BouncingBalls acts as the controller. 添加一个控制器并将其放在一起: BouncingBalls充当控制器。 It "wires" the different parts of the solution . 它“连接”解决方案的不同部分。
For convenience and simplicity, the entire following code can be copy-pasted into one file called BouncingBalls.java, and run. 为了方便和简单起见,可以将以下完整代码复制粘贴到一个名为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();
            }
        }
    }
}


在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM