JavaFX一次更新大量按钮/节点的最佳方法是什么?
我提前道歉这是我的第一个StackOverflow问题......
所以我为我们创建的特定内部设备创建了一个模拟器。该设备上有一个按钮,闪烁的LED速率为100毫秒。
所以我基本上创建了一个看起来像内部设备的视图。我在视图上设置了一个按钮,通过btn.setStyle("-fx-background-color: #"+rgb";");
这很好用,但当我在屏幕上添加100个这样的“设备”时,CPU使用天空火箭并且应用程序变得无法使用。
希望这里有一些相关的代码:
如果当前可见,此代码块会调用Update
上的Device
方法:
final Duration oneFrameAmt = Duration.millis(10);
final KeyFrame oneFrame = new KeyFrame(oneFrameAmt, new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent arg0) {
updateDrawnEntities();
}
}); // oneFrame
// sets the game world's game loop (Timeline)
loop = TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(oneFrame).build();
}
protected void updateDrawnEntities() {
for (ADeviceView<?> view : DeviceLayoutManager.getDeviceViews()) {
Pane content = view.getMainContentPanel();
if (content.isVisible()) {
view.update();
}
}
}
Update方法本身看起来如此(注意m_TaskLED
具有通过jfx ChangeListeners
设置的值,但只有在调用run函数时才会更新。):
@Override
public void update() {
m_TaskLED.run();
}
runnable方法如下:
@Override
public void run() {
if (m_Color == null) return;
if (m_LEDState == null) return;
if (!m_IsChanged) return;
if (m_LEDState != ELEDState.Off && m_Color != EColor.Off) {
ColorRegistry.applyColor(m_Control, m_Color);
}
else {
ColorRegistry.applyColor(m_Control, null);
}
// Clear the change flag so we can maybe shortcut future executions
m_IsChanged = false;
}
最后但并非最不重要的是applyColor
方法看起来如此:
public static void applyColor(Node p_Node, EColor p_Color) {
if (p_Color != null) {
if (p_Node instanceof Shape) {
((Shape) p_Node).setFill(Paint.valueOf(getRGB(p_Color)));
}
else {
p_Node.setStyle(FX_BACKGROUND_COLOR + getRGB(p_Color) + ";");
}
}
else {
if (p_Node instanceof Shape) {
String style = ((Shape) p_Node).getStyle().toLowerCase();
String rgb = style.substring(style.indexOf(FX_BACKGROUND_COLOR) + FX_BACKGROUND_COLOR.length());
((Shape) p_Node).setFill(Paint.valueOf(rgb));
}
p_Node.setStyle("");
}
}
作为旁注,我已经尝试了JavaFX 2和JavaFX 8,没有区别。实际上我又回到了JavaFX 2,因为JavaFX 8的性能更差,但我不认为它直接与JavaFX 8有关。事实上,我认为这是JDK8中的性能问题,因为我没有显示/更新任何GUI组件,但我的CPU使用率为20%。
以下是我的模拟器的示例图片:
以下是我尝试模拟的物理设备的示例图片:
以下是一些分析:
答案 0 :(得分:2)
您可以使用looked-up colors(向下滚动预定义颜色列表之后的链接)吗?这会每100毫秒快速更换300个按钮,它在我的系统上运行正常(需要一段时间才能启动,并且在JIT似乎启动之前需要几秒钟,但之后它就会启动)精细)。显然这里的所有按钮都有相同颜色的图形,但你可以根据这个来找出一些更复杂的逻辑。
您可能还想查看JavaFX 8 PseudoClasses。
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class LotsOfFlashingButtons extends Application {
@Override
public void start(Stage primaryStage) {
final FlowPane root = new FlowPane();
root.setStyle("led-color: red;");
final int NUM_BUTTONS = 300 ;
for (int i=0; i<NUM_BUTTONS; i++) {
final Button button = new Button(Integer.toString(i+1));
button.setOnAction(event -> System.out.println("Button "+button.getText() + " pressed"));
final Circle circle = new Circle(5);
circle.setStyle("-fx-fill: led-color");
button.setGraphic(circle);
root.getChildren().add(button);
}
final BooleanProperty red = new SimpleBooleanProperty(true);
final Timeline flash = new Timeline(new KeyFrame(Duration.millis(100),
event -> {
red.set( ! red.get());
if (red.get()) {
root.setStyle("led-color: red;");
} else {
root.setStyle("led-color: green;");
}
}
));
flash.setCycleCount(Animation.INDEFINITE);
flash.play();
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
顺便说一句,它从您的代码中看起来好像您正在改变用户线程中的样式(即不在JavaFX应用程序线程中)。这是你不应该做的事情。
更新:使用setFill(...)肯定更快。这个版本比使用css的等效版本运行得快很多。
final Circle[] graphics = new Circle[NUM_BUTTONS];
for (int i=0; i<NUM_BUTTONS; i++) {
final Button button = new Button(Integer.toString(i+1));
button.setOnAction(event -> System.out.println("Button "+button.getText() + " pressed"));
final Circle circle = new Circle(5);
graphics[i] = circle ;
button.setGraphic(circle);
root.getChildren().add(button);
}
final Random rng = new Random();
final Timeline flash = new Timeline(new KeyFrame(Duration.millis(100),
event -> {
for (Circle circle : graphics) {
circle.setFill(Color.rgb(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)));
}
}
));