2017-07-17 108 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); 

組合框仍然不顯示所選項目(現在不能,因爲它不再列表中),但用戶將無法選擇重複的項目更多。