Search code examples
javacsslistviewjavafxlistcellrenderer

Difficulties while trying to style individual list-view cells, like a chat app


For my Chat GUI, I'm trying to 'show' the messages styled as Facebook's Messenger does, or at least, in that manner. If I'm the one sending them, they should be aligned to the right, if I'm getting a message from anyone else, they should be aligned to the left, and their background-color should be different.

I've made something similar, but it really looks ugly, and on top of this, I cannot style them individually, but in the even-odd way, so it looks something like this, even tho, a message has never been sent to 'create' them.enter image description here

The message structure is really simple

public void sendMessageOnClick(){
        sendButton.setOnAction((e) -> {
            String message = textInput.getText();
            chatHistory.getItems().add("Sorin: " + message + "\n");
            textInput.clear();
            sendButton.setDisable(true);
        });
    }

Solution

  • The trick is to use a cell factory returing custom ListCells:

    @FXML
    private ListView<ChatMessage> chatHistory;
    
    private String user = "Sorin";
    private static final PseudoClass USER_MESSAGE = PseudoClass.getPseudoClass("user-message");
    
    @FXML
    private void initialize() {
        sendButton.disableProperty().bind(textInput.textProperty().isEmpty());
    
        final ColumnConstraints ownUserConstraints = new ColumnConstraints();
        ownUserConstraints.setHalignment(HPos.LEFT);
        ownUserConstraints.setHgrow(Priority.ALWAYS);
    
        final ColumnConstraints foreignUserConstraints = new ColumnConstraints();
        foreignUserConstraints.setHalignment(HPos.RIGHT);
        foreignUserConstraints.setHgrow(Priority.ALWAYS);
    
        final ColumnConstraints userConstraints = new ColumnConstraints();
        userConstraints.setHgrow(Priority.NEVER);
    
        chatHistory.setCellFactory(lv -> new ListCell<ChatMessage>() {
    
            private final Label message;
            private final Label userName;
            private final GridPane content;
    
            {
                message = new Label();
                userName = new Label();
                content = new GridPane();
                content.setHgap(10);
                content.addRow(0, userName, message);
                content.getColumnConstraints().addAll(userConstraints, userConstraints);
            }
    
            @Override
            protected void updateItem(ChatMessage item, boolean empty) {
                super.updateItem(item, empty);
    
                boolean userMessage;
                if (empty || item == null) {
                    userMessage = false;
                    setGraphic(null);
                } else {
                    userMessage = user.equals(item.getUserName());
                    userName.setText(item.getUserName() + ":");
                    message.setText(item.getMessage());
                    setGraphic(content);
    
                    content.getColumnConstraints().set(1, userMessage
                            ? ownUserConstraints
                            : foreignUserConstraints);
                }
                pseudoClassStateChanged(USER_MESSAGE, userMessage);
            }
    
        });
    }
    
    public class ChatMessage {
    
        private final String userName;
        private final String message;
    
        public ChatMessage(String userName, String message) {
            this.userName = userName;
            this.message = message;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public String getMessage() {
            return message;
        }
    
    }
    
    sendButton.setOnAction((e) -> {
        ...
        chatHistory.getItems().add(new ChatMessage(user, message);
        ...
    });
    

    CSS Stylesheet:

    .list-cell {
        -fx-background: lightblue;
    }
    
    .list-cell:user-message,
    .list-cell:empty {
        -fx-background: -fx-control-inner-background;
    }
    
    .list-view:focused .list-cell:filled:selected {
        -fx-background: -fx-selection-bar;
    }
    

    BTW: For a alignment of the own user's message that does not overlap other usernames you need to set the with of userConstraints to the largest text width for the user names...