Yannis P.
Yannis P.

Reputation: 2765

Setting the value of custom javafx ComboBox

EDIT: I have included sample files that give me errors I am trying to implement a ComboBox that filters items according to KeyStrokes, following the recipe from here. I 'd like to list countries of the world

I am having the FilterComboBox.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.collections.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import persondetails2.controller.*?>

<fx:root type="javafx.scene.control.ComboBox" fx:id="countries" 
         xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" 
         fx:controller="persondetails2.controller.FilterComboBox">
        <items>
          <FXCollections fx:factory="observableArrayList">
            <String fx:value="Austria" />
            <String fx:value="Denmark" />
            <String fx:value="France" />
            <String fx:value="Germany" />
            <String fx:value="Italy" />
            <String fx:value="Portugal" />
            <String fx:value="Spain" />
          </FXCollections>
        </items>
</fx:root>

FilterComboBox.java as in the recipe. Here is the source:

    /*
 * Follows: https://stackoverflow.com/questions/13362607/combobox-jump-to-typed-char
 */

package persondetails2.controller;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

/**
 *
 * @author DRY
 */
public class FilterComboBox<T> extends ComboBox<T> {
//implements Initializable{
    private final FilterComboBox<T> fcbo = this;

    private ObservableList<T> items;
    private ObservableList<T> filter;
    private String s;
    private Object selection;

    private class KeyHandler implements EventHandler<KeyEvent> {

        private SingleSelectionModel<T> sm;

        public KeyHandler() {
            sm = getSelectionModel();
            s = "";
            System.err.println("Initialized keyhandler");
        }

        @Override
        public void handle(KeyEvent event) {
            filter.clear();
            // handle non alphanumeric keys like backspace, delete etc
            if (event.getCode() == KeyCode.BACK_SPACE && s.length() > 0) {
                s = s.substring(0, s.length() - 1);
            } else {
                s += event.getText();
            }

            if (s.length() == 0) {
                fcbo.setItems(items);
                sm.selectFirst();
                return;
            }
            //System.out.println(s);
            if (event.getCode().isLetterKey()) {
                for (T item : items) {
                    if (item.toString().toUpperCase().startsWith(s.toUpperCase())) {

                        filter.add(item);
                        System.out.println(item);

                        fcbo.setItems(filter);

                        //sm.clearSelection();
                        //sm.select(item);

                    }
                }
                sm.select(0);
            }

        }
    }

    public FilterComboBox(final ObservableList<T> items) {
        super(items);
        this.items = items;
        this.filter = FXCollections.observableArrayList();

        setOnKeyReleased(new KeyHandler());

        this.focusedProperty().addListener(new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                if ((boolean)newValue == false) {
                    s = "";
                    fcbo.setItems(items);
                    fcbo.getSelectionModel().select((T)selection);
                }

            }

        });

        this.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                if (newValue != null) {
                    selection = (Object) newValue;
                }

            }
        });
    }

}

My main view is called PersonDetails.fxml

<?xml version="1.0" encoding="UTF-8"?>


<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.collections.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>


<AnchorPane xmlns:fx="http://javafx.com/fxml/1" id="AnchorPane" prefHeight="200" prefWidth="320" fx:controller="persondetails2.controller.PersonDetailsController">
  <ScrollPane id="personalData" fx:id="personaldata" fitToHeight="false" focusTraversable="false" maxHeight="-Infinity" prefHeight="-1.0" prefViewportHeight="1440.0" prefWidth="569.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-2.0">
    <content>
      <AnchorPane id="Content" focusTraversable="false" maxWidth="-1.0" minHeight="0.0" minWidth="0.0" prefHeight="-1.0" prefWidth="600.0">
        <children>
          <VBox focusTraversable="false" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
            <children>
              <Label prefHeight="21.0001220703125" text="Personal Details">
                <font>
                  <Font name="System Bold" size="14.0" fx:id="x1" />
                </font>
              </Label>

              <GridPane id="GridPane" focusTraversable="false" hgap="3.0" prefWidth="354.0" vgap="4.0">
                <children>
                  <Label text="Name:" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <labelFor>
                      <TextField fx:id="name" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="0" />
                    </labelFor>
                  </Label>
                  <fx:reference source="name" />
                  <Label text="Surname:" GridPane.columnIndex="0" GridPane.rowIndex="1" />
                  <TextField fx:id="surname" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                  <Label text="Country:" GridPane.columnIndex="0" GridPane.rowIndex="2"  />
                  <fx:include source="FilterComboBox.fxml" GridPane.columnIndex="1" GridPane.rowIndex="2" />
                </children>
              </GridPane>
            </children>
            <padding>
              <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
            </padding>
          </VBox>
        </children>
        <padding>
          <Insets left="5.0" right="5.0" />
        </padding>
      </AnchorPane>
    </content>
  </ScrollPane>
