Is there are any implementation of rectangle selection in javafx? - javafx

I mean like in file managers, when you would click, drag the mouse, creating a rectangle selection and after the mouse released selection is created?
I could do that like this (pseude-code like):
onMousePressed:
setGestureStarted(true)
onMouseMoved:
if isGestureStarted:
changeRectangle(event.getX, event.getY)
onMouseReleased:
select(getSelectionRectange())
But I thought that it's pretty common behavior and maybe it's already in framework.
EDIT1:
I was trying to do zoomable linechart. And I actually came across library to do that.
It's pretty good, but could be better though.
Right now I'm considering the actual worth of javaFX in our web project, because I don't like how such thing as zoomable chart is not in the library. Probably would be better with javascript (except I should learn it first, but It shouldn't be that hard).

You would probably need to make your own implementation for this. I found your pseudo code is quiet good. If you like to select for any component then you need to first create a simple rectangular boundary which is easily possible by your pseudo code.
Now for finding out either your node is inside that boundary then you need to do iteration of all the nodes/children of certain Parent Object by using this function: Node Intersect check
I would suggest to use that function after the onMouseReleased or if you like to see things in realtime then it is preferable in onMouseMoved

Your question asks "Is there any implementation of rectangle selection in JavaFX?"
The answer is "yes".
SceneBuilder implements drag-select functionality.
SceneBuilder is open source, so take a look through the source if you are interested on how this behaviour is achieved in JavaFX by SceneBuilder.
SceneBuilderKit is the framework from which SceneBuilder is derived, its source is at the link I provided.
From the SceneBuilder release notes:
JavaFX Scene Builder Kit is an API that allows the integration of Scene Builder panels and functionalities directly into the GUI of a larger application, or a Java IDE, such as NetBeans, IntelliJ, and Eclipse.
where is documentation?
From the release notes:
The javafx_scenebuilder_kit_javadoc-2_0-ea--.zip file, which contains an API javadoc for the JavaFX Scene Builder Kit. You can download the zip file from http://www.oracle.com/technetwork/java/javafx/downloads/devpreview-1429449.html.
The javafx_scenebuilder_kit_samples-2_0-ea--.zip file, which contains the SceneBuilderHello sample application that shows a minimal Java source code example of how the Scene Builder Kit API can be used. This sample is delivered as a NetBeans project. It can be downloaded from http://www.oracle.com/technetwork/java/javafx/downloads/devpreview-1429449.html.
Perhaps after you investigate, SceneBuilder and SceneBuilderKit might not be what you are looking for. In which case, edit your question to make it more explicit and perhaps include source for your rectangle selection implementation attempt and more detail on your requirements (what you intending to select, an image showing how the feature works, etc).

yes, in jfxtras-labs project via:
MouseControlUtil.addSelectionRectangleGesture(Parent root, Rectangle rect)
or
MouseControlUtil.addSelectionRectangleGesture(Parent root, Rectangle rect, EventHandler<MouseEvent> dragHandler, EventHandler<MouseEvent> pressHandler, EventHandler<MouseEvent> releaseHandler)
more info: http://jfxtras.org/doc/8.0labs/jfxtras/labs/util/event/MouseControlUtil.html
Note that selection behavior is extremely application specific and the class above is just a helper class to help you with selection gesture implementations. In the end you have to implement selection behavior yourself.
For a more detailed and matured example of node selection in JavaFx see my other answer here.
Edit: Basic Demo
This is the basic usage. Note that it's just a demo and should NOT be considered final or production ready! For more complex implementation of selection behavior you should tailor it (mostly mouse handlers) on your own based on your application's specific requirements.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import jfxtras.labs.util.event.MouseControlUtil;
public class ShapeSelectionExample extends Application {
private List<Shape> selected = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
final Group shapesGroup = new Group();
final AnchorPane root = new AnchorPane(shapesGroup);
// Add whatever shapes you like...
Rectangle shape1 = new Rectangle(200, 20, 50, 50);
Rectangle shape2 = new Rectangle(300, 60, 50, 50);
Circle shape3 = new Circle(100, 100, 30);
shapesGroup.getChildren().addAll(shape1, shape2, shape3);
final Rectangle selectionRect = new Rectangle(10, 10, Color.TRANSPARENT);
selectionRect.setStroke(Color.BLACK);
EventHandler<MouseEvent> mouseDragHanlder = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
for (Node shape : shapesGroup.getChildren()) {
handleSelection(selectionRect, (Shape) shape);
}
}
};
// Add selection gesture
MouseControlUtil.addSelectionRectangleGesture(root, selectionRect, mouseDragHanlder, null, null);
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
}
private void handleSelection(Rectangle selectionRect, Shape shape) {
if(selectionRect.getBoundsInParent().intersects(shape.getBoundsInParent())) {
shape.setFill(Color.RED);
if(!this.selected.contains(shape))
this.selected.add(shape);
} else {
shape.setFill(Color.BLACK);
this.selected.remove(shape);
}
System.out.println("number of selected items:" + this.selected.size());
}
public static void main(String[] args) {
launch(args);
}
}
This is how the result would look like:
You could also write mouse press and release handlers (currently null in this code) to handle selection behavior while mouse button is pressed or released (which is different to mouse drag).

