2017-07-17 1 views
0

Ich habe eine einfache Anwendung, die ein Element anzeigt, das aus einer Combobox in einer Tabelle ausgewählt wurde. Wenn das Element jedoch in der Combobox ausgewählt wird, werden die verbleibenden Elemente gefiltert, um Elemente einzuschließen, deren Namen in dem ausgewählten Element enthalten sind. Wenn Sie beispielsweise im folgenden MCVE "Apple" aus der Combobox auswählen würden, würde die Kontrollliste so gefiltert, dass sie "Apple" und "Pineapple" enthält.JavaFX Combobox ausgewähltes Element verschwindet nach dem Filtern der Combobox-Liste

Gelegentlich wird die Combobox zurückgesetzt, um das ausgewählte Objekt nicht mehr anzuzeigen, nachdem der Filter angewendet wurde. Das Problem tritt auf, wenn Sie ein Element auswählen, das keine anderen Elemente in der resultierenden gefilterten Liste enthält. Wenn Sie z. B. "Banana" oder "Pineapple" aus der Combobox auswählen, zeigt das Kombinationsfeld anstelle des ausgewählten Elements den Eingabeaufforderungstext an.

Bitte beachten Sie die folgenden MCVE

Main.java

package sample; 

import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 

public class Main extends Application { 

@Override 
public void start(Stage primaryStage) throws Exception{ 
    Parent root = FXMLLoader.load(getClass().getResource("sample.fxml")); 
    primaryStage.setTitle("ComboBox Issues"); 
    primaryStage.setScene(new Scene(root, 300, 275)); 
    primaryStage.show(); 
} 


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

Controller.java

package sample; 

import javafx.application.Platform; 
import javafx.beans.binding.Bindings; 
import javafx.beans.property.ReadOnlyStringWrapper; 
import javafx.beans.value.ObservableValue; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.collections.transformation.FilteredList; 
import javafx.event.ActionEvent; 
import javafx.fxml.FXML; 
import javafx.scene.control.ComboBox; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.util.StringConverter; 

import java.util.function.Predicate; 

public class Controller { 

    @FXML 
    private TableView<Fruit> fruityTable; 

    @FXML 
    private ComboBox<Fruit> fruitSelector; 

    private ObservableList<Fruit> selectedFruits; 

    private ObservableList<Predicate<Fruit>> filterCriteria; 

    private Predicate<Fruit> fruitFilter; 

    @FXML 
    private TableColumn<Fruit, String> fruitNameColumn; 

    @FXML 
    private TableColumn<Fruit, String> fruitColorColumn; 

    @FXML 
    void addSelectedFruit(ActionEvent event) { 
     if (fruitSelector.getValue() != null) { 
      Fruit selectedFruit = getSelectedFruitFromComboBox(); 
      final String name = selectedFruit.getName().toLowerCase(); 
      fruitFilter = selectableFruits -> selectableFruits.getName().toLowerCase().contains(name); 
      Platform.runLater(() -> filterCriteria.add(fruitFilter)); 
      this.selectedFruits.add(selectedFruit); 
      event.consume(); 
     } 
    } 

    private Fruit getSelectedFruitFromComboBox() { 
     return fruitSelector.getValue(); 
    } 

    @FXML 
    void initialize() { 
     Fruit apple = new Fruit("Apple", "Red"); 
     Fruit pineapple = new Fruit("Pineapple", "Brown"); 
     Fruit banana = new Fruit("Banana", "Yellow"); 
     ObservableList<Fruit> fruitSelectorItems = FXCollections.observableArrayList(); 
     fruitSelectorItems.addAll(apple, pineapple, banana); 
     initializeFruitSelector(fruitSelectorItems); 
     initializeFruitTable(); 
    } 

    private void initializeFruitSelector(ObservableList<Fruit> fruitSelectorItems) { 
     FilteredList<Fruit> filteredFruit = new FilteredList<>(fruitSelectorItems, x -> true); 
     fruitSelector.setItems(filteredFruit); 
     filterCriteria = FXCollections.observableArrayList(); 
     filteredFruit.predicateProperty().bind(Bindings.createObjectBinding(() -> 
      filterCriteria.stream().reduce(x-> true, Predicate::and), filterCriteria)); 
     fruitSelector.setConverter(createFruitChooserConverter()); 
    } 

    private StringConverter<Fruit> createFruitChooserConverter() { 
     return new StringConverter<Fruit>() { 
      @Override 
      public String toString(Fruit item) { 
       if (item == null) { 
        return null; 
       } else { 
        return item.getName(); 
       } 
      } 

      @Override 
      public Fruit fromString(String string) { 
       return null; 
      } 
     }; 
    } 

    private void initializeFruitTable() { 
     selectedFruits = FXCollections.observableArrayList(); 
     fruitNameColumn.setCellValueFactory(cellData -> formatFruitNameColumnText(cellData.getValue())); 
     fruitColorColumn.setCellValueFactory(cellData -> formatFruitColorColumnText(cellData.getValue())); 
     fruityTable.setItems(selectedFruits); 
    } 

    private ObservableValue<String> formatFruitColorColumnText(Fruit fruit) { 
     ReadOnlyStringWrapper color; 
     if (fruit == null) { 
      color = null; 
     } else { 
      color = new ReadOnlyStringWrapper(fruit.getColor()); 
     } 
     return color; 
    } 

