user5182503
user5182503

Reputation:

JavaFx: comboBox.setValue without ActionEvent

I have combobox with page numbers (1,2,3,etc). When user select the certain page (by mouse or by keyboard(as combobox editable)).I want to show and total pages. That's why on combobox action I also do:

combobox.setValue(currentPage+"/"+totalPages)

so the final code looks like

   @FXML
    private void onPageComboBoxAction(ActionEvent event){
       ....
       combobox.setValue(currentPage+"/"+totalPages)
    }

However this code fires one more ActionEvent and code cycles. So I see two possible ways:

  1. Handle some specific events (but which and how?)
  2. Find the way to change the value in combobox without firing one more event (again how?)

Can anyone help me to solve this problem?

Upvotes: 4

Views: 4467

Answers (3)

appsofteng
appsofteng

Reputation: 222

First remove all the listeners from the value property if any and add action event filter, e.g. by adding a method:

public class MyComboBox extends ComboBox<String> {
    public void setValueSilently(String value) {
        EventHandler<ActionEvent> filter = e -> e.consume();
        valueProperty().removeListener(myValueListener); // a custom listener
        addEventFilter(ActionEvent.ACTION, filter);
        setValue(value);
        removeEventFilter(ActionEvent.ACTION, filter);
        valueProperty().addListener(myValueListener);
    }
}

Upvotes: 2

James_D
James_D

Reputation: 209330

setValue(...) changes the value that is held by the combo box (i.e. it changes the data, or the state of the underlying model). It seems to me that what you really want here is just to change the way the data is displayed. You change the display of the selected value by setting the button cell.

Here is an example in which the button cell keeps track of both the selected item and the number of pages:

import java.util.Random;
import java.util.stream.IntStream;

import javafx.application.Application;
import javafx.collections.ListChangeListener.Change;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class PageNumberCombo extends Application {

    private static final Random RNG = new Random();

    @Override
    public void start(Stage primaryStage) {
        ComboBox<Integer> combo = new ComboBox<>();
        combo.setButtonCell(new ListCell<Integer>() {
            {
                itemProperty().addListener((obs, oldValue, newValue) -> update());
                emptyProperty().addListener((obs, oldValue, newValue) -> update());
                combo.getItems().addListener((Change<? extends Integer> c) -> update());
            }

            private void update() {
                if (isEmpty() || getItem() == null) {
                    setText(null);
                } else {
                    setText(String.format("%d / %d", getItem().intValue(), combo.getItems().size()));
                }
            }
        });

        Button reloadButton = new Button("Reload");
        reloadButton.setOnAction(e -> reload(combo));

        reload(combo);

        HBox root = new HBox(10, combo, reloadButton);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(24));
        primaryStage.setScene(new Scene(root, 240, 60));
        primaryStage.show();
    }

    private void reload(ComboBox<Integer> combo) {
        int numPages = RNG.nextInt(10) + 11 ;
        combo.getItems().clear();
        IntStream.rangeClosed(1, numPages).forEach(combo.getItems()::add);
    }

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

If your ComboBox is editable, then the button cell is by default a TextField, and the combo box's converter is used to convert the string value in the text field to the model value, and vice-versa. So in this case you just need to install a converter that converts between the integer x and the string "x / total".

Note that by default this converter is also used to display the text in the cells in the drop down, so if you want these to appear just as the integer values, you need to install a cellFactory to explicitly create those cells.

import java.util.Random;
import java.util.stream.IntStream;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class PageNumberCombo extends Application {

    private static final Random RNG = new Random();

    @Override
    public void start(Stage primaryStage) {
        ComboBox<Integer> combo = new ComboBox<>();

        combo.setEditable(true);
        combo.setConverter(new StringConverter<Integer>() {

            @Override
            public String toString(Integer object) {
                return object + " / " + combo.getItems().size();
            }

            @Override
            public Integer fromString(String string) {
                int index = string.indexOf('/');
                if (index < 0) {
                     index = string.length();
                }
                String text = string.substring(0, index).trim();
                try {
                    return Integer.parseInt(text);
                } catch (Exception exc) {
                    return 0 ;
                }
            }

        });

        combo.setCellFactory(lv -> new ListCell<Integer>() {
            @Override
            public void updateItem(Integer item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null) ;
                } else {
                    setText(item.toString());
                }
            }
        });

        Button reloadButton = new Button("Reload");
        reloadButton.setOnAction(e -> reload(combo));

        reload(combo);

        HBox root = new HBox(10, combo, reloadButton);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(24));
        primaryStage.setScene(new Scene(root, 240, 60));
        primaryStage.show();
    }

    private void reload(ComboBox<Integer> combo) {
        int numPages = RNG.nextInt(10) + 11 ;
        combo.getItems().clear();
        IntStream.rangeClosed(1, numPages).forEach(combo.getItems()::add);
    }

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

Upvotes: 1

Tomasz Mularczyk
Tomasz Mularczyk

Reputation: 36179

try to use prompt text instead setting value.

combobox.setPromptText(currentPage+"/"+totalPages);

but I don't know if it work once value is selected by user. Maybe you will have to setValue(null) just to see prompt text again. But then you would have to keep somewhere last selected value.

Upvotes: 0

Related Questions