Related

JavaFX - pagination: how to hide bottom (control) panel and occupy its area with a page?

In some situations I need to completely hide the page change buttons in the pagination control, but the hidden area may have been occupied by a page, that is, extend it downwards.
In this example of a window with pagination, I'd like to hide the gray area and enlarge the sky-colored area to the hidden area.
How can I do this?
I tried this:
package javafxpagination;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Pagination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestPagination extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test pagination");
Pagination pagination = new Pagination();
pagination.setPageCount(4);
pagination.setMaxPageIndicatorCount(4);
pagination.setCurrentPageIndex(0);
pagination.setPageFactory((pageIndex) -> {
Pane pagePane = new Pane();
pagePane.setPrefWidth(600);
pagePane.setPrefHeight(400);
pagePane.setStyle("-fx-background-color: #DDF1F8;"); //sky color
Button button = new Button("Hide/show page buttons");
button.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
HBox paginationHBox = (HBox)pagination.lookup(".control-box");
Node paginationControl = pagination.lookup(".pagination-control");
paginationControl.setStyle("-fx-background-color: gray;");
paginationControl.setVisible(!paginationControl.isVisible()); //switch visibility
paginationControl.setManaged(paginationControl.isVisible());
paginationControl.minHeight(0.0);
paginationControl.prefHeight(0.0);
paginationControl.maxHeight(0.0);
}
});
pagePane.getChildren().add(button);
return pagePane;
});
VBox vBox = new VBox(pagination);
Scene scene = new Scene(vBox, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
}
but it did not hide gray area and not stretch the sky-colored page area.
Solution using your VBox layout manager
By adding this line to your application, the Pagination control will expand to fill the available space:
VBox.setVgrow(pagination, Priority.ALWAYS);
That answers the "how to occupy" portion of your question: "JavaFX - pagination: how to hide bottom (control) panel and occupy its area with a page?"
For the hiding, you are already accomplishing that, which is based on the info at:
JavaFX - setVisible hides the element but doesn't rearrange adjacent nodes
with the addition of the lookup,
Node paginationControl = pagination.lookup(".pagination-control")
that you are already doing, plus these lines:
paginationControl.setVisible(!paginationControl.isVisible());
paginationControl.setManaged(paginationControl.isVisible());
The lines to set the pagination control's minHeight, prefHeight, maxHeight to 0, are not needed and I wouldn't recommend keeping them.
Background on Layout Managers
Layouts are managed by layout managers. These layout managers have some default behavior, but sometimes you need to configure them or give them hints to achieve the layout behavior you want.
You have your Pagination control inside a VBox. If you look at the VBox documentation, it says:
If a vbox is resized larger than its preferred height, by default it will keep children to their preferred heights, leaving the extra space unused. If an application wishes to have one or more children be allocated that extra space it may optionally set a vgrow constraint on the child.
So, the default behavior, which is what you are using, is not to expand the managed content to fill additional available space (which is what you are seeing).
Alternate solution - replacing the pagination control
An alternative implementation is, when hiding, replace the Pagination control with the panel to be displayed rather than hiding the Pagination controls.
For example, remove the pagination control from the parent layout container (the vbox) and, in its place, add the panel to be displayed directly into the parent layout container.
To show the pagination again, you would do the opposite.
That is probably the solution that I would prefer for this kind of work.
For such a solution, you need to be mindful that a node can only be the child of a single parent at any one time and take that into account for your implementation. Also, the previous information about layout managers and setting Vgrow constraints on nodes still applies for this solution.

