Can a set of lines be drawn on a text area component which would look like this .
I then need to be able to type in text over them . These lines also need to be erased and redrawn
Consider drawing lines on a Pane like so:
public class StageTest extends Application{
private static final double WIDTH = 100, HEIGHT = 60;
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Test Stage");
Label label = new Label("Some text ");
label.setStyle("-fx-background-color:TRANSPARENT");
label.setAlignment(Pos.CENTER);
label.setPrefSize(WIDTH, HEIGHT);
Pane linesPane = getPane(label);
StackPane root = new StackPane(linesPane, label);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private Pane getPane(Label label) {
Pane pane = new Pane();
pane.setStyle("-fx-background-color:WHITE");
Line blueLine = new Line();
blueLine.setStroke(Color.BLUE);
blueLine.startXProperty().bind(label.layoutXProperty());
blueLine.startYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.333)));
blueLine.endXProperty().bind(label.layoutXProperty().add(label.widthProperty()));
blueLine.endYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.333)));
Line redLine = new Line();
redLine.setStroke(Color.RED);
redLine.startXProperty().bind(label.layoutXProperty());
redLine.startYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.666)));
redLine.endXProperty().bind(label.layoutXProperty().add(label.widthProperty()));
redLine.endYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.666)));
pane.getChildren().addAll(blueLine, redLine);
return pane;
}
public static void main(String[] args) {
launch(args);
}
}
You may want to look at using the background property of TextArea.
new TextArea().setBackground(new Background(new BackgroundImage(myImage,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,BackgroundSize.DEFAULT)));
This code here is assuming you can get those lines as an image.
You can find more info for backgrounds here: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Background.html
If you want the background image to change and be dynamic for your current needs, you have two options.
Just use a Canvas for the whole project. Paint the lines onto the canvas first, and then paint the letters on top of this. This could be better as it would allow you to customize your project to however you would like it, but it would take a bit more code and thinking to do.
Use the TextArea, and for the BackgroundImage, use a Snapshot of another Canvas. You can use a Canvas to draw the lines however you would like, and then convert it to an image using Snapshot.
WritableImage i = canvas.snapshot(new SnapshotParameters(), null);
Then, using this image, you can use that as the background of the TextArea by using BackgroundImage.
Related
I am new to java fx. I am creating a basic GUI which is meant to look like this:
However, when I try my image looks like this:
I am unsure as to why there is a large gap between the two buttons. I am using the grid pane format.
Here is my code:
public class Main extends Application {
#Override
public void start(Stage stage) {
//Creating Text field
TextField textField1 = new TextField();
//Creating Buttons
Button submit = new Button("Submit");
Button cancel = new Button("Cancel");
//Creating a Grid Pane
GridPane gridPane = new GridPane();
//Setting size for the pane
gridPane.setMinSize(200, 100);
//Setting the padding
gridPane.setPadding(new Insets(10, 10, 10, 10));
//Setting the vertical and horizontal gaps between the columns
gridPane.setVgap(5);
gridPane.setHgap(1);
//Setting the Grid alignment
gridPane.setAlignment(Pos.CENTER);
//Arranging all the nodes in the grid
gridPane.add(textField1, 0, 0);
gridPane.add(submit, 0, 1);
gridPane.add(cancel, 1,1);
//Creating a scene object
Scene scene = new Scene(gridPane);
//Setting title to the Stage
stage.setTitle("Simple Form");
//Adding scene to the stage
stage.setScene(scene);
//Displaying the contents of the stage
stage.show();
}
public static void main(String args[]){
launch(args);
}
}
Help would be appreciated :-)
What happens here is that your TextField is set in the top-left corner (0,0), but only spans a single column. You can see that the cancel Button, which is in the bottom right corner (1,1), starts where the TextField stops (if you look only at the x-position).
There is a debug option for GridPanes so you can visualize more what happens. Just call
gridPane.setGridLinesVisible(true):
The solution for this is to set the TextField to obtain/span 2 columns. You can do so by either calling:
GridPane.setColumnSpan(textField1, 2);
Or use a different add method:
gridPane.add(textField1, 0, 0, 2, 1);
See https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/GridPane.html for more information about how the GridPane works.
I use a Pane to print some Label by adding them to its children list. For some reason, I need to bind a label width, but when I do something like :
public class LabelTestApp extends Application
{
private Pane root = new Pane();
private Label myLabel = new Label("test");
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
root.getChildren.add(myLabel);
System.out.println(myLabel.widthProperty());
stage.setScene(new Scene(root, 500, 500);
stage.show();
}
}
It seems like the Label doesn't update its width. When I print it with :
System.out.println(myLabel.widthProperty());
I get :
ReadOnlyDoubleProperty [bean : Label#xxxxxxxx[styleClass = label]'test', name:width, value: 0.0]
So I suppose that the width of a Label is calculated during Layout, and since a Pane doesn't have any Layout manager, it is never calculated, am I right ?
If so, how am I supposed to get this width ?
If not, the problem is somewhere else, and I can't figure out where.
For informations : I need to use a Pane to draw some Shape, and control their placement. The purpose of the Label is to print some informations on the shapes. I need the width of the Label because they are some Line on the shapes, and I don't want them to cross the Label.
Don't hesitate to ask for further informations.
JavaFX only lays out components which are visible.
You can see this if you try something like this:
final Label myLabel = new Label("test");
final Pane myPane = new Pane(myLabel);
System.out.println(myLabel.getWidth());
stage.setScene(new Scene(myPane, 640, 480));
stage.show();
System.out.println(myLabel.getWidth());
which prints
0.0
19.5
As you can see here, the label's size is not set until stage.show() is called. You can also override Pane to see layoutChildren() is called for the first time at this point.
As a sidenote, if you want to find out the width of some text without showing it in a Stage, you can make a Text node (instead of a label) and get its preferred width, e.g.
new Text("test").prefWidth(0)
Hope this helps!
Is there a way how I can make something like a FillTransition for a Label?
I basically just want to change the text color of the Label in an animated way.
Thank you already!
A simple solution would be using a Text node instead of a Label. Text extends Shape and therefore can be used with a FillTransition. On the other hand you loose the possibility of adding a background and the ellipsis functionality.
If you want to keep using a Label, I recommend animating the textFill property using a Timeline.
Example:
#Override
public void start(Stage primaryStage) throws Exception {
Color fromColor = Color.BLACK;
Color toColor = Color.ORANGE;
Label label = new Label("hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(label.textFillProperty(), fromColor)),
new KeyFrame(Duration.seconds(10), new KeyValue(label.textFillProperty(), toColor))
);
timeline.play();
Scene scene = new Scene(new StackPane(label));
primaryStage.setScene(scene);
primaryStage.show();
}
I don't want to use AWT.
I was using FontMetrics.computeStringWidth() but is gone in JDK 10 breaking my app. Is there an alternative that doesn't require bringing a new framework (I'm using javafx)
You can use Text and get the size from the boundsInLocal property. (The Text node does not need to be attached to a scene for this to work.)
The following code keeps the width of the Rectangle the same as the size of the Text.
#Override
public void start(Stage primaryStage) throws Exception {
Text text = new Text();
TextField textField = new TextField();
Rectangle rect = new Rectangle(0, 20);
textField.textProperty().addListener((o, oldValue, newValue) -> {
text.setText(newValue);
rect.setWidth(text.getBoundsInLocal().getWidth());
});
Scene scene = new Scene(new VBox(textField, text, rect), 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
So I got my Processing code working in java. But now I want to embed it in JavaFX for my GUI. How can I do so?
I tried using the following code but it does not seem to work.
package testprocessing;
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.swing.JApplet;
import javax.swing.SwingUtilities;
import java.awt.Dimension;
import java.util.concurrent.*;
import processing.core.*;
public class JavaFxApplet extends Application {
private PApplet applet = new MyProcessingSketch();
private Dimension appletSize;
#Override public void init() throws ExecutionException, InterruptedException {
applet.init();
FutureTask<Dimension> sizingTask = new FutureTask<>(() ->
applet.getRootPane().getPreferredSize()
);
SwingUtilities.invokeLater(sizingTask);
appletSize = sizingTask.get();
}
#Override public void start(Stage stage) {
final SwingNode swingNode = new SwingNode();
SwingUtilities.invokeLater(() ->
swingNode.setContent(applet.getRootPane())
);
stage.setScene(
new Scene(
new Group(swingNode),
appletSize.getWidth(), appletSize.getHeight(),
Color.BLACK
)
);
stage.show();
}
#Override public void stop() {
applet.stop();
applet.destroy();
}
public static void main(String[] args) {
launch(args);
}
}
I get error at getRootPane(). Can you suggest an alternative for it?
Background
Introduced in Processing 3 was a JavaFX rendering mode that makes it possible to include JavaFX in our sketches. Rather than creating our own JavaFX window from scratch and then embedding our sketch within it, we can modify the window that is constructed by the PApplet class when it is initialised in JavaFX mode, adding new JavaFX elements therein.
During initialisation in JavaFX mode, the PApplet class creates a javafx.scene.canvas.Canvas object and adds this as a child to a javafx.scene.layout.StackPane object. Then, a javafx.scene.Scene object is constructed with the stackPane object as a parameter. Finally, the PApplet class creates a javafx.stage.Stage object and sets its scene to the scene object, to give us our PApplet instance - the sketch.
So in terms of JavaFX elements, the PApplet window is initialised with four elements in the following hierarchy: Stage > Scene > StackPane > Canvas, where the canvas is the graphical canvas of the sketch (ie. the object that Processing draws to).
To create our own GUI, we can add any javafx.scene.Node object (this is the superclass of JavaFX graphical elements) to the stackPane object. Alternatively you could construct a new Scene, add the Processing's canvas to it, and replace the existing Scene of the Stage.
What doesn't seem to work
Without specifying the rendering mode, Processing defaults to JAVA2D mode. In this mode, the PApplet class creates a PApplet instance with java.awt versions of the canvas and window (a java.awt.Canvas and java.awt.Frame respectively). In theory, it is possible to cast the java.awt.Frame to a javax.swing.JFrame, embed this in a javafx.embed.swing.SwingNode object and finally add this to a JavaFX stage. However, I haven't been able to get this to work.
There are also the P2D & P3D modes. In these modes, the canvas is a com.jogamp.newt.opengl.GLWindow object. Again, I have tried to embed this in a Swing Node, with the help of a com.jogamp.opengl.awt.GLJPanel, but it has not proven successful.
Implementation
Initialise your sketch in Processing's FX2D rendering mode in the call to size():
size([width], [height], FX2D);
We can then expose the four JavaFX elements that were created during initialisation by repeated casting:
final PSurfaceFX FXSurface = (PSurfaceFX) surface;
final Canvas canvas = (Canvas) FXSurface.getNative();
final StackPane stackPane = (StackPane) canvas.getParent();
final Scene scene = canvas.getScene();
final Stage stage = (Stage) canvas.getScene().getWindow();
We now have an option as to how we add our JavaFX elements:
1) Add to the existing stackPane
We can add JavaFX elements (javafx.scene.Node objects) to the stackPane that was created during initialisation with the following method:
stackPane.getChildren().add(Node node);
2) Create a new scene (recommended)
Alternatively (recommended, unless you want a stackPane as a top-level aligner), we can create a new scene object (rather than using the scene and stackPane objects that were created during initialisation) and add JavaFX elements to this.
Scene newscene = new Scene(new Group(canvas)); // simple group containing only the Processing canvas
stage.setScene(Scene scene);
During initialisation, the canvas' dimensions are binded to those of the stackPane. If we wish to change the size of the Processing canvas within the window during runtime, we must include the following:
canvas.widthProperty().unbind();
canvas.heightProperty().unbind();
Now we can freely call canvas.setHeight() and canvas.setWidth() to resize to Processing canvas within the JavaFX window (the stage).
Example
Let's add a javafx.scene.control.MenuBar to the window. Note that I am initialising our JavaFX elements within the initSurface() method rather than doing so within the setup() method, as it's safer.
In this example, the stackPane is replaced with a javafx.scene.layout.VBox, first, so that the menubar sits atop of the canvas and second, to ensure the stage is the correct height (the sum of the menuBar height and canvas height) at launch.
#Override
public void settings() {
size(500, 500, FX2D);
}
#Override
protected PSurface initSurface() {
PSurface surface = super.initSurface();
final PSurfaceFX FXSurface = (PSurfaceFX) surface;
final Canvas canvas = (Canvas) FXSurface.getNative(); // canvas is the processing drawing
final Stage stage = (Stage) canvas.getScene().getWindow(); // stage is the window
stage.setTitle("Processing/JavaFX Example");
canvas.widthProperty().unbind();
canvas.heightProperty().unbind();
final MenuItem menuItem1 = new MenuItem("Fill green");
menuItem1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
noLoop();
background(0, 255, 0); // Fills the canvas green on click
}
});
final MenuItem menuItem2 = new MenuItem("Exit");
menuItem2.setOnAction(actionEvent -> exit()); // Exit PApplet on click
final Menu menu = new Menu("Menu");
menu.getItems().add(menuItem1);
menu.getItems().add(menuItem2);
final MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(menu);
final VBox vBox = new VBox(menuBar, canvas); // Menubar will sit on top of canvas
final Scene newscene = new Scene(vBox); // Create a scene from the elements
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.setScene(newscene); // Replace the stage's scene with our new one.
}
});
return surface;
}
#Override
public void draw() {
background(50);
fill(0, 255, 0);
strokeWeight(5);
stroke(255, 5, 5);
line(0, 0, width, 0); // shows us that window is the correct dimensions
line(0, height, width, height); // shows us that window is the correct dimensions
noStroke();
ellipse(100, 100, 200, 200);
fill(255, 0, 0);
ellipse(100, 200, 200, 200);
fill(0, 0, 255);
ellipse(100, 300, 200, 200);
}
Result
Why are you trying to get the applet's root pane? Just add the applet to a JPanel, then add the JPanel to your SwingNode:
JPanel panel = new JPanel();
panel.add(applet);
swingNode.setContent(panel)