2016-01-02 2 views
0

В основном заголовок вопроса говорит все. У меня есть столбец строк (называемый type) в виде таблицы и соответствующий столбец чисел (называемый size), каждая строка представляет объект CargoItem, который имеет два свойства type и size. Обе столбцы доступны для редактирования. TableView связан с соответствующим наблюдаемым списком CargoItem, который называется cargoList.динамически заполняет ChoiceBox из редактируемой колонки в TableView

Первоначально ChoiceBox установлен в наблюдаемый список. У меня есть слушатель, прикрепленный к ChoiceBox, поэтому он может обновлять TextField с соответствующим атрибутом размера. Это работает нормально: если вы введете новое значение в ячейку в столбце размера, соответствующее значение в TextField будет обновляться (после выбора элемента ChoiceBox).

На данный момент, если я отредактирую ячейку в столбце type, элементы в ChoiceBox не будут обновляться. У меня есть диагностическая кнопка, которая проверяет правильность обновления наблюдаемого списка. Я думаю, мне нужно поставить слушателя в ячейки столбца типа, но не знаю, как это сделать.

Ниже приводится минимальный рабочий пример, с 4-мя файлами: Table_test02.fxml, CargoItem.java, ControllerTest.java и TestApp.java

Table_test02.fxml:

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

<?import javafx.scene.text.*?> 
<?import javafx.scene.control.*?> 
<?import java.lang.*?> 
<?import javafx.scene.layout.*?> 

<AnchorPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="252.0" prefWidth="232.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="so_question01.ControllerTest"> 
    <children> 
     <TableView fx:id="cargoTable" editable="true" layoutX="9.0" layoutY="30.0" prefHeight="107.0" prefWidth="213.0"> 
     <columnResizePolicy> 
      <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /> 
     </columnResizePolicy> 
     <columns> 
      <TableColumn fx:id="cargoTableTypeCol" maxWidth="130.0" minWidth="60.0" prefWidth="75.0" sortable="false" text="Type" /> 
      <TableColumn fx:id="cargoTableSizeCol" maxWidth="118.0" minWidth="60.0" prefWidth="67.0" sortable="false" text="Size" /> 
     </columns> 
     </TableView> 
     <Label layoutX="10.0" layoutY="11.0" text="Cargo"> 
     <font> 
      <Font name="System Bold" size="12.0" /> 
     </font> 
     </Label> 
     <ChoiceBox fx:id="externalChoiceBox" layoutX="9.0" layoutY="162.0" prefHeight="25.0" prefWidth="99.0" /> 
     <TextField fx:id="externalTextField" layoutX="123.0" layoutY="162.0" prefHeight="25.0" prefWidth="99.0" /> 
     <Button fx:id="diagnosticButton" layoutX="83.0" layoutY="210.0" mnemonicParsing="false" text="Diagnostic" textFill="#862828"> 
     <font> 
      <Font name="Arial Black" size="11.0" /> 
     </font> 
     </Button> 
     <Label layoutX="10.0" layoutY="143.0" text="Type"> 
     <font> 
      <Font name="System Bold" size="12.0" /> 
     </font> 
     </Label> 
     <Label layoutX="123.0" layoutY="144.0" text="Size"> 
     <font> 
      <Font name="System Bold" size="12.0" /> 
     </font> 
     </Label> 
    </children> 
</AnchorPane> 

CargoItem.java:

package so_question01; 

import javafx.beans.property.*; 

public class CargoItem { 

    private final StringProperty type; 
    private final FloatProperty size; 

    public CargoItem() { 
     this.type = new SimpleStringProperty(""); 
     this.size = new SimpleFloatProperty(0f); 
    } 

    public CargoItem(String type, float size) { 
     this.type = new SimpleStringProperty(type); 
     this.size = new SimpleFloatProperty(size); 
    } 

    public void setType(String type) { 
     this.type.set(type); 
    } 

    public void setSize(float size) { 
     this.size.set(size); 
    } 

    public String getType() { 
     if (!(type.get().equals("") || type.get() == null)) { 
     return type.get(); 
     } 
     return ""; 
    } 

    public float getSize() { 
     return size.get(); 
    } 

    public StringProperty typeProperty() { 
     return type; 
    } 

    public FloatProperty sizeProperty() { 
     return size; 
    } 

    public void clear() { 
     this.type.set(""); 
     this.size.set(0); 
    } 

    @Override 
    public String toString() { 
     return type.get();  
    } 

    public String display() { 
     return "\nCARGO ITEM:" 
       + "\n\ttype: " + type.get() 
       + "\n\tsize: " + size.get(); 
    } 
} 

ControllerTest.java:

package so_question01; 

import java.text.DecimalFormat; 
import javafx.collections.ListChangeListener.*; 
import javafx.collections.ObservableList; 
import javafx.collections.FXCollections; 
import javafx.collections.*; 
import javafx.fxml.FXML; 
import javafx.scene.control.Button; 
import javafx.scene.control.ChoiceBox; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.control.TextField; 
import javafx.scene.control.cell.*; 
import javafx.scene.control.cell.PropertyValueFactory; 
import javafx.beans.value.ObservableValue; 
import javafx.util.StringConverter; 
import javafx.beans.value.*; 

public class ControllerTest { 

    ObservableList<CargoItem> cargoList = FXCollections.observableArrayList(); 
    private TestApp mainApp; // Reference to the main application. 

    @FXML private ChoiceBox externalChoiceBox; 
    @FXML private TextField externalTextField; 
    @FXML private Button diagnosticButton; 
    @FXML private TableView<CargoItem> cargoTable; 
    @FXML private TableColumn<CargoItem, Float> cargoTableSizeCol; 
    @FXML private TableColumn<CargoItem, String> cargoTableTypeCol; 

    public ControllerTest() { } 

    @FXML 
    private void initialize() { 

     //*****DIAGNOSTIC ONLY*******************// 
     diagnosticButton.setOnAction(e->{ 
     for(CargoItem c: cargoList){ 
      System.out.print(c.display()); 
     } 
     }); 
     //does not work when changes made via table editing, but may be useful when new list loaded from file? 
     cargoList.addListener(new ListChangeListener< CargoItem>(){ 
     public void onChanged(Change<? extends CargoItem> c){ 
      // Do your changes here 
      System.out.println(c.getList()); 
     }}); 

     //**** test choicebox***// 
     StringConverter cargoItemConverter= new CargoItemStringConverter(); 
     externalChoiceBox.setConverter(cargoItemConverter); 

     //***************** CARGO TABLE **************// 
     cargoTable.setEditable(true); 
     // Initialize the cargo table with the 2 columns. 
     cargoTableTypeCol.setCellFactory(TextFieldTableCell.forTableColumn()); 
     cargoTableTypeCol.setCellValueFactory(new PropertyValueFactory<>("type")); 

     cargoTableSizeCol.setCellFactory(TextFieldTableCell.forTableColumn(new FloatIntegerStringConverter())); 
     cargoTableSizeCol.setCellValueFactory(new PropertyValueFactory<>("size")); 
    } 

    /** 
    * Is called by the main application to give a reference back to itself. 
    * 
    * @param mainApp 
    */ 
    public void setMainApp(TestApp mainApp) { 
     this.mainApp = mainApp; 
     cargoList.setAll(mainApp.getCargoData()); 

     // Add observable list data to the table 
     cargoTable.setItems(cargoList); 
     cargoTable.getSelectionModel().setCellSelectionEnabled(true); 

     externalChoiceBox.setItems(cargoList); 
     //adds listener to choicebox selection, so size is rendered to text field 
     externalChoiceBox.getSelectionModel().selectedItemProperty().addListener(
     new ChangeListener<CargoItem>() { 
      @Override public void changed(ObservableValue<? extends CargoItem> observableValue, CargoItem oldChoice, CargoItem newChoice) { 
       externalTextField.setText(new FloatIntegerStringConverter().toString(newChoice.getSize())); 
      } 
     }); 
    } 
} 

//*******string converter classes, nothing to see here************// 
class CargoItemStringConverter extends StringConverter<CargoItem>{ 
    @Override 
    public String toString(CargoItem c){ 
     return c.toString();   
    } 
    @Override 
    public CargoItem fromString(String type){ 
     return null; 
    } 
} 

class FloatIntegerStringConverter extends StringConverter<Float> 
{ 
    DecimalFormat decimalFormat = new DecimalFormat("##,###,##0"); 
    private Float returnVal; 

    @Override 
    public Float fromString(String value) {  
     if (value == null) { 
     return null; 
     } 
     value = value.trim(); 
     String digits = value.replaceAll("[^0-9.]", ""); //gets rid of non-numerics 

     if (digits.length() < 1) { 
     return null; 
     } 
     try{ //avoids exception by converting invalid strings to zero (which should just be redundant due to regexp above) 
     returnVal= Float.valueOf(Math.round(Float.valueOf(digits)));//have to do this so the float value coresponds to the rounded value in table 
     } catch(NumberFormatException e) { returnVal= Float.valueOf(0); } 
     return returnVal;    
    } 

    @Override 
    public String toString(Float value) { 
     if (value == null) { // If the specified value is null, return a zero-length String 
     return ""; 
     } 
     return decimalFormat.format(value)+" MT"; //truncated integer representation, unit metric tonnes 
    } 
} 

TestApp.java:

package so_question01; 

import java.io.*; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.layout.*; 
import javafx.collections.ObservableList; 
import javafx.collections.FXCollections; 
import javafx.application.Application; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 

public class TestApp extends Application { 

    private Stage primaryStage; 
    private ObservableList<CargoItem> cargoData = FXCollections.observableArrayList(); 

    public TestApp() { 
     // create some sample data 
     CargoItem cargoInstance0 = new CargoItem("Toxic waste", 5000f); 
     CargoItem cargoInstance1 = new CargoItem("Cement", 10000f); 
     CargoItem cargoInstance2 = new CargoItem("Wheat", 20000f); 
     cargoData.add(cargoInstance0); 
     cargoData.add(cargoInstance1); 
     cargoData.add(cargoInstance2); 
    } 

    /** 
    * Returns the data as observable lists of objects. 
    * @return 
    */ 
    public ObservableList<CargoItem> getCargoData() {return cargoData; } //called in the controller 

    @Override 
    public void start(Stage primaryStage) { 
     this.primaryStage = primaryStage; 
     this.primaryStage.setTitle("**** TEST ****"); 
     show(); 
    } 

     /** 
    * Initializes the root layout. 
    */ 
    public void show() { 
     try { 
      // Load root layout from fxml file. 
      FXMLLoader loader = new FXMLLoader(); 
      loader.setLocation(TestApp.class.getResource("Table_test02.fxml")); 
      AnchorPane root = (AnchorPane) loader.load(); 

      // Give the controller access to the main app. 
      ControllerTest controller = loader.getController(); 
      controller.setMainApp(this); 

      // Show the scene containing the root layout. 
      Scene scene = new Scene(root); 
      primaryStage.setScene(scene); 
      primaryStage.show(); 
     } catch (IOException e) { System.out.println(e);} 
    } 

    /** 
    * Returns the main stage. 
    * @return 
    */ 
    public Stage getPrimaryStage() { 
     return primaryStage; 
    } 

    public static void main(String[] args) { 
     Application.launch(TestApp.class, (java.lang.String[])null); 
    } 
} 

ответ

1

Предполагая, что я понимаю вопрос правильно, вы должны инициализировать cargoList с extractor:

ObservableList<CargoItem> cargoList = FXCollections.observableArrayList(cargo -> 
    new Observable[]{cargo.typeProperty(), cargo.sizeProperty()}); 

ChoiceBox имеет историю плохой работы с уведомлениями об обновлениях в своем базовом списке; Я не уверен, что все исправлено в последней версии. Возможно, вам стоит рассмотреть возможность переключения на ComboBox.

Update

Если вам нужно использовать ChoiceBox, обходной путь может быть, чтобы не установить элементы непосредственно cargoList, но обновлять элементы всякий раз, когда cargoList изменения.Вы можете сделать это, сделав следующее изменение в setMainApp(...):

// externalChoiceBox.setItems(cargoList); 
    externalChoiceBox.getItems().setAll(cargoList); 

и добавить следующее в вашем списке изменений слушателя:

cargoList.addListener(new ListChangeListener< CargoItem>(){ 
    public void onChanged(Change<? extends CargoItem> c){ 
     // Do your changes here 
     System.out.println(c.getList()); 
     externalChoiceBox.getItems().setAll(cargoList); 
    }}); 
+0

Спасибо Джеймс, но я думаю, что я достиг предела моего понимания - компилятор говорит, что нет класса Observable, и я не могу найти нужную библиотеку. Есть что-то еще, что мне нужно сделать? – Tel

+0

Просто добавьте импорт: 'import javafx.beans.Observable;' –

+0

OK спасибо! Это почти работает, но есть странная вещь: если я изменяю элемент в столбце типа, старый элемент все еще находится в списке ChoiceBox ... т.е. если я изменил «Cement» на «Lollies», ChoiceBoxupdates на _ » «Токсичные отходы», «Лолли», «Цемент», «Пшеница» _, но наблюдаемый список верен (Цемент заменен Лоллис) - любые идеи? – Tel

Смежные вопросы