JavafX & CSS - Move Node to Front

As the title basically. I have a node in JavaFX which I want to be displayed in front of all other nodes according to certain CSS rules. I do not want this to change the ordering of the nodes in my VBox which .toFront() appears to do. See this question.
Is this even possible?
EDIT: To clarify. The situation is the following. I have a VBox containing a bunch of tightly packed ImageViews. When I hover over one I want it to grow slightly to give it the feel that the image is being lifted off of the screen. But since the ImageViews are so tightly packed only the top edge grows (visibly). The bottom edge grows but is below the following image and cannot be seen.
EDIT 2: Upon request here is a screenshot of what I am doing.
The different colour gradients are ImageViews and as I hover over one it should grow as the top edge of the top gradient has in this image (look closely at the top right corner next to the X). However as is also visible in this image the bottom edge of this ImageView has become hidden by the next gradient in this VBox and the grow is not visible.
This sounds like the perfect situation for using the viewOrder property of Node added in Java 9. The viewOrder controls how Nodes are drawn in relation to other Nodes of the same Parent without changing the order of the Nodes in the child list. Here's the Javadoc:
Defines the rendering and picking order of this Node within its parent.
This property is used to alter the rendering and picking order of a
node within its parent without reordering the parent's children list.
For example, this can be used as a more efficient way to implement
transparency sorting. To do this, an application can assign the
viewOrder value of each node to the computed distance between that
node and the viewer.
The parent will traverse its children in decreasing viewOrder order.
This means that a child with a lower viewOrder will be in front of a
child with a higher viewOrder. If two children have the same
viewOrder, the parent will traverse them in the order they appear in
the parent's children list.
However, viewOrder does not alter the layout and focus traversal order
of this Node within its parent. A parent always traverses its children
list in order when doing layout or focus traversal.
Here's an example using this property:
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var box = new HBox(createRectangles(Color.DARKBLUE, Color.FIREBRICK, 25));
box.setAlignment(Pos.CENTER);
box.setPadding(new Insets(50, 20, 50, 20));
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
private Rectangle[] createRectangles(Color start, Color end, int count) {
var list = new ArrayList<Rectangle>(count);
for (double i = 0; i < count; i++) {
var rect = new Rectangle(30, 60, start.interpolate(end, i / count));
var scaleTrans = new ScaleTransition(Duration.millis(250), rect);
scaleTrans.setFromX(1.0);
scaleTrans.setFromY(1.0);
scaleTrans.setToX(1.2);
scaleTrans.setToY(1.2);
rect.setOnMouseEntered(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(1.0);
rect.setViewOrder(-1.0);
scaleTrans.play();
});
rect.setOnMouseExited(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(-1.0);
rect.setViewOrder(0.0);
scaleTrans.play();
});
// *the "stop()"'s don't seem to be necessary. When I commented
// them out the animation still worked. In fact, the animation
// actually seems smoother in the situation where you move the
// mouse over and then away quickly (before the zoom-in completes).
list.add(rect);
}
return list.toArray(new Rectangle[0]);
}
}
It uses Rectangles instead of ImageViews but the concept is the same. When the mouse hovers over a Rectangle it sets the view order to be lower than the others and then plays a ScaleTransition to make it bigger. When the mouse exits it resets the view order back to 0 and then reverses the ScaleTransition.
Note: I used the var keyword which was added in Java 10.
And here is a GIF of the example in action:
Edit: Since you brought up CSS I went and checked if the view order could be set from a stylesheet. And it appears it can. Looking at the CSS Reference Guide there is a CSS property defined for Node named -fx-view-order.
Here is one such solution, creating a new stage to show the zoomed in image.
I do not set the proper coordinates in this sample, but this works as a proof of concept.
In a nutshell: capture the onMouseEntered and onMouseExited events and hide or show the new stage.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 0; i < 25; i++) {
root.getChildren().add(getImagePane());
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private VBox getImagePane() {
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(50);
setHeight(50);
setStyle("-fx-border-color: black");
}};
ImageView img = new ImageView("sample/imageGallery/cerulean.png") {{
setFitWidth(50);
setFitHeight(50);
setPreserveRatio(true);
Stage stage = zoomedStage(pane, this);
setOnMouseEntered(mouseEvent -> stage.show());
setOnMouseExited(mouseEvent -> stage.hide());
}};
pane.getChildren().add(img);
return pane;
}
private Stage zoomedStage(VBox parent, ImageView img) {
Stage stage = new Stage();
stage.setWidth(110);
stage.setHeight(110);
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(110);
setHeight(110);
setStyle("-fx-border-color: black");
}};
pane.setPickOnBounds(false);
ImageView zoomedImage = new ImageView(img.getImage());
zoomedImage.setFitHeight(100);
zoomedImage.setFitWidth(100);
pane.getChildren().add(zoomedImage);
stage.setScene(new Scene(pane));
stage.initStyle(StageStyle.UNDECORATED);
return stage;
}
}
From here, it should just be a matter or fixing the stage's coordinates to be centered over the image and then remove the stage's decoration.
Known Issues:
You will also need to handle the issue with the mouse cursor being blocked by the new stage. This will lead to a loop where the mouse is constantly entering and exiting the thumbnail of the image, causing the zoomed in stage to flicker.
From my point of view, you have a VBox with spacing 0 so each ImageView is tightly packed to each other so the glow effect is not well visible in every image. In that case you could just add a margin each time you want to select an ImageView in order to 'help' the glowing effect to appear.
For Java 8 :
Sadly, this can't be happened from a CSS cause the ImageView does not provide any rule for setting margin or padding. So you are more or less (in my opinion) bound to write that behaviour through code.
:
private Node createImageView(String imageLink) {
// Setting the image view
ImageView imageView = new ImageView(imageLink);
// setting the fit width of the image view
imageView.setFitWidth(400);
// Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
// Instantiating the Glow class
Glow glow = new Glow();
imageView.setOnMouseEntered(e -> {
// setting level of the glow effect
glow.setLevel(0.9);
// Adding a margin on TOP and Bottom side just to make the
// glowing effect visible
VBox.setMargin(imageView, new Insets(2,0,2,0));
});
imageView.setOnMouseExited(e -> {
// remove the glow effect
glow.setLevel(0.0);
// remove the margins
VBox.setMargin(imageView, new Insets(0));
});
// Applying bloom effect to text
imageView.setEffect(glow);
return imageView;
}

