Search code examples
javafxjavafx-8fxml

How can I make two TextArea scroll together in JavaFX


I tried some methods, but I didn’t work. Basically I tried to do it by creating a new TextArea. But I just want the two scrolls to be tied to each other usersChatArea scroll and myChatArea scroll Is there any easy way to make it so that if I scroll the one with the scrollbar, the other one will automatically follow?

it is my MainChatController:

package ru.geekbrains.chat.client;
import com.sun.javafx.menu.MenuItemBase;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.NodeOrientation;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.ResourceBundle;

public class MainChatController {

    public TextArea usersChatArea = new TextArea();
    public TextArea myChatArea = new TextArea();
    public ListView onlineUsers;
    public TextField inputField;
    public Button btnSendMessage;
    public Button btnSendMessageUser;
    public Button btnOpenAboutWindow;


    public void mockAction(ActionEvent actionEvent) {
        System.out.println("MOCK!");
    }

    public void exit(ActionEvent actionEvent) {
        Platform.exit();
    }

    public void showAbout(ActionEvent actionEvent) {

    }

    public void showHelp(ActionEvent actionEvent) throws URISyntaxException, IOException {
        Desktop desktop = Desktop.getDesktop();
        desktop.browse(new URI("https://docs.google.com/document/d/1wr0YEtIc5yZtKFu-KITqYnBtp8KC28v2FEYUANL0YAM/edit?usp=sharing"));
    }


    public void sendMessage(ActionEvent actionEvent) {
        appendTextFromTF();
    }

    public void sendMessageUser(ActionEvent actionEvent) {
        appendTextFromTF2();
    }

    private void appendTextFromTF() {
        int c = 1;
        String msg1 = inputField.getText();
        myChatArea.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
        if (msg1.isEmpty()) return;
        for (int i = 0; i < c ; i++) {
            usersChatArea.appendText(System.lineSeparator());
        }
        myChatArea.appendText( msg1 + " :ME  " + System.lineSeparator());
        c++;
        inputField.clear();
    }

    private void appendTextFromTF2() {
        int b = 1;
        String msg = inputField.getText();
        usersChatArea.setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
        if (msg.isEmpty()) return;
        for (int i = 0; i < b ; i++) {
            myChatArea.appendText(System.lineSeparator());
        }
        b++;
        usersChatArea.appendText("  User: " + msg + System.lineSeparator());
        inputField.clear();
    }

    public void btnOpenAboutWindow(ActionEvent actionEvent) {
        btnOpenAboutWindow.setOnAction(new EventHandler<ActionEvent>() {
                                           public void handle(ActionEvent event) {
                                               try {
                                                   FXMLLoader loader = new FXMLLoader();
                                                   loader.setLocation(getClass().getResource("/about.fxml"));
                                                   Parent root = loader.load();

                                                   Scene scene = new Scene(root);
                                                   Stage stage = new Stage();
                                                   stage.setScene(scene);
                                                   stage.setAlwaysOnTop(true);
                                                   stage.setTitle("About");
                                                   stage.show();
                                               } catch (IOException e) {
                                                   e.printStackTrace();
                                               }
                                           }
                                       }
        );

    }
}

it is my App:

package ru.geekbrains.chat.client;


import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;



public class App extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader();

        loader.setLocation(getClass().getResource("/scene.fxml"));
        Parent root = loader.load();
        MainChatController mainChatController = new MainChatController();


        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setAlwaysOnTop(true);
        stage.setTitle("April Chat");
        stage.show();

    }
}