</AnchorPane>

and the respective controller is PersonDetailsController.java

package persondetails2.controller;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

/**
 *
 * @author DRY
 */
public class PersonDetailsController implements Initializable {

    @FXML
    private Label label;

    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }    

}

This configuration gives me now an java.lang.InstantiationException: persondetails2.controller.FilterComboBox. I would like to read the data in the controller initialisation and then be able to set the values in the TextFields as well as the chosen value in the ComboBox.

Any ideas on how to continue?

Upvotes: 0

Views: 2809

Answers (1)

Uluk Biy
Uluk Biy

Reputation: 49185

Since you are setting the items for combobox in FXML file, I refactored the FilterComboBox constructor as a no-arg and added the code that loads the FXML file:

public class FilterComboBox<T> extends ComboBox<T> {

    private final ObservableList<T> items;
    private final ObservableList<T> filter;
    private String s;
    private Object selection;

    private class KeyHandler implements EventHandler<KeyEvent> {

        private SingleSelectionModel<T> sm;

        public KeyHandler() {
            sm = getSelectionModel();
            s = "";
            System.err.println("Initialized keyhandler");
        }

        @Override
        public void handle(KeyEvent event) {
            filter.clear();
            // handle non alphanumeric keys like backspace, delete etc
            if (event.getCode() == KeyCode.BACK_SPACE && s.length() > 0) {
                s = s.substring(0, s.length() - 1);
            } else {
                s += event.getText();
            }

            if (s.length() == 0) {
                setItems(items);
                sm.selectFirst();
                return;
            }
            //System.out.println(s);
            if (event.getCode().isLetterKey()) {
                for (T item : items) {
                    if (item.toString().toUpperCase().startsWith(s.toUpperCase())) {

                        filter.add(item);
                        System.out.println(item);

                        setItems(filter);

                        //sm.clearSelection();
                        //sm.select(item);
                    }
                }
                sm.select(0);
            }

        }
    }

    public FilterComboBox() {

        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("FilterComboBox.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

        items = getItems();
        this.filter = FXCollections.observableArrayList();

        setOnKeyReleased(new KeyHandler());

        this.focusedProperty().addListener(new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                if ((boolean) newValue == false) {
                    s = "";
                    setItems(items);
                    getSelectionModel().select((T) selection);
                }
            }
        });

        this.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                if (newValue != null) {
                    selection = (Object) newValue;
                }
            }
        });
    }

}

Next, changed the FilterComboBox.fxml as:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.collections.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import persondetails2.controller.*?>

<fx:root type="FilterComboBox"
         xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2">
        <items>
          <FXCollections fx:factory="observableArrayList">
            <String fx:value="Austria" />
            <String fx:value="Denmark" />
            <String fx:value="France" />
            <String fx:value="Germany" />
            <String fx:value="Italy" />
            <String fx:value="Portugal" />
            <String fx:value="Spain" />
          </FXCollections>
        </items>
</fx:root>

and replaced the fx:include in PersonDetails as:

<?import persondetails2.controller.FilterComboBox ?>
...

<FilterComboBox GridPane.columnIndex="1" GridPane.rowIndex="2" />

Upvotes: 1

Related Questions