2

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.

enter image description here 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.

Tgggggggg

Is there any solution that will work for my needs? I would be very grateful, thank you for your time!

Keith

Keith
  • 161
  • 1
  • 10
  • Do you really need `TextArea`s here? Wouldn't labels work? (In fact, you should probably consider using a `ListView`, which would be much more efficient than a `GridPane` anyway.) – James_D Apr 03 '18 at 01:34
  • Hey James, Good point, I prefer to not use a label as I want the Text to be selected and I forsee similar height problems with a Label anyway. However, I suppose a ListView may be better, I will look into using a ListView :) – Keith Apr 03 '18 at 01:50
  • Ah, yes, a `Label` won't let you select the text (but it will size correctly). A `ListView` won't help with sizing if you still want to display a `TextArea` in it (but will be more efficient). – James_D Apr 03 '18 at 01:52

1 Answers1

3

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;
}

enter image description here

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.

JKostikiadis
  • 2,847
  • 2
  • 22
  • 34