JavaFX - How to set Custom font to javaFX controls? [duplicate]

Firstly, I am quite a new guy in coding. I need to embed a font in my java FXML-based app and don't know how to do it. I have pasted the font, fontName.ttf in a "resources" folder in the root of the sources of my project, ie App/src/app/resources. I have set the CSS for the component (text) as
#text {
-fx-font-family: url(resources/fontName.ttf);
}
I have also tried adding inverted commas in the url, ie url("resources/fontName.ttf");, but it doesn't work. I have also set the CSS id for the component, so that can't be the problem. Is there any other working way to do so? I have seen http://fxexperience.com/2010/05/how-to-embed-fonts/, but it doesn't work since I have JDK 1.7 u21. Any ideas for a correct way to embed fonts?
Solution Approach
I updated the sample from Javafx How to display custom font in webview? to demonstrate using a custom true-type font in JavaFX controls styled using CSS.
Key points are:
Place the font in the same location as your application class and ensure your build system places it in your binary build package (e.g. application jar file).
Load the code font in your JavaFX code before you apply a style which uses it.
Font.loadFont(CustomFontApp.class.getResource("TRON.TTF").toExternalForm(), 10);
To use the custom font in a style class use the -fx-font-family css attribute and just reference the name of the font (e.g. in this case "TRON").
Create and load a stylesheet which defines the style classes.
Apply style classes to your controls.
Additional Information
If you are using Java 8, you may be interested in Use web(Google) fonts in JavaFX.
Font Collections
If your font file is in .ttc format, containing multiple fonts in a single file, then use the Font.loadFonts API (instead of Font.loadFont). Note that Font.loadFonts is only available since JDK 9 and is not available in earlier releases.
Sample Output Using a Custom Font
Sample Code
The example relies on a TRON.TTF font which you can download from dafont.
CustomFontApp.java
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.scene.text.*;
import javafx.stage.Stage;
// demonstrates the use of a custom font.
public class CustomFontApp extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
stage.setTitle("TRON Synopsis");
// load the tron font.
Font.loadFont(
CustomFontApp.class.getResource("TRON.TTF").toExternalForm(),
10
);
Label title = new Label("TRON");
title.getStyleClass().add("title");
Label caption = new Label("A sci-fi flick set in an alternate reality.");
caption.getStyleClass().add("caption");
caption.setMaxWidth(220);
caption.setWrapText(true);
caption.setTextAlignment(TextAlignment.CENTER);
VBox layout = new VBox(10);
layout.setStyle("-fx-padding: 20px; -fx-background-color: silver");
layout.setAlignment(Pos.CENTER);
layout.getChildren().setAll(
title,
new ImageView(
new Image(
"http://ia.media-imdb.com/images/M/MV5BMTY5NjM2MjAwOV5BMl5BanBnXkFtZTYwMTgyMzA5.V1.SY317.jpg"
)
),
caption
);
// layout the scene.
final Scene scene = new Scene(layout);
scene.getStylesheets().add(getClass().getResource("custom-font-styles.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
}
custom-font-styles.css
/** file: custom-font-styles.css
* Place in same directory as CustomFontApp.java
*/
.title {
-fx-font-family: "TRON";
-fx-font-size: 20;
}
.caption {
-fx-font-family: "TRON";
-fx-font-size: 10;
}
On FXML Usage
Font.loadFont(url, size) is a static method taking two parameters. I don't think you can invoke font.loadFont from FXML and wouldn't advise it if you could. Instead, load the font in Java code (as I have done in my answer) before you load your FXML or style sheet which requires the font.
I know you didn't ask for a pure programmatic way to use a custom TTF font in a java fx application but i thought maybe it helps someone to see a programmatic version:
public class Test2 extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(final Stage primaryStage) {
Group rootGroup = new Group();
// create a label to show some text
Label label = new Label("Demo Text");
try {
// load a custom font from a specific location (change path!)
// 12 is the size to use
final Font f = Font.loadFont(new FileInputStream(new File("./myFonts/TRON.TTF")), 12);
label.setFont(f); // use this font with our label
} catch (FileNotFoundException e) {
e.printStackTrace();
}
rootGroup.getChildren().add(label);
// create scene, add root group and show stage
Scene scene = new Scene(rootGroup, 640, 480, Color.WHITE);
primaryStage.setScene(scene);
primaryStage.show();
}
}
That did the job for me. You can place the font wherever you want just make sure you adapt the path.
You can find a lot more about using fonts inside java fx apps here.
HTH

