I am encountering a strange issue when working with Label and Text in conjuction. Under simple scenario, if I provide a string to both Label and Text nodes with the same style, do they render differently? In my case it is happening. And I cant figure out why it is implemented in such way.
For example, for the given code:
Text text = new Text("Hello");
text.getStyleClass("myText");
Label label = new Label("Hello");
label.getStyleClass("myText");
and the css code:
.myText, .myText .text{
-fx-font-family:"Verdana";
-fx-font-size:18px;
}
If I render both label and text nodes in a layout (say HBox):
Case#1: (text first and then label)
hBox.getChildren().add(text,label);
The text bounds are smaller than the text node bounds of the label.
Case#2: (label first and then text)
hBox.getChildren().add(label,text);
The text bounds are same as the text node bounds of the label.
So in short, once the Label is rendered, the next rendering of all Text nodes (same string & style) will have the same size as Label.
Demo:
For a better understanding, if you check the below demo, the bounds of first Text node is different to the bounds of second Text node (because a Label is rendered prior to second Text node).
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class LabelVsText_Demo extends Application {
public static void main(String[] args) {
launch(args);
}
Text labelTextNode;
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setHgap(10);
root.setVgap(10);
root.setPadding(new Insets(10));
Text txt1Text = new Text("Text 1:");
Text labelText = new Text("Label:");
Text txt2Text = new Text("Text 2:");
Text text1 = new Text("AF421");
text1.getStyleClass().add("myText");
Label label = new Label("AF421");
label.getStyleClass().add("myText");
label.getChildrenUnmodifiable().addListener(
(InvalidationListener) p -> label.getChildrenUnmodifiable().forEach(node -> labelTextNode = (Text) node));
Text text2 = new Text("AF421");
text2.getStyleClass().add("myText");
Text b1 = new Text();
Text b2 = new Text();
Text b3 = new Text();
Button button = new Button("Show Bounds");
button.setOnAction(e -> {
b1.setText(text1.getLayoutBounds().toString());
b2.setText(labelTextNode.getLayoutBounds().toString());
b3.setText(text2.getLayoutBounds().toString());
});
root.addRow(0, txt1Text, text1, b1);
root.addRow(1, labelText, label, b2);
root.addRow(2, txt2Text, text2, b3);
root.add(button, 0, 3, 3, 1);
Scene scene = new Scene(root, 1200, 400);
scene.getStylesheets().add(LabelVsText_Demo.class.getResource("labeltext.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
}
And labeltext.css code is :
.myText, .myText .text{
-fx-font-family:"Verdana";
-fx-font-size:18px;
}
Upon digging deep into the JavaFX code, it is noticed that the PrismTextLayout.java uses cache to get the bounds of already rendered Text (for same string and style). I believe which is good for performance boosting. But I am not expecting different results (in my case, different bounds for Text node)
Is this a bug in JavaFX or am I missing something?
Related
Ok so from my stand point my code is pretty decent enough to get a passing grade but I am having trouble adding a simple refresh/shuffle button. NOT USING the aids of JOptionPane.
Eclipse doesnt seem to recognize that I have created the button which doesnt make sense at all for me because its telling me something about a Node which the Button is in fact a node and it is created. But when I go into another class and add another button with the 3 line example it simply works. But when I move it to my homework program it simply gives me an error on the add method which breaks the whole program!
Says
"The method add(Node) in the type List is not applicable for the arguements (Button)"
Could anyone shed some light of where I could be going wrong in my code? It has to be something along the a node to string conversion or something I just cant seem to figure it out. Willing to take any hints given to me but please DO NOT SOLVE THE PROBLEM FOR ME.
Here is the question from the book basically.
"Write a program that lets the user click the refresh button to display four cards from a deck of 54 cards."
I just need some help on the button thats all. I literally have the rest.
Here is my code so far.
I Have left the imports out as there is just too many.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import java.awt.Button;
import java.io.File;
import java.util.ArrayList;
public class Cards extends Application
{
public void start(Stage primaryStage)
{
ArrayList<String> cards = new ArrayList<>(); //Array list
Shuffle(cards); //Shuffles the Cards
String file1 = new File("cards" + "/" + cards.get(1) + ".png").toURI().toString();
String file2 = new File("cards" + "/" + cards.get(2) + ".png").toURI().toString();
String file3 = new File("cards" + "/" + cards.get(3) + ".png").toURI().toString();
String file4 = new File("cards" + "/" + cards.get(4) + ".png").toURI().toString();
Pane pane = new HBox(20); //Creates the Box for the Images
pane.setPadding(new Insets(5, 5, 5, 5)); //Spreads the Images out
Image image = new Image(file1); //Creates the String Image
Image image2 = new Image(file2);
Image image3 = new Image(file3);
Image image4 = new Image(file4);
pane.getChildren().add(new ImageView(image)); //Adds the First Image
ImageView view1 = new ImageView(image);
view1.setFitHeight(100);
view1.setFitWidth(100);
pane.getChildren().add(new ImageView(image2)); //Adds the Second Image
ImageView view2 = new ImageView(image2);
view2.setFitHeight(100);
view2.setFitWidth(100);
pane.getChildren().add(new ImageView(image3)); //Add the Third Image
ImageView view3 = new ImageView(image3);
view3.setFitHeight(100);
view3.setFitWidth(100);
pane.getChildren().add(new ImageView(image4)); //Add the Fourth Image
ImageView view4 = new ImageView(image4);
view4.setFitHeight(100);
view4.setFitWidth(100);
HBox hbox = new HBox(5); //Creates the Box for the Button
Button shuffle = new Button("Shuffle"); //Creates the Button
hbox.getChildren().add(shuffle); //Should add the button but doesn't
shuffle.addActionListener( e -> //Listener for the button
{
Shuffle(cards);
});
BorderPane pane2 = new BorderPane();/ /Creates the Pane for the Button
pane2.setCenter(pane); //Sets the cards in the Center
pane2.setBottom(hbox); //Sets the Button on the bottom
BorderPane.setAlignment(hbox, Pos.CENTER);
hbox.setAlignment(Pos.BOTTOM_CENTER);//Aligns the Button to BOT_CENTER
Scene scene = new Scene(pane2); //Creates the Scene
primaryStage.setTitle("Cards");
primaryStage.setScene(scene);
primaryStage.show();
}
public void Shuffle(ArrayList<String> cards)
//Allows the cards to Shuffle when called.
{
for (int i = 0; i <= 53; i++) //Sets the Number of Cards in Deck
cards.add(String.valueOf(i+1));
java.util.Collections.shuffle(cards);
}
public static void main(String[] args)
{
launch(args);
}
}
You're using the AWT-button with your import java.awt.Button;, that's why you can use the method public void addActionListener(ActionListener l).
Replace your import to import javafx.scene.control.Button;. Furthermore you could use (analogue to your code) the following lambda:
shuffle.setOnAction( (x) -> //Listener for the button
{
Shuffle(cards);
});
Give it a try :)
What I am trying to do is create a label in fxml, using Scenebuilder, which updates its font size to always ensure that the content of the label is the same size.
Some background info is that I am using an AnchorPane, which is maximized and non-resizable.
I do not need the height of the text to be the same--just the width. Also, I would only like to resize if it is too big to fit. So if the label is only 1 letter, I do not want it to be a giant single letter. I only have rudimentary ideas, of which some pseudocode is below. Thanks!
lengthOfLabel = menuLabel.getText().length();
if(lengthOfLabel > numOfCharsThatCanFitInWidth){
menuLabel.setStyle("-fx-font-size: " + (int) (someConstant/lengthOfLabel) + ";")
}
You can use temp Text object to measure text size, and scale the font if it doesn't fit. Something like this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
//maximum width of the text/label
private final double MAX_TEXT_WIDTH = 400;
//default (nonscaled) font size of the text/label
private final double defaultFontSize = 32;
private final Font defaultFont = Font.font(defaultFontSize);
#Override
public void start(Stage primaryStage) {
final TextField tf = new TextField("Label text goes here");
final Label lbl = new Label();
lbl.setFont(defaultFont);
lbl.textProperty().addListener((observable, oldValue, newValue) -> {
//create temp Text object with the same text as the label
//and measure its width using default label font size
Text tmpText = new Text(newValue);
tmpText.setFont(defaultFont);
double textWidth = tmpText.getLayoutBounds().getWidth();
//check if text width is smaller than maximum width allowed
if (textWidth <= MAX_TEXT_WIDTH) {
lbl.setFont(defaultFont);
} else {
//and if it isn't, calculate new font size,
// so that label text width matches MAX_TEXT_WIDTH
double newFontSize = defaultFontSize * MAX_TEXT_WIDTH / textWidth;
lbl.setFont(Font.font(defaultFont.getFamily(), newFontSize));
}
});
lbl.textProperty().bind(tf.textProperty());
final AnchorPane root = new AnchorPane(lbl, tf);
AnchorPane.setLeftAnchor(tf, 0d);
AnchorPane.setRightAnchor(tf, 0d);
AnchorPane.setBottomAnchor(tf, 0d);
primaryStage.setScene(new Scene(root, MAX_TEXT_WIDTH, 200));
primaryStage.show();
}
}
Note that tmpText.getLayoutBounds() returns the bounds that do not include any transformations/effects (if these are needed, you'll have to add text object to temp scene and calculate its bounds in parent).
Can anyone tell me why sometimes JavaFX displays the content of a TextField with a blur effect on it? It seems to be random and occurs in any of my TextFields. Please see the image attached.
Focusing on the intermittent rendering artifact mentioned here, the 2 glyph looks like it's been rendered twice, with one copy shifted horizontally relative to the other. Such apparently random anomalies are notoriously difficult to identify. Myriad causes may include incorrect synchronization, improper layout, defects in the host platform's rendering pipeline, etc. For reference, the example below may allow you to test on disparate platforms.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/53989899/230513
*/
public class TextFieldTest extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("TextFieldTest");
BorderPane root = new BorderPane();
root.setCenter(createContent());
root.setBottom(createVersion());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private Node createContent() {
HBox row1 = new HBox(4);
Label channelsLabel = new Label("Channels:");
TextField channelsText = new TextField("2");
channelsText.setPrefWidth(32);
Label separatorLabel = new Label("Separator:");
TextField separatorText = new TextField("!");
separatorText.setPrefWidth(32);
row1.setPadding(new Insets(8));
row1.getChildren().addAll(
channelsLabel, channelsText, separatorLabel, separatorText);
HBox row2 = new HBox(4, new Label("Label:"), new TextField());
row2.setPadding(new Insets(8));
return new VBox(row1, row2);
}
private Label createVersion() {
Label label = new Label(
System.getProperty("os.name") + " v"
+ System.getProperty("os.version") + "; Java v"
+ System.getProperty("java.version"));
label.setPadding(new Insets(8));
return label;
}
public static void main(String[] args) {
launch(args);
}
}
As shown in the Modena example, an intentional blur effect indicates that the text field is focused:
The detail that gives rise to the blurred effect in your image is a compound border, seen below at 2x:
Comparable effects are seen here for buttons (top row) and default buttons (bottom row):
Thank you ahead of time for your time taken.
Currently, I am in the process of creating a JavaFX GUI for a simple-enough client/server application.
On the right side of a SplitPane is a GridPane, where-by every time a message is sent or received, that Message is displayed within the new ROW in the GridPane, and the message is basically an ImageView(image) followed by a TextArea with a String in it displaying the message sent/received.
My issue is that I cannot figure out after over a week how to size the TextArea appropriately for the block of text within it.
Before you mark this question as a duplicate, I have tried every implementation I could find.
Firstly, the ScrollBar listening solution does not work on runtime, this only appears to work WHILE a user is typing, so I have scratched that as a potential solution for my particular issue.
The solution I'm currently using (which isn't working) is using a Text object and getting the layout bounds/height of THAT for the TextArea.
I am fine with my TextAreas (acting as message bubbles) all being the same width, as of now I am specifying the minWidth to be 300.0, the problem again is with the HEIGHT.
My code is as follows:
HBox messageBox = new HBox(10);
messageBox.setPadding(new Insets(0, 0, 0, 25));
TextArea textArea = new TextArea();
textArea.setText(message);
textArea.setFont(new Font(20));
textArea.setWrapText(true);
final Text helper = new Text();
helper.setText(message);
helper.setFont(textArea.getFont());
helper.setWrappingWidth(300.0);
double width = helper.getLayoutBounds().getWidth();
double height = helper.getLayoutBounds().getHeight();
textArea.setMinWidth(width);
textArea.setPrefHeight(height);
messageBox.getChildren().addAll(imageView, textArea);
messagePane.add(messageBox, 0, rowCount);
rowCount++;
Please note that I have also tried placing my helper Text object into a throw-away Pane, which renders almost identical results.
Lastly, I have tried adding padding to the setPrefHeight() of the TextArea, I have tried MinHeight/MaxHeight combinations.
This picture illustrates my FIRST problem, the 3rd message has far too much space below the end of the block of text, while preceding message look fine, (IMO). The second picture BELOW demonstrated my 2nd problem, larger blocks of text seem to gradually decrease the width of the TextAreas or perhaps the HBox's above them. Before these subsequent HBox's were, added, the highlighted TextArea had enough space, for instance.
Is there any solution that will work for my needs?
I would be very grateful, thank you for your time!
Keith
This is not a trivial task (unless you find a workaround), I am afraid you will have to somehow to compute the actual width and height and apply it to the TextArea. The way I am thinking is to either find your magic numbers by trial and error approach or better take the message text add it to a Label and then compute its dimensions (width, height) and then use those in order to set the TextArea. Here is a small example :
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class MessagerTest extends Application {
private VBox displayPane = new VBox(5);
private TextArea messageArea = new TextArea();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
BorderPane mainPane = new BorderPane();
ScrollPane scrollPane = new ScrollPane(displayPane);
displayPane.setPadding(new Insets(10));
displayPane.prefWidthProperty().bind(scrollPane.widthProperty());
scrollPane.prefWidthProperty().bind(mainPane.widthProperty());
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
mainPane.setCenter(scrollPane);
mainPane.setBottom(messageArea);
mainPane.setPadding(new Insets(10));
messageArea.setPrefHeight(120);
messageArea.setFont(Font.font(16));
messageArea.setWrapText(true);
messageArea.setPromptText("Type a message here...");
messageArea.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ENTER && !e.isShiftDown()) {
sendMessage(messageArea.getText());
e.consume();
} else if (e.getCode() == KeyCode.ENTER && e.isShiftDown()) {
messageArea.appendText(System.lineSeparator());
}
});
mainPane.getStylesheets().add(this.getClass().getResource("messanger.css").toExternalForm());
stage.setScene(new Scene(mainPane, 450, 600));
stage.show();
}
private void sendMessage(String message) {
TextArea txtArea = new TextArea(message);
txtArea.setWrapText(true);
txtArea.setId("Message");
txtArea.setEditable(true);
resizeTextArea(txtArea);
displayPane.getChildren().add(txtArea);
messageArea.clear();
}
private void resizeTextArea(TextArea txtArea) {
String text = txtArea.getText();
double maxWidth = displayPane.getWidth() - 40;
HBox h = new HBox();
Label l = new Label(text);
l.setFont(Font.font(15));
h.getChildren().add(l);
Scene s = new Scene(h);
l.impl_processCSS(true);
l.applyCss();
double width = l.prefWidth(-1) + 20;
double height = l.prefHeight(-1) + 20;
if (width > maxWidth) {
txtArea.setMaxWidth(maxWidth);
txtArea.setMinWidth(maxWidth);
} else {
txtArea.setMaxWidth(width);
txtArea.setMinWidth(width);
}
txtArea.setMinHeight(height);
txtArea.setMaxHeight(height);
}
}
In case you want the CSS file too :
#Message {
-fx-background-color : transparent;
-fx-font-size : 15px;
-fx-text-fill: black;
-fx-display-caret:false;
}
#Message .content:pressed {
-fx-background-color: #E5E4E4;
}
#Message .content {
-fx-background-color: #F1F0F0;
}
.scroll-pane > .viewport {
-fx-background-color: white;
}
The problem with the above is that when you write everything is one line and let the TextArea wrap the text this cause the actual label height to be bigger so you will have to adjust the values a bit in that case.
To be honest I am not sure if this is the only approach you can take or if its the optimal solution. I believe its worth to lose the mouse selection of the text and use a Label instead of doing the above with the TextArea.
Let's say I already have a Shape on the screen. For example:
Circle circle = new Circle(x, y, radius);
circle.setFill(Color.YELLOW);
root.getChildren().add(circle);
I would like to create a Label "over" that Circle such that the Label is centered in the Circle, the font size is maximized to fit inside the Circle, etc.
I can see how this could be accomplished via binding, but that seems needlessly complicated if the position/size of these things will never change during runtime.
Thank you in advance for your help! I'm very new to JavaFX and not all that experienced at programming in the first place, so I apologize if I should've been able to find this out via my research.
Use a StackPane to automatically center the text on top of the shape.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
// java 8 code.
public class Circular extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(final Stage stage) throws Exception {
Text text = createText("Xyzzy");
Circle circle = encircle(text);
StackPane layout = new StackPane();
layout.getChildren().addAll(
circle,
text
);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private Text createText(String string) {
Text text = new Text(string);
text.setBoundsType(TextBoundsType.VISUAL);
text.setStyle(
"-fx-font-family: \"Times New Roman\";" +
"-fx-font-style: italic;" +
"-fx-font-size: 48px;"
);
return text;
}
private Circle encircle(Text text) {
Circle circle = new Circle();
circle.setFill(Color.ORCHID);
final double PADDING = 10;
circle.setRadius(getWidth(text) / 2 + PADDING);
return circle;
}
private double getWidth(Text text) {
new Scene(new Group(text));
text.applyCss();
return text.getLayoutBounds().getWidth();
}
}
Related
how to put a text into a circle object to display it from circle's center?
The answer to the related question discusses different bounds types for text (such as Visual bounds), in case you need that.
StackPane stackPane = new StackPane();
Circle circle = new Circle();
Label label = new Label("Hi");
circle.setFill(Color.GOLD);
circle.setStroke(Color.GRAY);
circle.radiusProperty().bind(label.widthProperty());
stackPane.getChildren().addAll(circle, label);