从TableView中的可编辑列动态填充ChoiceBox [英] dynamically populate ChoiceBox from editable column in TableView

查看:79
本文介绍了从TableView中的可编辑列动态填充ChoiceBox的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基本上,问题标题说明了一切.我在表视图中有一列字符串(称为type)和一个对应的数字列(称为size),每一行代表一个对象CargoItem,该对象具有两个属性typesize. 两列都是可编辑的. TableView与CargoItem的相应可观察列表相关联,称为cargoList.

Basically the question title says it all. I have a column of strings (called type) in a tableview and a corresponding column of numbers (called size), each row representing an object CargoItem, which has the two properties type and size. Both columns are editable. The TableView is associated with the corresponding observable list of CargoItem, called cargoList.

ChoiceBox最初设置为可观察列表.我在ChoiceBox上附加了一个侦听器,因此它可以使用相应的size属性更新TextField.这样就可以了:如果您在size列的单元格中键入一个新值,则TextField中的相应值将更新(一旦选择ChoiceBox项).

A ChoiceBox is initially set to the observable list. I have a listener attached to the ChoiceBox, so it can update a TextField with the corresponding size attribute. This works OK: if you type a new value into a cell in the size column, the corresponding value in the TextField will update (once the ChoiceBox item is selected).

目前,如果我在type列中编辑单元格,则ChoiceBox中的项目将不会更新.我有一个诊断按钮,它将验证可观察列表是否正确更新.我想我需要在类型列的单元格中放置一个侦听器,但不确定如何.

At the moment if I edit a cell in the type column, the items in the ChoiceBox won't update. I have a diagnostic button which will verify that the observable list is updating properly. I think I need to put a listener into the cells of the type column, but not sure how.

以下是一个最小的工作示例,其中包含4个文件: Table_test02.fxml,CargoItem.java,ControllerTest.java TestApp.java

Following is a minimal working example, with 4 files: Table_test02.fxml, CargoItem.java, ControllerTest.java, and 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);
    }   
}

推荐答案

假设我正确理解了这个问题,则需要使用

Assuming I understand the question correctly, you need to initialize your cargoList with an extractor:

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

ChoiceBox的历史记录无法很好地处理其基础列表的更新通知;我不确定最新版本是否已修复所有问题.您可能需要考虑改为使用ComboBox.

ChoiceBox has a history of not working well with update notifications to its underlying list; I'm not sure if everything is fixed in the latest version. You might want to consider switching to a ComboBox instead.

更新

如果您需要使用ChoiceBox,则解决方法可能不是将项目直接设置为cargoList,而是在cargoList更改时更新项目.您可以通过在setMainApp(...)中进行以下更改来做到这一点:

If you need to use a ChoiceBox, a workaround might be not to set the items directly to cargoList, but to update the items whenever cargoList changes. You can do this by making the following change in setMainApp(...):

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

并在列表更改侦听器中添加以下内容:

and adding the following in your list change listener:

  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);
     }});

这篇关于从TableView中的可编辑列动态填充ChoiceBox的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