ComboBox mit FilteredList


#1

Hallo,

ich habe in JavaFX eine ComboBox mit einer FilteredList kompiniert, so dass das Popup der ComboBox aufgrund der Eingabe in den Editor der Combobox angepasst wird.

Wenn ich aus der ComboBox mit der Maus einen Text selektiert habe, dann im Editor diesen markiere und dann eine Tastatureingabe machen will, geht der erste Tastendruck “quasi” ins Leere.

Hat jemand von den Profis eine Idee, wie ich den markierten Text gleich mit dem ersten Tastendruck ersetzen kann?

Hier mein Beispiel mit zwei ComboBoxen. Die Linke hat den Filter, die recht ist hat keinen Filter (ich habe die Datei auch als Txt hochgeladen.

ComboBox.txt (2,2 KB)

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Main extends Application {
    public void start(Stage stage) {
        HBox root = new HBox();

        ComboBox<String> prim = new ComboBox<String>();
        prim.setEditable(true);
        
        ComboBox<String> sec = new ComboBox<String>();
        sec.setEditable(true);

        
        ObservableList<String> itemsPrim = FXCollections.observableArrayList("Ahh", "Bhh", "Chh", "Dhh", "Ehh", "Fhh",
                "Ghh", "Hhh", "Ihh", "Jhh");
        ObservableList<String> itemsSec = FXCollections.observableArrayList("One", "Two", "Three", "Four", "Five", "Six",
                "Seven", "Eight", "Nine", "Ten");

      
        FilteredList<String> filteredItems = new FilteredList<String>(itemsSec, p -> true);

  
        prim.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
            final TextField editor = prim.getEditor();
            final String selected = prim.getSelectionModel().getSelectedItem();

   
            Platform.runLater(() -> {
            
                if (selected == null || !selected.equals(editor.getText())) {
                    filteredItems.setPredicate(item -> {
                      
                        if (item.toUpperCase().startsWith(newValue.toUpperCase())) {
                            return true;
                        } else {
                            return false;
                        }
                    });
                }
            });
        });

        prim.setItems(filteredItems);
        sec.setItems(itemsPrim);

        root.getChildren().add(prim);
        root.getChildren().add(sec);

        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

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

Danke für Eure Unterstützung

GGK


#2

Bis einer von den Profis antwortet (zu denen gehöre ich bestenfalls bei Swing, aber nicht bei JavaFX) nur das hier, wo ich mit etwas rumprobieren gelandet bin:

    prim.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
        final TextField editor = prim.getEditor();
        final String selected = prim.getSelectionModel().getSelectedItem();

        System.out.println("Changed from "+oldValue+" to "+newValue);
        Platform.runLater(() -> 
        {
            if (newValue.isEmpty())
            {
                editor.setText(oldValue);
                return;
            }
            if (selected == null || !selected.equals(editor.getText())) {
                filteredItems.setPredicate(item -> {
                    return item.toUpperCase().startsWith(newValue.toUpperCase());
                });
            }
        });
    });

Aaaaber: Eine wichtige Frage ist IMHO, unter welchen Bedingungen der Filter zurückgesetzt werden soll. Wenn man z.B. "T" eintippt und dann “Three” auswählt, kann man bei einem weiteren Ausklappen immernoch “Two” und “Three” auswählen - ist das so gedacht?


#3

Ich kann auch nur mit einem Swing-Beispiel dienen. Bei JavaFX bin ich leider auch raus.


#4

Probier einmal folgendes:

   prim.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
        final TextField editor = prim.getEditor();
        final String selected = prim.getSelectionModel().getSelectedItem();

        Platform.runLater(() -> {
            if (selected == null || !selected.equals(editor.getText())) {
                filteredItems.setPredicate(item -> {
                  
                    if (item.toUpperCase().startsWith(newValue.toUpperCase())) {
                        return true;
                    } else {
                        return false;
                    }
                });
                if(selected==null) {
                    prim.setValue(null);
                }
            }
        });
    });

Ich hatte das Gefühl, dass dein Problem nicht immer auftritt, aber nach der Änderung noch weniger.
Ansonsten schaue ich später nochmal genauer


#5

Hi,

danke…aber dein Vorschlag verhält sich genau wie meiner.
Interessant ist, dass sich das letzte Zeichen nicht mehr aus dem Editor der Combo rauslöschen lässt.

GGK


#6

