0

I wrote the following method that is used to react when I press a button:

private void handlePlayButton(ActionEvent e){
    for (int i = 0; i < commands.size(); i++) {
        list.getSelectionModel().select(i);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException ie){
            System.out.println("Error at handlPlayButton: interruption");
        }

    }

}

In this code I try to select each element starting at the first line and than wait 1 second to select the next one, but it seems like it waits n-1 seconds (where n is the size of the items) and than selects the last item. Is there any way to fix this?

The field list is a ListView<String> by the way.

Karatawi
  • 378
  • 2
  • 6
  • 16
  • Perhaps you are trying to implement logic of a similar pattern to: [Timeline to Display Random images one by one](http://stackoverflow.com/questions/35869192/timeline-to-display-random-images-one-by-one), but perhaps not, it is a bit hard for me to understand what you are really trying to accomplish here. Never call sleep on the JavaFX application thread (it just hangs your UI). – jewelsea Mar 17 '16 at 20:33
  • Well I have a ListView with let's say 4 elements in it (so 4 lines) and I have a button. When I press the button it must select the first element, wait for 1 second then select the second element, wait for 1 second, etc. But in my case it waits 3 seconds and than selects the last element without selecting one of the previous ones. So I want to fix this. – Karatawi Mar 17 '16 at 20:52
  • OK, I think I get what you are trying to do now, but I have no idea why you would wish to do something like this (it seems very strange). Also what would happen if the user pressed the button again or selected another item in the ListView, while the automated cycling logic was ongoing? And what happens when the end of the list is reached, does the automated process just stop, or does it cycle around again to the first item and then continue? – jewelsea Mar 17 '16 at 21:00
  • Hmmm very good questions: repressing the button would just end the currect cycle and restart it. If the user selects an item during a cycle it would stop the cycle and select the item that the user selected (obviously). When the process ends it just selects the last item and that's it. This question seems strange indeed but each element in the list is connected with a different part of the GUI where it would output things depending on the list value but you don't have to worry about that – Karatawi Mar 17 '16 at 21:11

1 Answers1

3

This question is relatively obscure, so I don't think the solution will be generally applicable to anybody else. The basic solution is to use a Timeline to automate updating the selection in the ListView when the user presses a "Cycle" button in the UI.

There is a bit of additional logic to handle edge cases such as what to do if the user modifies the selection while the cycling is ongoing or if the user restarts the cycling process. If the user clicks on the currently selected item, the automated cycling does not stop, so the original asker might wish to add some of his own code to do that if he adopts a similar solution.

Also there is some logic for placing ImageViews in the ListView, but that isn't central to the application and can be ignored for more common types stored used in a ListView such as Strings. The ImageView related stuff is just there to make the app look a bit better.

fishes

import javafx.animation.*;
import javafx.application.Application;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.Arrays;
import java.util.stream.Collectors;

public class RotatingSushiMenu extends Application {

    private static final Duration AUTO_CHANGE_PAUSE = Duration.seconds(2);

    private boolean autoChange;

    @Override
    public void start(Stage stage) {
        ObservableList<Image> images = FXCollections.observableList(
                Arrays.stream(IMAGE_LOCS)
                        .map(Image::new)
                        .collect(Collectors.toList())
        );

        ListView<Image> list = new ListView<>(FXCollections.observableList(images));
        list.setCellFactory(param -> new ImageListCell());

        Timeline timeline = new Timeline(
                new KeyFrame(Duration.ZERO),
                new KeyFrame(
                        AUTO_CHANGE_PAUSE,
                        e -> {
                            int curIdx = list.getSelectionModel().getSelectedIndex();
                            if (curIdx < list.getItems().size() - 1) {
                                autoChange = true;
                                list.scrollTo(curIdx + 1);
                                list.getSelectionModel().select(curIdx + 1);
                                autoChange = false;
                            }
                        }
                )
        );
        timeline.setCycleCount(list.getItems().size());

        Button cycle = new Button("Cycle");
        cycle.setOnAction(event -> {
            if (list.getItems().size() > 0) {
                list.scrollTo(0);
                list.getSelectionModel().select(0);
                timeline.playFromStart();
            }
        });

        list.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Image>) c -> {
            if (!autoChange) {
                timeline.stop();
            }
        });

        VBox layout = new VBox(10, cycle, list);
        layout.setPadding(new Insets(10));
        stage.setScene(new Scene(layout));
        stage.show();
    }

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

    private class ImageListCell extends ListCell<Image> {
        final ImageView imageView = new ImageView();

        ImageListCell() {
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        }

        @Override
        protected void updateItem(Image item, boolean empty) {
            super.updateItem(item, empty);

            if (empty || item == null) {
                imageView.setImage(null);
                setText(null);
                setGraphic(null);
            }

            imageView.setImage(item);
            setGraphic(imageView);
        }
    }

    // image license: linkware - backlink to http://www.fasticon.com
    private static final String[] IMAGE_LOCS = {
            "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Blue-Fish-icon.png",
            "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Red-Fish-icon.png",
            "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Yellow-Fish-icon.png",
            "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Green-Fish-icon.png"
    };
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • Is there any reason you use a `ObservableList` backed by a `ObservableList` backed by a `List` (especially since the backing list is never used anywhere else)? Personally I'd use `FXCollections.observableArrayList(Arrays.stream(IMAGE_LOCS).map(Image::new).toArray(Image[]::new))` or `Arrays.stream(IMAGE_LOCS).map(Image::new).collect(Collectors.toCollection(FXCollections::observableArrayList)` – fabian Mar 18 '16 at 08:50
  • The reason the particular chain of data structures was chosen is that is was the first one I came to mind. I didn't think about it too much. I realize that it is not the most efficient data structure choice to solve the problem. Given the sample only has four items in it, it didn't matter much for the sample data, and would only have an impact for very large data sets, which, by the nature of the problem, would be unexpected. Feel free to edit the sample code in the answer to use an alternate data structure which provides more efficiency or clarity. – jewelsea Mar 18 '16 at 17:45