JavaFX does not repaint when removing an object drawn on top of another object

In the code below, the green rectangle is not removed on mouse click. However, if you resize the stage after the mouse click, the scene is repainted and the green rect vanishes.
If you set the green rectangle's size to 150/150, then some of it is immediately on top of the pane, and it vanishes immediately on mouse click.
Is this a JavaFX bug, or am I overlooking something?
How can I make the rectangle disappear on mouse click?
My environment: Windows 7 / Java 1.8.0 64-Bit Server VM build 25.0-b70.
package xschach.client;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main3 extends Application {
public static void main(String[] args) {
Application.launch(Main3.class.getName());
}
public Main3() {}
#Override
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
Rectangle rect = new Rectangle(10, 10, 200, 200);
pane.getChildren().add(rect);
stage.setScene(new Scene(pane, 300, 300));
stage.show();
Rectangle rect2 = new Rectangle(100, 100, 50, 50);
rect2.setFill(Color.GREEN);
pane.getChildren().add(rect2);
pane.setOnMouseClicked(event -> pane.getChildren().remove(rect2));
}
}
It really seems to me a bug.
Doing some tests, what happens is the very last node (on top), no matter how many we have, that lays within the bounds of the first one, when it's removed, it is not visible to the scene graph, it's not marked as dirty, and no requestLayout() is called.
I've found also other workaround. Just allow some (minimal) transparency to the first child, and it will work...
rect.setFill(Color.web("000000FE"));
And you can always put this node behind the first one...
pane.setOnMouseClicked(event -> {
rect2.toBack();
pane.getChildren().remove(rect2);
});
Anyway, consider filing a bug to Jira.
I know it's a little bit late, but it might help someone.
If you use the method .clear() on the children list of the pane if forces the render of the view.
So this solution, quite extreme, I admit it, works :
pane.setOnMouseClicked(event -> {
pane.getChildren().clear();
pane.getChildren().add(rect);
});
I found another workaround. Move the node that is not erasing to the back with the toBack() method, and then move it back to its place with the toFront() method. This seems to activate the dirty mechanism and repaints the background node correctly.

