Reputation: 1510
I'm considering porting our internal automotive/measuring application from Swing to JavaFX, mainly because of the better look and multitouch support, but I can't find a way to render custom components only when they are visible.
For motivation, imagine a screen with 10 tabs, and inside each tab is a plot displaying some live data being measured. At any time, only one plot can be seen. The amount of data is large, the computer has enough power to render one plot at a time, but not all of them simultaneously.
Swing version
Now in the Swing version, the implementation of this behavior was straightforward. Each plot is a custom JComponent with overriden paintComponent method that does all the plot drawing. paintComponent gets called in two important cases:
It is more complicated than this (see http://www.oracle.com/technetwork/java/painting-140037.html for details), but it works roughly like this. The important thing is that only the plots that are visible are also being processed and redrawn..
Porting to JavaFX?
Now I'm trying to port this behaviour to JavaFX, the natural choice being Canvas. But the paintComponent callback mechanism is gone, you just draw directly into the Canvas.getGraphicsContext2D.
If I understand correctly, calling draw operations on this context doesn't really perform any immediate drawing, it just pushes the operations on a stack that gets processed later by the Prism subsystem when the Prism thread runs (is this correct?). So, probably (I'm just guessing, I'm pretty new to JavaFX), there would be no actual painting at all if Prism discovers that the Canvas is not visible (another tab is selected, it is obscured by another Node, lies outside the visible portion of Screen, ...), is this correct? That would be nice.
But even if this is this case, it doesn't help me, because I don't want to even start the rendering until I know that the Canvas is visible. Even preprocessing the live data for plotting can take quite a lot of CPU power, perhaps even more than the rendering itself - imagine interpolating thousands of data points to plot just a few lines. So, after the live data changes, I'd like to first test the actual visibility of each plot (Canvas) before starting its redraw. I also need to be notified when it becomes visible. Is this somehow possible with JavaFX? Or am I taking a bad route altogether with Canvas and there is a better way?
P.S. Please, don't confuse this visibility with Node.visibleProperty, that one does something different, basically switches off the Node from rendering.
Upvotes: 2
Views: 1321
Reputation: 209320
Using a tab pane, I think the only approach is to observe the tab pane's selected item (i.e. selected tab). You probably want to code your plot area so it's independent of whether or not it's displayed in a tab pane, so I would include a BooleanProperty
in the plot, indicating whether or not it's active:
public class PlotPane extends Pane {
private final BooleanProperty active = new SimpleBooleanProperty();
public BooleanProperty activeProperty() {
return active ;
}
public final boolean isActive() {
return activeProperty().get();
}
public final void setActive(boolean active) {
activeProperty().set(active);
}
public PlotPane(String name) {
// just for demo...
activeProperty().addListener((obs, wasActive, isActive) -> {
if (isActive) {
System.out.println(name + " is active");
} else {
System.out.println(name + " is inactive");
}
});
}
}
and then when you assemble it into a tab pane you can bind the property:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class ActiveTabTest extends Application {
@Override
public void start(Stage primaryStage) {
PlotPane pane1 = new PlotPane("Pane1");
Tab tab1 = new Tab("Pane1", pane1);
PlotPane pane2 = new PlotPane("Pane2");
Tab tab2 = new Tab("Pane2", pane2);
TabPane tabPane = new TabPane(tab1, tab2);
pane1.activeProperty().bind(tabPane.getSelectionModel().selectedItemProperty().isEqualTo(tab1));
pane2.activeProperty().bind(tabPane.getSelectionModel().selectedItemProperty().isEqualTo(tab2));
primaryStage.setScene(new Scene(tabPane, 400, 400));
primaryStage.show();
}
public static class PlotPane extends Pane {
private final BooleanProperty active = new SimpleBooleanProperty();
public BooleanProperty activeProperty() {
return active ;
}
public final boolean isActive() {
return activeProperty().get();
}
public final void setActive(boolean active) {
activeProperty().set(active);
}
public PlotPane(String name) {
// just for demo...
activeProperty().addListener((obs, wasActive, isActive) -> {
if (isActive) {
System.out.println(name + " is active");
} else {
System.out.println(name + " is inactive");
}
});
}
}
public static void main(String[] args) {
launch(args);
}
}
You are correct that the canvas won't be painted if it's not physically visible, but this gives you the chance to turn on/off things like data stream monitoring, etc.
You can refine the bindings more if you need, e.g.
plot1.activeProperty().bind(tabPane.getSelectionModel().selectedItemProperty().isEqualTo(tab1)
.and(tabPane.sceneProperty().isNotNull()));
Upvotes: 3