    private ObservableValue<String> formatFruitNameColumnText(Fruit fruit) { 
     ReadOnlyStringWrapper name; 
     if (fruit == null) { 
      name = null; 
     } else { 
      name = new ReadOnlyStringWrapper(fruit.getName()); 
     } 
     return name; 
    } 

} 

Fruit.java

package sample; 

public class Fruit { 
    private String name; 
    private String color; 

    Fruit(String name, String color){ 
     this.name = name; 
     this.color = color; 
    } 

    public String getColor() { 
     return color; 
    } 

    public String getName() { 
     return name; 
    } 

} 

sample.fxm l

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

<?import javafx.scene.control.ComboBox?> 
<?import javafx.scene.control.TableColumn?> 
<?import javafx.scene.control.TableView?> 
<?import javafx.scene.layout.ColumnConstraints?> 
<?import javafx.scene.layout.GridPane?> 
<?import javafx.scene.layout.RowConstraints?> 

<GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller"> 
    <children> 
     <TableView fx:id="fruityTable" prefHeight="200.0" prefWidth="201.0" GridPane.columnIndex="1"> 
     <columns> 
      <TableColumn fx:id="fruitNameColumn" prefWidth="75.0" text="Name" /> 
      <TableColumn fx:id="fruitColorColumn" prefWidth="75.0" text="Color" /> 
     </columns> 
     </TableView> 
     <ComboBox fx:id="fruitSelector" onAction="#addSelectedFruit" prefWidth="150.0" promptText="Choose a fruit" /> 
    </children> 
    <columnConstraints> 
     <ColumnConstraints minWidth="10.0" prefWidth="100.0" /> 
     <ColumnConstraints /> 
    </columnConstraints> 
    <rowConstraints> 
     <RowConstraints /> 
    </rowConstraints> 
</GridPane> 

Dies scheint ein Fehler mit dem JavaFX Combobox zu sein, aber ich habe niemanden mit einem ähnlichen Problem gesehen (vielleicht, weil es ist eine ungewöhnliche Anforderung die gleiche Combobox nach einer Auswahl zu filtern?) oder bin ich etwas falsch machen?

bearbeiten

Wie James_D in den Kommentaren stellt fest, dieses Problem in neuerer Version von Java (Java 8u131 zumindest) nicht vorhanden ist. Ich muss jedoch jetzt Java 8u25 verwenden. Der Hauptgrund, warum ich mich mit diesem Problem befasse, ist, weil es dem Benutzer erlaubt, denselben Gegenstand zweimal auszuwählen. Eine Lösung, die verhindert, dass der Benutzer Elemente in der Tabelle dupliziert, wäre für mich akzeptabel.

+0

Sie scheinen den neuen Filter jedes Mal, wenn etwas ausgewählt wird, mit dem vorhandenen Filter zu kombinieren. (Warum? Das ist nicht die Anforderung, die Sie beschreiben.) Wenn Sie also "Apple" auswählen und dann "Banana" auswählen, wird die Liste so gefiltert, dass nur die Elemente mit dem Text "Apple" * und * der Text "Banana" enthalten sind ", was zu einer leeren Liste führt. –

+0

Richtig, das habe ich beim Schreiben dieses MCVE übersehen. Die tatsächliche Anwendung wendet eine zusätzliche Logik an, um zu bestimmen, wann die Liste gefiltert werden soll. Es wird nur einmal einen Filter anwenden.Die Methode, die ich verwende, um die Liste zu filtern, ist für meine Zwecke etwas übertrieben. Ich habe es aus einer Ihrer Antworten gestohlen, nachdem Sie die ersten beiden Methoden, die Sie vorgeschlagen haben, ausprobiert haben, um dieses Problem zu beheben. –

+0

Ich habe gerade versucht, das zu laufen und es scheint zu funktionieren wie erwartet. (Sie können nicht wirklich "Apple" und dann "Banana" wie ich im vorherigen Kommentar beschrieben, weil nach der Auswahl von "Apple", "Banana" gefiltert wird.) Ich kann das Problem nicht beschreiben, das Sie beschreiben. –

Antwort

0

@James_D half mir herauszufinden, dass ich einen Fehler hatte, der in neueren Versionen von Java behoben wurde. Aber da ich momentan nicht in der Lage bin, meine Java-Anwendungen zu aktualisieren, musste ich noch eine Möglichkeit finden, zu verhindern, dass der Benutzer doppelte Einträge zur Liste hinzufügen kann. Sobald ich das Problem auf diese Weise formulierte, war es leicht, eine anständige Arbeit zu bestimmen.

Ich habe den Filter geändert, um das ausgewählte Element in der gefilterten Liste auszuschließen.

fruitFilter = selectableFruits -> selectableFruits.getName().toLowerCase().contains(name) && !selectableFruits.getName().toLowerCase().equals(name); 

Die Combobox noch nicht angezeigt das ausgewählte Element (es jetzt nicht kann, weil sie in der Liste nicht mehr sind), aber der Benutzer nicht in der Lage sein, mehr ein Duplikat Element auszuwählen.

Verwandte Themen