Search code examples
javafxtextarealimit

JavaFX - Automate Textarea purging (rolling policy?)


I have a Textarea component that is used to display messages going between 2 applications (HL7 messages to be exact). Each time a message is successfully processed, the message is logged as well as the acknowledgement sent back from the receiving system. These messages can be sent by the thousands and i feel like there will inevitably be a point where problems will start happening when this components "overflows". I would like to implement a rollover strategy, kinda like log4j where you can tell it to only keep say 10 files of 1MB. I would like to have a value the user can set and my component (perhaps an extension of the Textarea component) would automatically only keep that number of rows and purge the first ones as new ones are added. I am relatively new to JavaFX (coming from Swing), i have looked at the options but cannot quite figure out how one would do this.

Thanks


Solution

  • As I mention in the comments section of the question, I recommend you use a ListView instead of a TextArea. This gives you a few benefits:

    1. ListView is a "virtual" control—it only renders enough cells to fill the visible space and the cells are reused while scrolling. This allows one to have thousands of items in the ListView without rendering performance suffering.

    2. The model of a ListView is an observable list, which is a much better way to represent separate messages than having one giant String in a TextArea. When adding an element to the list causes it to grow beyond some arbitrary capacity you can simply remove an item(s) from the start of said list (or end, if inserting items at the top rather than the bottom).

    3. A ListView provides much greater flexibility when it comes to displaying your message. This is accomplished with a custom cell factory. For instance, you could have certain ranges of the message be different colors by using a TextFlow as the graphic of the ListCell. Make sure you read the documentation of Cell.updateItem(Object,boolean), however, as you have to override that method correctly; failing to do so can lead to artifacts due to the fact cells are reused.

    A simple example:

    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.function.Consumer;
    import javafx.animation.PauseTransition;
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.ListView;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    public class Main extends Application {
    
        private static void generateMessages(Consumer<String> onNewMessage) {
            AtomicInteger counter = new AtomicInteger();
            PauseTransition pt = new PauseTransition(Duration.seconds(1));
            pt.setOnFinished(e -> {
                onNewMessage.accept(String.format("Message #%,d", counter.incrementAndGet()));
                pt.playFromStart();
            });
            pt.playFromStart();
        }
    
        @Override
        public void start(Stage primaryStage) {
            ListView<String> listView = new ListView<>();
            primaryStage.setScene(new Scene(listView, 500, 300));
            primaryStage.show();
    
            generateMessages(message -> {
                listView.getItems().add(message);
                if (listView.getItems().size() > 10) {
                    listView.getItems().remove(0);
                }
            });
        }
    
    }