如何从线程内更新带有矩形的窗格?
How to update a Pane with rectangles on from within a thread?
所以我正在制作一个排序算法可视化工具来练习编码并更习惯在我的程序中使用图形用户界面。我在 gui 中使用 javafx 而不是 swing。
我的程序是这样工作的。对于 gui,我有一个 Main class 和一个 Controller class 以及我在场景生成器中制作的 fxml 样式表。 main 基本上是所有 javafx 项目附带的模板,当然 Controller 具有 gui 背后的逻辑,例如单击按钮时发生的情况。它也是模拟和排序算法初始化和启动的地方。窗格是算法实际可视化的地方,使用不同高度的矩形来表示数字大小。到目前为止,我只实现了一个选择排序,以便我可以测试该排序是否有效。排序工作正常并按预期运行,但无法正确显示。
当我启动程序并生成要排序的数据时,它在正确的位置、宽度和高度上显示为矩形。 What the GUI looks like after I generate the numbers。此外,当算法完成并且数字位于正确的位置时,如果我调用 sim.update() 矩形更新以在窗格上正确显示排序的位置。但是它没有显示这些步骤之间的步骤(即使我在传递或比较结束时调用 sim.update() )。就好像我按下开始,算法 运行s,然后 gui 更新以按顺序显示它们。我希望看到它们随着算法的运行而交换。
我四处寻找答案,提到 Platform.runLater() 看起来可能会有帮助,但我并不真正理解,因为我必须将 运行nable 传递给它并且没有线程可以传递它。
下面我将展示一些代码并解释它是如何工作的以及我需要帮助的地方。
import SortingTools.ShuttleSort;
import SortingTools.Simulation;
import SortingTools.Sorter;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
public class Controller {
Simulation sim;
Sorter sorter;
boolean ready;
@FXML
public void initialize() {
ready = false;
}
@FXML
Pane world;
@FXML
Button playButton;
@FXML
public void playButtonClicked() {
if(ready) {
System.out.println("Play button Clicked");
this.sorter = new ShuttleSort(sim);
sorter.sort(sim.getNumbers());
/*sorter.start();
try {
sorter.join();
System.out.println("Ended");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
ready = false;
} else {
System.out.println("Click reset to load the data set before you click play!");
}
}
@FXML
Button pauseButton;
@FXML
public void pauseButtonClicked() {
System.out.println("Pause button Clicked");
}
@FXML
Button resetButton;
@FXML
public void resetButtonClicked() {
System.out.println("Reset button Clicked");
world.getChildren().clear();
this.sim = new Simulation(world, 50);
ready = true;
//Rectangle r = new Rectangle(0, 0, 50, 350);
//world.getChildren().add(r);
}
@FXML
Button stepButton;
@FXML
public void stepButtonClicked() {
System.out.println("Step button Clicked");
}
}
所以我启动了我的程序,那里有按钮和一个空白窗格。当我点击重置时,我生成了一个新的模拟对象。当您单击播放时,将创建一个排序器,它会对模拟中的数字 ArrayList 进行排序。现在忽略注释代码,这是故意遗漏的,稍后会解释。
import java.util.ArrayList;
import java.util.Random;
import javafx.scene.layout.Pane;
public class Simulation {
private ArrayList<Number> numbers;
public static final int MAX_NUM = 30;
public Simulation(Pane world, int popSize) {
numbers = new ArrayList<Number>();
Random random = new Random();
for(int i=0; i<popSize; i++) {
numbers.add(new Number((random.nextInt(MAX_NUM-1)+1), i, popSize, world));
}
}
public ArrayList<Number> getNumbers() {
return numbers;
}
//Loops threw wall the numbers and updates them due to there positions
public void update() {
for(Number num: numbers) {
num.update();
}
}
}
这是我的模拟class。这是创建 ArrayList of Numbers 的地方。这也是更新方法所在的地方,它意味着根据矩形的当前位置更新矩形的 x 位置(这将显示在数字 class 中)。
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class Number {
private int size;
private int position;
public Pane world;
private Rectangle r;
private int rectWidth;
public Number(int size, int position, int popSize, Pane world) {
this.size = size;
this.position = position;
this.world = world;
int rectWidth = (int)(world.getWidth()/popSize);
this.rectWidth = rectWidth;
int rectHeight = (int)((world.getHeight()/Simulation.MAX_NUM)*this.size);
int rectY = (int) (world.getHeight()-rectHeight);
int rectX = (int)(this.position*rectWidth);
this.r = new Rectangle(rectX, rectY, rectWidth, rectHeight);
r.setStroke(Color.BLACK);
r.setFill(Color.WHITE);
world.getChildren().add(r);
}
public int getSize() {
return size;
}
public int getPosition() {
return position;
}
public void setPosition(int newPosition) {
this.position = newPosition;
}
//Relocates the rectangle due to its current position
public void update() {
System.out.println(this.r.getX());
this.r.setX(this.position*this.rectWidth);
System.out.println(this.r.getX());
}
public Rectangle getR() {
return r;
}
}
这是号码class。每个数字 class 都有一个矩形。在构造函数中,您可以看到我将它添加到世界(即窗格),这是它第一次被绘制的时候。您还可以看到更新方法。当调用 sim.update() 时,它会遍历所有数字并调用它们的更新方法,根据它们的位置计算矩形的新 x 位置。
import java.util.ArrayList;
import java.util.Collections;
public abstract class Sorter /*extends Thread*/{
ArrayList<Number> numbers;
Simulation sim;
public Sorter(Simulation sim) {
this.sim = sim;
this.numbers = sim.getNumbers();
}
/*public void run() {
System.out.println("Thread running");
sort(numbers);
}*/
//Sorts the ArrayList
public abstract void sort(ArrayList<Number> numbers);
//Swaps two numbers in the ArrayList and updates there position variables to reflect the swap
public void swap(ArrayList<Number> numbers, int firstIndex, int secondIndex) {
int firstNumber = numbers.get(firstIndex).getPosition();
int secondNumber = numbers.get(secondIndex).getPosition();
int holder = firstNumber;
firstNumber = secondNumber;
secondNumber = holder;
numbers.get(firstIndex).setPosition(firstNumber);
numbers.get(secondIndex).setPosition(secondNumber);
Collections.swap(numbers, firstIndex, secondIndex);
}
}
这是分拣机 class。我计划制作的所有分类器都会扩展这个 class。您会注意到一些注释掉的部分(如上所述)。这是我试图 运行 排序 class 作为一个线程,这样我就可以在更新矩形位置后执行 this.sleep() 这样人们就可以真正看到算法的工作。
import java.util.ArrayList;
import javafx.application.Platform;
public class ShuttleSort extends Sorter{
public ShuttleSort(Simulation sim) {
super(sim);
}
public void sort(ArrayList<Number> numbers) {
for(int i=0; i<numbers.size(); i++) {
System.out.println("Pass " + i);
for(int j=numbers.size()-1; j>i; j--) {
System.out.println("j " + j);
if(numbers.get(j).getSize() < numbers.get(j-1).getSize()) {
System.out.println(numbers.get(j).getSize());
swap(numbers, j, j-1);
sim.update();
}
}
/*try {
this.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
}
for(Number num: numbers) {
System.out.println(num.getSize() + " " + num.getPosition());
}
}
}
最后这是我做的选择排序。这个的实际逻辑工作正常。您可以在每次比较结束时看到一个 sim.update() 。矩形 x 实际上正在更新,因此 sim.update() 工作正常,但屏幕上的矩形实际上并没有改变位置。然而,似乎最后的 sim.update() 正在屏幕上反映出来。我还尝试 运行 排序器 class 作为线程的另一个原因是我可以在每个 sim.update() 之后执行 thread.sleep() 因为我认为算法可能是如此之快以至于图形用户界面没有时间更新但是同样的事情发生了。算法 运行(当控制台正在输出所有 println() 时我可以看到这一点)和循环中的最后一个 sim.update() 导致 gui 上的成品。
如有任何帮助和建议,我们将不胜感激。我认为 运行 在线程中加入它可能是可行的方法,但出于某种原因,无论我做什么 sim.update() 都不会在最后一个被调用之前反映在屏幕上。谢谢。
您问题的答案比问题本身简单得多。您肯定会阻塞 JavaFX 平台线程,并且已经提到的 Platform.runLater() 是一种可能的解决方案。您应该 运行 在单独的线程上进行排序,并在每次交互中在 Platform.runLater 调用或 JavaFX 的其他更复杂的并发编程技术中更新 GUI。
所以我正在制作一个排序算法可视化工具来练习编码并更习惯在我的程序中使用图形用户界面。我在 gui 中使用 javafx 而不是 swing。
我的程序是这样工作的。对于 gui,我有一个 Main class 和一个 Controller class 以及我在场景生成器中制作的 fxml 样式表。 main 基本上是所有 javafx 项目附带的模板,当然 Controller 具有 gui 背后的逻辑,例如单击按钮时发生的情况。它也是模拟和排序算法初始化和启动的地方。窗格是算法实际可视化的地方,使用不同高度的矩形来表示数字大小。到目前为止,我只实现了一个选择排序,以便我可以测试该排序是否有效。排序工作正常并按预期运行,但无法正确显示。
当我启动程序并生成要排序的数据时,它在正确的位置、宽度和高度上显示为矩形。 What the GUI looks like after I generate the numbers。此外,当算法完成并且数字位于正确的位置时,如果我调用 sim.update() 矩形更新以在窗格上正确显示排序的位置。但是它没有显示这些步骤之间的步骤(即使我在传递或比较结束时调用 sim.update() )。就好像我按下开始,算法 运行s,然后 gui 更新以按顺序显示它们。我希望看到它们随着算法的运行而交换。
我四处寻找答案,提到 Platform.runLater() 看起来可能会有帮助,但我并不真正理解,因为我必须将 运行nable 传递给它并且没有线程可以传递它。
下面我将展示一些代码并解释它是如何工作的以及我需要帮助的地方。
import SortingTools.ShuttleSort;
import SortingTools.Simulation;
import SortingTools.Sorter;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
public class Controller {
Simulation sim;
Sorter sorter;
boolean ready;
@FXML
public void initialize() {
ready = false;
}
@FXML
Pane world;
@FXML
Button playButton;
@FXML
public void playButtonClicked() {
if(ready) {
System.out.println("Play button Clicked");
this.sorter = new ShuttleSort(sim);
sorter.sort(sim.getNumbers());
/*sorter.start();
try {
sorter.join();
System.out.println("Ended");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
ready = false;
} else {
System.out.println("Click reset to load the data set before you click play!");
}
}
@FXML
Button pauseButton;
@FXML
public void pauseButtonClicked() {
System.out.println("Pause button Clicked");
}
@FXML
Button resetButton;
@FXML
public void resetButtonClicked() {
System.out.println("Reset button Clicked");
world.getChildren().clear();
this.sim = new Simulation(world, 50);
ready = true;
//Rectangle r = new Rectangle(0, 0, 50, 350);
//world.getChildren().add(r);
}
@FXML
Button stepButton;
@FXML
public void stepButtonClicked() {
System.out.println("Step button Clicked");
}
}
所以我启动了我的程序,那里有按钮和一个空白窗格。当我点击重置时,我生成了一个新的模拟对象。当您单击播放时,将创建一个排序器,它会对模拟中的数字 ArrayList 进行排序。现在忽略注释代码,这是故意遗漏的,稍后会解释。
import java.util.ArrayList;
import java.util.Random;
import javafx.scene.layout.Pane;
public class Simulation {
private ArrayList<Number> numbers;
public static final int MAX_NUM = 30;
public Simulation(Pane world, int popSize) {
numbers = new ArrayList<Number>();
Random random = new Random();
for(int i=0; i<popSize; i++) {
numbers.add(new Number((random.nextInt(MAX_NUM-1)+1), i, popSize, world));
}
}
public ArrayList<Number> getNumbers() {
return numbers;
}
//Loops threw wall the numbers and updates them due to there positions
public void update() {
for(Number num: numbers) {
num.update();
}
}
}
这是我的模拟class。这是创建 ArrayList of Numbers 的地方。这也是更新方法所在的地方,它意味着根据矩形的当前位置更新矩形的 x 位置(这将显示在数字 class 中)。
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class Number {
private int size;
private int position;
public Pane world;
private Rectangle r;
private int rectWidth;
public Number(int size, int position, int popSize, Pane world) {
this.size = size;
this.position = position;
this.world = world;
int rectWidth = (int)(world.getWidth()/popSize);
this.rectWidth = rectWidth;
int rectHeight = (int)((world.getHeight()/Simulation.MAX_NUM)*this.size);
int rectY = (int) (world.getHeight()-rectHeight);
int rectX = (int)(this.position*rectWidth);
this.r = new Rectangle(rectX, rectY, rectWidth, rectHeight);
r.setStroke(Color.BLACK);
r.setFill(Color.WHITE);
world.getChildren().add(r);
}
public int getSize() {
return size;
}
public int getPosition() {
return position;
}
public void setPosition(int newPosition) {
this.position = newPosition;
}
//Relocates the rectangle due to its current position
public void update() {
System.out.println(this.r.getX());
this.r.setX(this.position*this.rectWidth);
System.out.println(this.r.getX());
}
public Rectangle getR() {
return r;
}
}
这是号码class。每个数字 class 都有一个矩形。在构造函数中,您可以看到我将它添加到世界(即窗格),这是它第一次被绘制的时候。您还可以看到更新方法。当调用 sim.update() 时,它会遍历所有数字并调用它们的更新方法,根据它们的位置计算矩形的新 x 位置。
import java.util.ArrayList;
import java.util.Collections;
public abstract class Sorter /*extends Thread*/{
ArrayList<Number> numbers;
Simulation sim;
public Sorter(Simulation sim) {
this.sim = sim;
this.numbers = sim.getNumbers();
}
/*public void run() {
System.out.println("Thread running");
sort(numbers);
}*/
//Sorts the ArrayList
public abstract void sort(ArrayList<Number> numbers);
//Swaps two numbers in the ArrayList and updates there position variables to reflect the swap
public void swap(ArrayList<Number> numbers, int firstIndex, int secondIndex) {
int firstNumber = numbers.get(firstIndex).getPosition();
int secondNumber = numbers.get(secondIndex).getPosition();
int holder = firstNumber;
firstNumber = secondNumber;
secondNumber = holder;
numbers.get(firstIndex).setPosition(firstNumber);
numbers.get(secondIndex).setPosition(secondNumber);
Collections.swap(numbers, firstIndex, secondIndex);
}
}
这是分拣机 class。我计划制作的所有分类器都会扩展这个 class。您会注意到一些注释掉的部分(如上所述)。这是我试图 运行 排序 class 作为一个线程,这样我就可以在更新矩形位置后执行 this.sleep() 这样人们就可以真正看到算法的工作。
import java.util.ArrayList;
import javafx.application.Platform;
public class ShuttleSort extends Sorter{
public ShuttleSort(Simulation sim) {
super(sim);
}
public void sort(ArrayList<Number> numbers) {
for(int i=0; i<numbers.size(); i++) {
System.out.println("Pass " + i);
for(int j=numbers.size()-1; j>i; j--) {
System.out.println("j " + j);
if(numbers.get(j).getSize() < numbers.get(j-1).getSize()) {
System.out.println(numbers.get(j).getSize());
swap(numbers, j, j-1);
sim.update();
}
}
/*try {
this.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
}
for(Number num: numbers) {
System.out.println(num.getSize() + " " + num.getPosition());
}
}
}
最后这是我做的选择排序。这个的实际逻辑工作正常。您可以在每次比较结束时看到一个 sim.update() 。矩形 x 实际上正在更新,因此 sim.update() 工作正常,但屏幕上的矩形实际上并没有改变位置。然而,似乎最后的 sim.update() 正在屏幕上反映出来。我还尝试 运行 排序器 class 作为线程的另一个原因是我可以在每个 sim.update() 之后执行 thread.sleep() 因为我认为算法可能是如此之快以至于图形用户界面没有时间更新但是同样的事情发生了。算法 运行(当控制台正在输出所有 println() 时我可以看到这一点)和循环中的最后一个 sim.update() 导致 gui 上的成品。
如有任何帮助和建议,我们将不胜感激。我认为 运行 在线程中加入它可能是可行的方法,但出于某种原因,无论我做什么 sim.update() 都不会在最后一个被调用之前反映在屏幕上。谢谢。
您问题的答案比问题本身简单得多。您肯定会阻塞 JavaFX 平台线程,并且已经提到的 Platform.runLater() 是一种可能的解决方案。您应该 运行 在单独的线程上进行排序,并在每次交互中在 Platform.runLater 调用或 JavaFX 的其他更复杂的并发编程技术中更新 GUI。