2
votes

I'm making a chat application using JavaFX for the GUI. I display the chat content in a ListView, but I have one big problem - it's very very slow. When I add new items to the list and especially when I scroll the list up/down. I think maybe it has something to do with the fact that the list refreshes itsellf every time a new item is added (each cell in the list!) and also refreshes every time I scroll up/down. Does someone know what can I do to solve this problem? TNX

I override ListCell's updateItem:

chatListView.setCellFactory(new Callback<ListView<UserInfo>, ListCell<UserInfo>>() {
            @Override
            public ListCell<UserInfo> call(ListView<UserInfo> p) {
                ListCell<UserInfo> cell = new ListCell<UserInfo>() {
                    @Override
                    protected void updateItem(UserInfo item, boolean bln) {
                        super.updateItem(item, bln);

                        if (item != null) {
                            BorderPane borderPane = new BorderPane();
                            ImageView profileImage = new ImageView(new Image(item.getImageURL()));
                            profileImage.setFitHeight(32);
                            profileImage.setFitWidth(32);

                            Rectangle clip = new Rectangle(
                                    profileImage.getFitWidth(), profileImage.getFitHeight()
                            );
                            clip.setArcWidth(30);
                            clip.setArcHeight(30);
                            profileImage.setClip(clip);
                            SnapshotParameters parameters = new SnapshotParameters();
                            parameters.setFill(Color.TRANSPARENT);
                            WritableImage image = profileImage.snapshot(parameters, null);
                            profileImage.setClip(null);
                            profileImage.setImage(image);

                            ImageView arrowImage = new ImageView(new Image("arrow1.png"));
                            ImageView arrowImage2 = new ImageView(new Image("arrow1.png"));
                            Label nameLabel = new Label(item.getUserName());
                            nameLabel.setStyle(" -fx-text-alignment: center; -fx-padding: 2;");

                            HBox hbox = null;
                            Label textLabel = new Label();
                            String messageText = splitTolines(item.getMessage());
                            textLabel.setText(messageText);
                            textLabel.setStyle("-fx-background-color: #a1f2cd; "
                                    + "-fx-padding: 10;\n"
                                    + "-fx-spacing: 5;");
                            hbox = new HBox(arrowImage, textLabel);

                            VBox vbox = new VBox(profileImage, nameLabel);
                            BorderPane.setMargin(vbox, new Insets(0, 10, 10, 10));
                            BorderPane.setMargin(hbox, new Insets(10, 0, 0, 0));

                            //Time
                            Date dNow = new Date();
                            SimpleDateFormat ft = new SimpleDateFormat("hh:mm a");
                            Label timeLabel = new Label(ft.format(dNow));
                            timeLabel.setStyle("-fx-font: 8px Tahoma;  -fx-width: 100%");

                            HBox hbox2 = new HBox(arrowImage2, timeLabel);
                            arrowImage2.setVisible(false);
                            VBox vbox2 = new VBox(hbox, hbox2);

                            borderPane.setCenter(vbox2);
                            borderPane.setLeft(vbox);
                            setGraphic(borderPane);
                        }
                    }
                };

                return cell;
            }
        });
3
You should include an mcve, otherwise your question is effectively unanswerable.jewelsea
Yes, please provide sample code. Do you override ListCell's updateItem method? Do you do anything expensive in there? Flowless may or may not help in your situation.Tomas Mikula

3 Answers

4
votes
  1. Never ever add (big) GUI Elements in updateItem() without checking if it is not already there.

    1. updateItem() is called everytime for EVERY SINGLE ROW when you scroll, resize or change gui in any other way.
    2. You should alway reset the graphic to null if you do not have an item or the second boolean of updateItem(item, empty) is false, because the second boolean is the EMPTY flag.
  2. I recommend to you that you use a VBox instead of a ListView.

0
votes

You must not build new instances of your components everytime the view gets updated.

Instanciate them one time initialy, then you reuse and change their attributes.

0
votes

I just noticed that too. It's too slow even for a list containing only 5-10 items (with scaled images and text). Since I need no selection feature, I also rewrote the code to use VBox instead and the slowness is immediately gone!

To emulate the setItems, I have a helper function which you may find handy:

public static <S, T> void mapByValue(
        ObservableList<S> sourceList,
        ObservableList<T> targetList,
        Function<S, T> mapper)
{
    Objects.requireNonNull(sourceList);
    Objects.requireNonNull(targetList);
    Objects.requireNonNull(mapper);
    targetList.clear();
    Map<S, T> sourceToTargetMap = new HashMap<>();
    // Populate targetList by sourceList and mapper
    for (S s : sourceList)
    {
        T t = mapper.apply(s);
        targetList.add(t);
        sourceToTargetMap.put(s, t);
    }
    // Listen to changes in sourceList and update targetList accordingly
    ListChangeListener<S> sourceListener = new ListChangeListener<S>()
        {
            @Override
            public void onChanged(ListChangeListener.Change<? extends S> c)
            {
                while (c.next())
                {
                    if (c.wasPermutated())
                    {
                        for (int i = c.getFrom(); i < c.getTo(); i++)
                        {
                            int j = c.getPermutation(i);
                            S s = sourceList.get(j);
                            T t = sourceToTargetMap.get2(s);
                            targetList.set(i, t);
                        }
                    }
                    else
                    {
                        for (S s : c.getRemoved())
                        {
                            T t = sourceToTargetMap.get2(s);
                            targetList.remove2(t);
                            sourceToTargetMap.remove2(s);
                        }
                        int i = c.getFrom();
                        for (S s : c.getAddedSubList())
                        {
                            T t = mapper.apply(s);
                            targetList.add(i, t);
                            sourceToTargetMap.put(s, t);
                            i += 1;
                        }
                    }
                }
            }
        };
    sourceList.addListener(new WeakListChangeListener<>(sourceListener));
    // Store the listener in targetList to prevent GC
    // The listener should be active as long as targetList exists
    targetList.addListener((InvalidationListener) iv ->
        {
            Object[] refs = { sourceListener, };
            Objects.requireNonNull(refs);
        });
}

It can then be used like:

ObservableList<Bookmark> bookmarkList;
VBox bookmarkListVBox;
mapByValue(bookmarkList, bookmarkListVBox.getChildren(), bmk -> new Label(bmk.getName());

To automatically update the list (VBox's children) from observable list.

PS: other functions such as grouping are here => ObservableListHelper