Hi Clayn,

das Problem scheint die Selection eines Items zu sein. Wenn ich aus dem Popup ein Item anwähle habe ich mein gechildertes Verhalten…

GGK


#7

Das Problem ist (oder scheint zu sein) : Wenn man die Liste filtert, indem man ein neues Filter-Prädikat setzt, dann wird das interne Selection-Model darüber benachrichtigt, dass “nichts” mehr ausgewählt ist - und DAS (also nichts) wird dann im Editor angezeigt. Blrgmls… da gibt es ziemlich viele Kreuz-Und-Quer-Abhängigkeiten. Wie immer, wenn ein Programm/Library/Computer versucht, einem das Leben leichter zu machen: Es funktioniert fast immer, aber wenn mal nicht, macht es einem das Leben so viel schwerer, dass man sich manchmal von Anfang an die schwerere Variante gewünscht hätte.

Mit dem hier schien es zwar jetzt zu “”“funktionieren”"", aber das ist ein übler Krampf, und DARF einfach nicht die einzige/richtige Lösung sein

    prim.getEditor().textProperty().addListener(new ChangeListener<String>()
    {
        boolean clearing = false;
        boolean forcing = false;

        @Override
        public void changed(ObservableValue<? extends String> observable,
            String oldValue, String newValue)
        {
            TextField editor = prim.getEditor();
            String selected = prim.getSelectionModel().getSelectedItem();

            if (forcing)
            {
                return;
            }
            if (clearing)
            {
                forcing = true;
                prim.getEditor().setText(oldValue);
                forcing = false;
                return;
            }

            Platform.runLater(() -> {

                if (selected == null || !selected.equals(editor.getText()))
                {
                    clearing = true;
                    filteredItems.setPredicate(item -> {
                        return item.toUpperCase().startsWith(
                            newValue.toUpperCase());
                    });
                    clearing = false;
                }
                prim.getEditor().positionCaret(newValue.length());
            });
        }
    });

Hoffentlich postet bald jemand was besseres :worried:


#8

Hab noch ein bisschen mit rumgespielt (keine ordentliche Lösung allerdings bisher).
War zwar kurz davor nur gab es dann andere Problemchen.

Ich hab zwar @Marco13’s Lösung nicht ausprobiert, aber so in etwa löse ich solche Aufgaben meistens auch. Unschön aber naja.

So als Anregung (falls es keine ComboBox sein muss: https://controlsfx.bitbucket.io/org/controlsfx/control/textfield/TextFields.html#bindAutoCompletion-javafx.scene.control.TextField-javafx.util.Callback-)

Edit: Mein neuer Vorschlag

    prim.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
        final TextField editor = prim.getEditor();
        final String selected = prim.getSelectionModel().getSelectedItem();
        Platform.runLater(() -> {
            if (selected == null || !selected.equals(editor.getText())) {
                filteredItems.setPredicate(item -> {
                  
                    if (item.toUpperCase().startsWith(newValue.toUpperCase())) {
                        return true;
                    } else {
                        return false;
                    }
                });
            }
            editor.positionCaret(editor.getText().length());
        });
    });
    prim.valueProperty().addListener(new ChangeListener<String>()
    {
        @Override
        public void changed(
                ObservableValue<? extends String> observable,
                String oldValue, String newValue)
        {
            if(newValue==null) {
                TextField editor=prim.getEditor();
                if(!editor.getText().isEmpty()&&!editor.getText().equals(
                        oldValue)) {
                    prim.setValue(editor.getText());
                }
            }
        }
    });

#9

Wow, das funktioniert wirklich, ich brauch zwar noch etwas um das ganze zu durchblicken…

Vielen Dank

GGK


#10

auch dir vielen Dank…!

GGK


#11

Ohje, ich hab’ jetzt echt schon ein schlechtes Gewissen, dafür, dass sowas evtl. tatsächlich in irgendeiner Codebasis landet. Bitte nicht meinen Namen dazuschreiben :roll_eyes: Im Ernst: Da muss es etwas besseres geben. Ich muss wohl irgendwann mal ähnlich durch JavaFX browsen, wie ich das bei Swing schon Jahrelang gemacht habe (aber Swing scheint in der Hinsicht seeeehr viel zugänglicher zu sein … :confused: )


#12

Spätere Ergebnisse solltest du dann bitte auch im Wiki veröffentlichen. :+1: