0

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?

Community
  • 1
  • 1
Yannis P.
  • 2,745
  • 1
  • 24
  • 39
  • Can you show the code where you load the fxml? – James_D May 18 '14 at 14:04
  • Just edited the post to reflect the way it is loaded – Yannis P. May 18 '14 at 16:35
  • I don't think you can use `` with a dynamic root fxml (i.e. one that uses ``): there's no way to set the root element. You should probably just do ``, this assumes your `FilterComboBox.java` is correctly set up. – James_D May 18 '14 at 17:11
  • Thank you @James_D. I tried to do the steps [here](http://rterp.wordpress.com/2013/09/13/creating-custom-javafx-components-with-scene-builder-and-fxml/) for creating a ``FilterComboBox`` control but in the ``PersonDetails.fxml`` (see above for a bit different version) I get an error when editing the FXML that instances ``FilterComboBox`` cannot be created by FXML loader (although I 've included the packages in the preamble) – Yannis P. May 19 '14 at 12:21

1 Answers1

1

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" />
Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
  • Thanks @Uluk Biy, I overcame this hurdle but now it gives me the error that it cannot set ``FilterComboBox field packages.countries to javafx.scene.control.ComboBox``. Sorry I don't give more details, its because this is a part of a complicated design – Yannis P. May 18 '14 at 16:17
  • for the moment this gives me an InstantiationException – Yannis P. May 18 '14 at 17:17
  • @YannisP. It is difficult to answer by guessing. So it seems you have 2 FXML files and 2 controller files. Can you post only those in reproducible format. – Uluk Biy May 18 '14 at 17:42
  • Hi @Uluk Biy. I have edited the post to include a reproducible example, thanks! – Yannis P. May 19 '14 at 12:22
  • Thanks @Uluk Biy, it works like a charm! In fact I am able to assign an ``fx:id`` in the ``PersonDetails.fxml`` so that I can control the ComboBox from the PersonDetailsController, i.e. load values. Just a small detail in my case it needed ``getResource("FilterComboBox.fxml")`` to load correctly. Btw the does parameterised constructor imply that the element ```` needs to have ```` elements? – Yannis P. May 19 '14 at 20:37
  • 1
    @YannisP. No, an instance of FilterComboBox will be created by FXMLLoader through reflection. By default (if the factory is not used) it will call no-arg constructor. The instantiation exception you were seeing was due to this, i.e. the loader could not find no-arg constructor. The parameterized constructor can be used within factories, or you create a parameterized instance explicitly yourself then call setController of the loader. – Uluk Biy May 20 '14 at 06:16