How to center/wrap/truncate Text to fit within Rectangle in JavaFX 2.1?

I need to dynamically create Rectangles over Pane in JavaFX 2.1. Next I need to center/wrap/truncate Text over the Rectangle. The text has to fit within the rectangle. I am able to center and wrap the text with the following code, however, if the text length is too long, it will appear out of the rectangle. I want to create the behavior like Label within StackPane, essentially if the Rectangle grows, the Text will grow with it but always remain in the center of the Rectangle and if the Text cannot fit within the Rectangle, it will be truncated accordingly.
Rectangle r;
Text t;
...
//center and wrap text within rectangle
t.wrappingWidthProperty().bind(rect.widthProperty().multiply(0.9);
t.xProperty().bind(rect.xProperty().add(rect.widthProperty().subtract(t.boundsInLocalProperty().getValue().getWidth().divide(2)));
t.yProperty().bind(rect.yProperty().add(rect.heightProperty().divide(2)));
t.setTextAlignment(TextAlignment.CENTER);
t.setTextOrigin(VPos.CENTER);
What properties can I use to achieve that or is there a better way of doing this?
Here is a sample alternate implementation.
It uses a subclass of Group with a layoutChildren implementation rather than the binding api.
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.geometry.VPos;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class TextInRectangle extends Application {
public static void main(String[] args) throws Exception { launch(args); }
public void start(final Stage stage) throws Exception {
TextBox text = new TextBox("All roads lead to Rome", 100, 100);
text.setLayoutX(30);
text.setLayoutY(20);
final Scene scene = new Scene(text, 160, 140, Color.CORNSILK);
stage.setScene(scene);
stage.show();
}
class TextBox extends Group {
private Text text;
private Rectangle rectangle;
private Rectangle clip;
public StringProperty textProperty() { return text.textProperty(); }
TextBox(String string, double width, double height) {
this.text = new Text(string);
text.setTextAlignment(TextAlignment.CENTER);
text.setFill(Color.FORESTGREEN);
text.setTextOrigin(VPos.CENTER);
text.setFont(Font.font("Comic Sans MS", 25));
text.setFontSmoothingType(FontSmoothingType.LCD);
this.rectangle = new Rectangle(width, height);
rectangle.setFill(Color.BLACK);
this.clip = new Rectangle(width, height);
text.setClip(clip);
this.getChildren().addAll(rectangle, text);
}
#Override protected void layoutChildren() {
final double w = rectangle.getWidth();
final double h = rectangle.getHeight();
clip.setWidth(w);
clip.setHeight(h);
clip.setLayoutX(0);
clip.setLayoutY(-h/2);
text.setWrappingWidth(w * 0.9);
text.setLayoutX(w / 2 - text.getLayoutBounds().getWidth() / 2);
text.setLayoutY(h / 2);
}
}
}
Sample output of the sample app:
A couple of notes:
It is usually best to use a Label rather than trying to recreate part of a Label's functionality.
The layout in layoutChildren approach is a similar to that used by the JavaFX team in implementing the JavaFX control library. There are probably reasons they use layoutChildren rather than binding for layout, but I am not aware of what all of those reasons are.
I find for simple layouts, using the pre-built controls and layout managers from the JavaFX library is best (for instance the above control could have been implemented using just a Label or Text in StackPane). Where I can't quite get the layout I need from the built-in layouts, then I will supplement their usage with bindings, which I find also very easy to work with. I don't run into the need to use layoutChildren that much. It's probably just a question of scaling up to lay out complex node groups - most likely doing the calculations in the layoutChildren method performs better and may be easier to work with and debug when applied to complex node groups.
Rather than truncating Text by calculating a text size and eliding extra characters from a String as a Label does, the code instead calls setClip on the text node to visually clip it to the rectangle's size. If instead you would like to truncate Text more like a Label, then you could look at the code for the JavaFX utility class which does the computation of clipped text.
The sample code in the question does not compile because it is missing a bracket on the wrappingWidthProperty expression and it uses getValue and getWidth methods inside a bind expression, which is not possible - instead it needs to use a listener on the boundsInLocalProperty.
Also, created a small sample app demoing adding text placed in a Label with a rectangular background to a Pane with precise control over the x,y position of the labeled rectangle via binding.

Resources