2017-07-17 111 views
0

我有一个简单的应用程序,显示已从表格中的组合框中选择的项目。但是,如果在组合框中选择了项目,则会过滤其余项目以包括名称包含在所选项目中的项目。例如,在下面的MCVE中,如果要从组合框中选择“Apple”,控制列表将被过滤为包含“Apple”和“Pineapple”。JavaFX组合框选定的项目在过滤组合框列表后消失

偶尔,组合框将被重置为在应用过滤器后不再显示所选项目。 当您选择在结果筛选列表中没有任何其他项目的项目时,会发生此问题。例如,如果您从组合框中选择“香蕉”或“菠萝”,而不是显示所选项目,则组合框将显示提示文本。

请看下面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升

<?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> 

这似乎是与JavaFX的组合框的错误,但我还没有看到任何一个有类似的问题(也许是因为它是一个选择后过滤相同的组合框?一种罕见的要求)还是我做错事情?

编辑

由于James_D在评论中指出,这个问题是不存在的Java的更新版本(Java的8u131至少)。然而,我现在被迫使用Java 8u25。我关心这个问题的主要原因是因为它允许用户两次选择相同的项目。因此,防止用户复制表中项目的解决方案对我来说是可以接受的。

+0

您似乎在每次选择某个东西时将新过滤器与现有过滤器组合在一起。 (为什么?这不是你描述的要求。)所以,如果你选择“苹果”,然后选择“香蕉”,该列表将被过滤,只包含那些包含文本“苹果”*和*包含文本“香蕉“,导致一个空的列表。 –

+0

对,我在写这个mcve时忽略了。实际应用程序应用一些额外的逻辑来确定何时过滤列表。它只会应用一次过滤器。我用来过滤列表的方法对我而言有点矫枉过正。在尝试使用您建议的前两种方法尝试解决此问题后,我实际上是从您的答案中偷了它。 –

+0

我只是试着运行它,它似乎按预期工作。 (因为在选择“Apple”之后,“Banana”被过滤了,所以你不能真正选择“Apple”,然后选择“Banana”。)我无法重现你描述的问题。 –

回答

0

@James_D帮助我发现我遇到了一个在较新版本的Java中已修复的错误。但由于我目前无法更新我的应用程序版本的Java,我仍然需要找出一种方法来防止用户能够将重复的项目添加到列表中。一旦我以这种方式说明问题,就很容易确定一个体面的工作。

我修改了筛选器以排除筛选列表中的选定项目。

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

组合框仍然不显示所选项目(现在不能,因为它不再列表中),但用户将无法选择重复的项目更多。