繁体   English   中英

组合框选择和模型绑定

[英]Combobox selection and Model binding

问题概述:

我有两个模型OrderModelOrderStatusModel OrderStatusModel项放入组合框,该组合框包含供用户选择的用于更新Order表中名为orderStatusId的外键字段的orderStatusId 初始化数据时,我希望能够从OrderModel并检索当前的orderStatusId ,然后设置组合框(保存所有用户选择)以匹配位于orderStatusId中的OrderModel (该OrderModel的FK位于Order表)。 做出新选择时,组合框需要能够更新OrderModel 从数据库获取数据以填充OrderModel ,我提取了FK orderStatusId 从这一点出发,我将使用case语句在OrderStatusModel选择适当的项,并在初始化时选择选项。 我知道这不是最好的方法,因为如果要在后端数据库中为新的状态类型添加新行(例如:订单已取消),那么我需要去更新所有的switch语句以允许那改变。

范例程式码

Controller

package test.pkg2;

import java.awt.Label;
import java.beans.Statement;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;

public class Test2Controller implements Initializable{

    @FXML
    private Label customerName;

    @FXML
    private Label orderNumber;

    @FXML
    private Label orderStatusId;

    @FXML
    private ComboBox<OrderStatusModel> orderStatusCmb;

    private ObservableList<OrderStatusModel> orderStatusModel;
    private ObservableList<OrderModel> orderModel;

    int statusId;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        this.orderStatusCmb.getSelectionModel().selectedItemProperty().addListener((obs, newValue, oldValue) ->{
            if(newValue != null && newValue != oldValue){
                this.orderStatusCmb.getSelectionModel().getSelectedItem().getOrderStatusId();
                //TODO UPDATE Order table with new orderStatusId from above code ^^^^^^
            }
        });
        populateOrderStatusCombobox();
        getCurrentOrder();
    }

    private void populateOrderStatusCombobox(){
        String SQL = "SELECT Status.statusId, Status.statusName \n" 
                     +"FROM Status;";

        orderStatusModel = FXCollections.observableArrayList();

        //Status table:
        // +-----------+-------------+------+-----+---------+
        // | Field     | Type        | Null | Key | Default |
        // +-----------+-------------+------+-----+---------+
        // | statusId  | int         | YES  | PK  | NULL    |       
        // | statusName| varchar(20) | YES  |     | NULL    |       
        // +-----------+-------------+------+-----+---------+

        //example data: statusId = 1 statusName = Order Taken
        //example data: statusId = 2 statusName = Order Processing
        //example data: statusId = 3 statusName = Shipped

        //Above items are populated into the combobox for selection
        try(Connection connection = DBConnection.getConnection()){
            PreparedStatement statement = connection.prepareStatement(SQL);
            ResultSet resultSet = statement.executeQuery();
            while(resultSet.next()){
                this.orderStatusModel.add(new OrderStatusModel(resultSet.getInt("statusId"),
                                                    resultSet.getString("statusName")));
            }
        } catch(SQLException e){

        }
        this.orderStatusCmb.setItems(orderStatusModel);
    }
    private void getCurrentOrder(){
        String SQL = "SELECT Order.orderNumber, Order.customerName, Order.orderStatusId \n"
                    +"FROM Order"
                    +"WHERE orderNumber = 123;";
        orderModel = FXCollections.observableArrayList();

        //Order table:
        // +--------------+-------------+------+-----+---------+
        // | Field        | Type        | Null | Key | Default |
        // +--------------+-------------+------+-----+---------+
        // | orderNumber  | int         | YES  | PK  | NULL    |       
        // | customerName | varchar(20) | YES  |     | NULL    |       
        // | orderStatusId| int         | YES  | FK  | NULL    |  
        // +--------------+-------------+------+-----+---------+

        //example data: orderNumber = 123
        //              customerName = SomeCompany
        //              orderStatusId = 1

        //I usually set the combobox here using the below method:
        try(Connection connection = DBConnection.getConnection()){
            PreparedStatement statement = connection.prepareStatement(SQL);
            ResultSet resultSet = statement.executeQuery();
            while(resultSet.next()){
                this.orderModel.add(new OrderModel(resultSet.getInt("orderNumber"),
                                                    resultSet.getString("customerName"),
                                                    resultSet.getInt("orderStatusId")));
                                                    statusId = resultSet.getInt("orderStatusId");
            }

            //HERE is where I initially set the orderStatus to match the orderModel
            //I am guessing I need some kind of binding here?
           switch(statusId){
               case 1: this.orderStatusCmb.getSelectionModel().select(0);
               break;
               case 2: this.orderStatusCmb.getSelectionModel().select(1);
               break;
               case 3: this.orderStatusCmb.getSelectionModel().select(3);
               break;
           }
        } catch(SQLException e){

        }
    }
}

OrderStatusModel

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;


public class OrderStatusModel {
    private final IntegerProperty orderStatusId;
    private final StringProperty orderStatusName;

    public OrderStatusModel(int orderStatusId, String orderStatusName){
        this.orderStatusId = new SimpleIntegerProperty(orderStatusId);
        this.orderStatusName = new SimpleStringProperty(orderStatusName);
    }

    public IntegerProperty getOrderStatusId() {
        return orderStatusId;
    }

    public StringProperty getOrderStatusName() {
        return orderStatusName;
    }
}

OrderModel

package test.pkg2;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class OrderModel {

    private final IntegerProperty orderNumber;
    private final StringProperty customerName;
    private final IntegerProperty orderStatusId;

    public OrderModel(int orderNumber, String customerName, int orderStatusId){
        this.orderNumber = new SimpleIntegerProperty(orderNumber);
        this.customerName = new SimpleStringProperty(customerName);
        this.orderStatusId = new SimpleIntegerProperty(orderStatusId);
    }

    public IntegerProperty getOrderNumber() {
        return orderNumber;
    }

    public StringProperty getCustomerName() {
        return customerName;
    }

    public IntegerProperty getOrderStatusId() {
        return orderStatusId;
    }
}

您无需使用任何显式绑定即可在选项和ID之间实现链接。 相反,通过已经包含ID和友好字符串名称的OrderStatusModel对象支持组合框,然后将适当的StringConverter应用于组合框将可以很好地显示组合框。 id->友好名称链接仍保留在模型类中,可以根据需要在以后的数据库更新中反向使用该链接。

样品

该示例所做的是几件事:

  • 将数据库交互提取到接口。
  • 提供数据库接口的内存中实现示例(将其替换为执行实际数据库访问代码的实现)。
  • 在一个单独的类中定义数据库访问权限,该类将其逻辑移出UI代码,以便您可以更轻松地独立开发,测试和交换其实现逻辑(即使对于小型应用程序,这也是非常可取的) 。
  • 在代码而不是FXML中定义UI布局(最好使用FXML,但在代码中创建UI布局只是为了使此示例保持代码相对简短)。
  • 将模型类上访问器的命名约定更新为xxxProperty()而不是您正在使用的getXXX()。 根据标准JavaFX命名约定,任何返回属性的内容都应具有Property的名称后缀。 getXXX()方法用于检索属性的实际值,而不是属性本身。
  • 在OrderModel中,订单状态记录为ObjectProperty,这使OrderModel易于使用,因为其中包含所有相关状态信息,而无需联接和查询来获取其他信息。 我知道在您的数据库系统中并没有这样表示,但是让数据库接口类进行转换并返回所有相关信息通常是解决这种情况的更好方法。
  • 在使用ComboBox时,请使用valueProperty而不是selectedItemProperty,因为valueProperty是ComboBox值的更直接表示(在这种情况下,用户始终从选择下拉列表中选择并且不能文本编辑值部分,但从概念上讲,只使用值会更直接一些)。
  • 为组合框定义了一个StringConverter,它将把OrderStatusModel值转换为它们友好的字符串名称,以便在组合框中显示。
  • 为OrderStatusModel定义equals和hashcode,以便可以使用适当的匹配和重复逻辑(否则,它们可能不会发挥您的预期作用)进行正确比较,并使用Java collections API将其插入到集合中。

样本输出

样本输出

ComboApp.java

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

import java.util.Optional;

public class ComboApp extends Application {
    private Label orderNumber = new Label();
    private Label customerName = new Label();
    private ComboBox<OrderStatusModel> orderStatus = new ComboBox<>();

    private Database database = new InMemoryTestDatabase();

    @Override
    public void start(Stage stage) throws Exception {
        HBox layout = new HBox(
                10, 
                orderNumber, 
                customerName, 
                orderStatus
        );
        layout.setPadding(new Insets(10));
        layout.setAlignment(Pos.BASELINE_LEFT);

        populateOrderStatusCombobox();
        showOrder(123);
        updateOrderWhenStatusChanged();

        stage.setScene(new Scene(layout));
        stage.show();
    }

    private void updateOrderWhenStatusChanged() {
        orderStatus.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.equals(oldValue)) {
                database.updateOrder(
                        new OrderModel(
                                Integer.parseInt(orderNumber.getText()),
                                customerName.getText(),
                                orderStatus.getValue()
                        )
                );

                System.out.println(
                        "Updated order status of order "
                                + orderNumber.getText()
                                + " to "
                                + orderStatus.getValue()
                                    .orderStatusNameProperty()
                                    .getValue()
                );
            }
        });
    }

    private void populateOrderStatusCombobox(){
        orderStatus.setPlaceholder(new Label("None Selected"));
        orderStatus.getItems().setAll(database.listAllOrderStatuses());

        orderStatus.setConverter(new StringConverter<OrderStatusModel>() {
            @Override
            public String toString(OrderStatusModel orderStatusModel) {
                return orderStatusModel.orderStatusNameProperty().get();
            }

            @Override
            public OrderStatusModel fromString(String orderStatusName) {
                Optional<OrderStatusModel> result = database.findOrderStatusWithName(
                        orderStatusName
                );

                return result.orElse(null);
            }
        });
    }

    private void showOrder(int orderId){
        Optional<OrderModel> result = database.findOrderById(orderId);

        if (!result.isPresent()) {
            orderNumber.setText("");
            customerName.setText("");
            orderStatus.setValue(null);

            return;
        }

        OrderModel order = result.get();

        orderNumber.setText("" + order.orderNumberProperty().get());
        customerName.setText(order.customerNameProperty().get());
        orderStatus.setValue(order.orderStatusProperty().get());
    }

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