it is my FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<VBox prefHeight="500" prefWidth="650" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ru.geekbrains.chat.client.MainChatController">
    <children>
        <MenuBar>
            <Menu text="File">
                <MenuItem onAction="#mockAction" text="Item1" />
                <MenuItem onAction="#mockAction" text="Item2" />
                <MenuItem onAction="#mockAction" text="Item3" />
                <MenuItem onAction="#mockAction" text="Item4" />
                <MenuItem onAction="#exit" text="Exit" />
            </Menu>
            <Menu text="Edit">
                <MenuItem onAction="#mockAction" text="Item1" />
                <MenuItem onAction="#mockAction" text="Item2" />
                <MenuItem onAction="#mockAction" text="Item3" />
                <MenuItem onAction="#mockAction" text="Item4" />
                <MenuItem onAction="#mockAction" text="Item5" />
            </Menu>
            <Menu text="View">
                <MenuItem onAction="#mockAction" text="Item1" />
                <MenuItem onAction="#mockAction" text="Item2" />
                <MenuItem onAction="#mockAction" text="Item3" />
                <MenuItem onAction="#mockAction" text="Item4" />
                <MenuItem onAction="#mockAction" text="Item5" />
            </Menu>
            <Menu text="Help">
                <MenuItem onAction="#mockAction" text="Item1" />
                <MenuItem onAction="#mockAction" text="Item2" />
                <MenuItem onAction="#mockAction" text="Item3" />
                <MenuItem onAction="#showHelp" text="Help" />
                <MenuItem onAction="#showAbout" text="About" />

            </Menu>

        </MenuBar>
        <Button fx:id="btnOpenAboutWindow" onAction="#btnOpenAboutWindow" prefHeight="20.0" prefWidth="50.0" text="ABOUT">
            <HBox.margin>
                <Insets left="5.0" right="5.0" />
            </HBox.margin>
         <font>
            <Font size="10.0" />
         </font>
        </Button>

        <HBox prefHeight="471.0" prefWidth="705.0" VBox.vgrow="ALWAYS">
            <TextArea fx:id="usersChatArea" editable="false" nodeOrientation="LEFT_TO_RIGHT" prefHeight="461.0" prefWidth="250.0" style="-fx-border-color: white;" styleClass="usersChatArea" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS">
            <HBox.margin>
               <Insets left="3.0" />
            </HBox.margin>
            <padding>
               <Insets right="3.0" />
            </padding>

            <font>
               <Font size="18.0" />
            </font></TextArea>
            <TextArea fx:id="myChatArea" editable="false" nodeOrientation="RIGHT_TO_LEFT" prefHeight="461.0" prefWidth="256.0" style="-fx-border-color: white" styleClass="myChatArea" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS">
            <font>
               <Font size="18.0" />
            </font></TextArea>

            <ListView fx:id="onlineUsers" prefHeight="461.0" prefWidth="289.0">
                <HBox.margin>
                    <Insets left="5.0" right="5.0" />
                </HBox.margin>
            </ListView>
            <padding>
                <Insets bottom="5.0" top="5.0" />
            </padding>

        </HBox>
        <HBox>
            <TextField fx:id="inputField" onAction="#sendMessage" prefHeight="70.0" prefWidth="487.0" HBox.hgrow="ALWAYS">
                <HBox.margin>
                    <Insets left="5.0" right="5.0" />
                </HBox.margin>
            <font>
               <Font size="24.0" />
            </font>
            </TextField>
            <Button fx:id="btnSendMessage" onAction="#sendMessage" prefHeight="70.0" prefWidth="124.0" text="SEND">
                <HBox.margin>
                    <Insets left="5.0" right="5.0" />
                </HBox.margin>
            </Button>
            <Button fx:id="btnSendMessageUser" onAction="#sendMessageUser" prefHeight="70.0" prefWidth="127.0" text="SEND USER ">
                <HBox.margin>
                    <Insets left="5.0" right="5.0" />
                </HBox.margin>
            </Button>
            <padding>
                <Insets bottom="5.0" top="5.0" />
            </padding>
        </HBox>
    </children>
</VBox>

it is my Main

package ru.geekbrains.chat.client;

public class Main {
    public static void main(String[] args) {
        App.main(args);
    }
}

Solution

  • A TextArea has these two properties you can use:

    DoubleProperty scrollLeftProperty()

    The number of pixels by which the content is horizontally scrolled.

    DoubleProperty scrollTopProperty()

    The number of pixels by which the content is vertically scrolled (docs).

    Some simple bidirectional bindings as follows may be enough for your needs:

    // binding for horizontal scroll bar positions:
    textArea1.scrollLeftProperty().bindBidirectional(textArea2.scrollLeftProperty());
    
    // vertical:
    textArea1.scrollTopProperty().bindBidirectional(textArea2.scrollTopProperty());