Database.java

import java.util.List;
import java.util.Optional;

public interface Database {
    List<OrderStatusModel> listAllOrderStatuses();
    Optional<OrderStatusModel> findOrderStatusWithName(String orderStatusName);
    Optional<OrderStatusModel> findOrderStatusById(int orderStatusId);
    Optional<OrderModel> findOrderById(int orderId);
    void updateOrder(OrderModel order);
}

InMemoryTestDatabase.java

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class InMemoryTestDatabase implements Database {
    List<OrderStatusModel> orderStatuses = new ArrayList<>();
    List<OrderModel> orders = new ArrayList<>();

    public InMemoryTestDatabase() {
        // populate with dummy sample data.
        orderStatuses.add(new OrderStatusModel(1, "Order Taken"));
        orderStatuses.add(new OrderStatusModel(2, "Order Processing"));
        orderStatuses.add(new OrderStatusModel(3, "Shipped"));

        orders.add(
                new OrderModel(
                        123,
                        "XYZZY",
                        findOrderStatusById(1)
                                .orElse(null)
                )
        );
    }

    public List<OrderStatusModel> listAllOrderStatuses() {
        return orderStatuses;
    }

    @Override
    public Optional<OrderStatusModel> findOrderStatusWithName(String orderStatusName) {
        return orderStatuses.stream()
                .filter(orderStatus -> orderStatusName.equals(orderStatus.orderStatusNameProperty().get()))
                .findFirst();
    }

    @Override
    public Optional<OrderStatusModel> findOrderStatusById(int orderStatusId) {
        return orderStatuses.stream()
                .filter(orderStatus -> orderStatusId == orderStatus.orderStatusIdProperty().get())
                .findFirst();
    }

    public Optional<OrderModel> findOrderById(int orderId) {
        return orders.stream()
                .filter(order -> orderId == order.orderNumberProperty().get())
                .findFirst();
    }

    public void updateOrder(OrderModel order) {
        for (int i = 0; i < orders.size(); i++) {
            if (orders.get(i).orderNumberProperty().get() == order.orderNumberProperty().get()) {
                orders.set(i, order);
                break;
            }
        }
    }
}

OrderModel.java

import javafx.beans.property.*;

public class OrderModel {

    private final IntegerProperty orderNumber;
    private final StringProperty customerName;
    private final ObjectProperty<OrderStatusModel> orderStatus;

    public OrderModel(int orderNumber, String customerName, OrderStatusModel orderStatus){
        this.orderNumber = new SimpleIntegerProperty(orderNumber);
        this.customerName = new SimpleStringProperty(customerName);
        this.orderStatus = new SimpleObjectProperty<>(orderStatus);
    }

    public IntegerProperty orderNumberProperty() {
        return orderNumber;
    }

    public StringProperty customerNameProperty() {
        return customerName;
    }

    public ObjectProperty<OrderStatusModel> orderStatusProperty() {
        return orderStatus;
    }
}

OrderStatusModel.java

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import java.util.Objects;

public class OrderStatusModel {
    private final IntegerProperty orderStatusId;
    private final StringProperty orderStatusName;

    public OrderStatusModel(int orderStatusId, String orderStatusName){
        this.orderStatusId = new SimpleIntegerProperty(orderStatusId);
        this.orderStatusName = new SimpleStringProperty(orderStatusName);
    }

    public IntegerProperty orderStatusIdProperty() {
        return orderStatusId;
    }

    public StringProperty orderStatusNameProperty() {
        return orderStatusName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        OrderStatusModel that = (OrderStatusModel) o;
        return Objects.equals(orderStatusId.get(), that.orderStatusId.get()) &&
                Objects.equals(orderStatusName.get(), that.orderStatusName.get());
    }

    @Override
    public int hashCode() {
        return Objects.hash(orderStatusId.get(), orderStatusName.get());
    }
}

背景

该答案大致基于JavaFX论坛帖子中的类似问答,该问答与ChoiceBoxes而不是ComboBoxes有关:

但是,此答案已更新为可直接与ComboBoxes而不是ChoiceBoxes一起使用。

不相关的建议

有关您发布的代码的一些无关的建议:

  • 切勿在未抛出异常,对其采取措施或将其记录下来之前捕获异常。 即使只是在互联网上发布的代码,也不要这样做。
  • 导入java.awt.Label ,然后尝试使用FXML注入:这是不对的,您应该使用javafx.scene.control.Label 不要混合awt代码和JavaFX代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM