简体   繁体   English

调用setValue()时设置ChoiceBox的显示值

[英]Setting displayed value of ChoiceBox when calling setValue()

I want to store objects in a ChoiceBox to mirror a lookup table with (id, name) fields in order to be able to easily retrieve the id when later saving the record while the choice displays the text. 我想将对象存储在ChoiceBox中,以镜像具有(ID,名称)字段的查找表,以便以后在选择显示文本时保存记录时能够轻松检索ID。 For example, if I have a ChoiceBox with Category objects, when the user selects a category in the the dropdown, the text will display the name of that Category -- the display and selection will match. 例如,如果我有一个带有Category对象的ChoiceBox,则当用户在下拉列表中选择一个类别时,文本将显示该Category的名称-显示和选择将匹配。 Eventually, when the model saves the data, it will pull out the id field of the Category object as a parameter in the SQL query. 最终,当模型保存数据时,它将拉出Category对象的id字段作为SQL查询中的参数。

My code at the bottom of this post shows my attempts to understand what is going on. 这篇文章底部的代码显示了我对正在发生的事情的尝试。

The challenge is that the text portion of the Choice is not necessarily updated when the value changes, either with an explicit setValue() call or a an indirect one when I bind the Choice to an ObjectProperty. 挑战在于,当值更改时,不一定要更新Choice的文本部分,当我将Choice绑定到ObjectProperty时,要么使用显式setValue()调用,要么使用间接调用。

I believe I understand why. 我相信我明白原因。 If I call setValue() with a Category object identical to one in the ObservableList of the Choice (the same object, not another instance with the same field values), the text portion displays the correct value. 如果我使用与Choice的ObservableList中的一个类别对象相同的Category对象(相同的对象,而不是具有相同字段值的另一个实例)调用setValue(),则文本部分显示正确的值。 However, if I call setValue() with another instance, it doesn't work, even if the field values are the same. 但是,如果我用另一个实例调用setValue(),即使字段值相同,它也不起作用。 I did an Override on the equals() of the class I'm using, but that didn't help. 我对正在使用的类的equals()进行了覆盖,但这无济于事。 Is there another method ChoiceBox uses to compare objects? ChoiceBox是否使用另一种方法比较对象?

Do you have any suggestions to work around this issue? 您是否有解决此问题的建议? I want to avoid coding other listeners, or search functions every time I implement this use case. 我想避免在每次实现此用例时编写其他侦听器或搜索功能。

Maybe a simple way would be to use the id from the database to get one of the objects in the pick list used to build the combo and set that as the value of my model's property. 也许一种简单的方法是使用数据库中的id来获取用于构建组合的选择列表中的对象之一,并将其​​设置为模型属性的值。 For instance, if the id is 1, my model's category field would be set to = picklist[1] (a Category object). 例如,如果id为1,则我模型的类别字段将设置为= picklist [1](一个类别对象)。 Since the choice's ObservableList would be initialized with the same picklist, the objects would be identical and the problem would be solved. 由于选择的ObservableList将使用相同的选择列表进行初始化,因此对象将完全相同,从而可以解决问题。 (As in the third scenario in my code.) (与我的代码中的第三种情况一样。)

/*
 * If I set the value of a ChoiceBox to exact copy of an object
 * in the ObservableList, the text portion is updated accordingly
 *
 * If is set the value with another instance of the same class
 * with the same values, but not the same exact object,
 * the text portion is not updated. 
 * 
 */
package javafxapplication1;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class JavaFXApplication1 extends Application {

    class TestClass {
        // public to simplify demo - normally private

        public int id;
        public String name;

        public TestClass(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public boolean equals(TestClass other) {
            // not called ...
            System.out.println("TestClass.equals() was called");
            return this.id == other.id && this.name.equals(other.name); 
        }

        @Override
        public String toString() {
            return "[id = " + id + "  name = " + name + "]";
        }
    }

    @Override
    public void start(Stage primaryStage) {

        ChoiceBox<String> cbS = new ChoiceBox<>();
        ObservableList<String> valuesS = FXCollections.observableArrayList();
        valuesS.addAll("A", "B", "C");
        cbS.setItems(valuesS);

        // a text converter would make the choice display only text
        // no the toString() of the class

        /*
        * SET INITIAL VALUE BEFORE SHOWING VIEW 
        * no issues with String choice
         */
        cbS.setValue("A");
        System.out.println("Initial String value: " + cbS.getSelectionModel().getSelectedItem());

        Button btn = new Button();
        btn.setText("Change String Value");
        btn.setOnAction((ActionEvent event) -> {
            cbS.setValue("C");
            System.out.println("setValue(\"C\") was called");
            System.out.println("New String value is " + cbS.getSelectionModel().getSelectedItem());
        });

        ChoiceBox<TestClass> cbO = new ChoiceBox<>();
        ObservableList<TestClass> valuesO = FXCollections.observableArrayList();
        TestClass specificInstance = new TestClass(3, "Exact instance");
        valuesO.addAll(new TestClass(1, "Aba"), new TestClass(2, "Ubu"), specificInstance);
        cbO.setItems(valuesO);
        cbO.setValue(new TestClass(1, "Aba"));
        System.out.println("Initial Object value: " + cbO.getSelectionModel().getSelectedItem());


        Button btnO = new Button();
        btnO.setText("Change Object Value");
        /*
        * SET VALUE TO OTHER INSTANCE WITH SAME FIELD VALUES
        */
        btnO.setOnAction((ActionEvent event) -> {
            cbO.setValue(new TestClass(2, "Ubu"));
            System.out.println("setValue(same-values-object)");
            System.out.println("New Object value is " + cbO.getSelectionModel().getSelectedItem());
        });

        /*
        * SET VALUE TO ONE OF THE OBJECTS IN THE CHOICE
        */
        Button btnO2 = new Button();
        btnO2.setText("Change Object Value");
        btnO2.setOnAction((ActionEvent event) -> {
            cbO.setValue(specificInstance);
            System.out.println("setValue(exact-instance-of-object) was called");
            System.out.println("New Object value is " + cbO.getSelectionModel().getSelectedItem());
        });

        VBox root = new VBox();
        root.getChildren().addAll(cbS, btn, cbO, btnO, btnO2);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Test setValue()");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

The problem is simply that you haven't overridden the equals(...) method, which has a signature 问题很简单,您没有覆盖具有签名的equals(...)方法

public boolean equals(Object other) 

By defining it with a different signature 通过使用其他签名定义它

public boolean equals(TestClass other)

you have overloaded it. 你已经超载了。 (It's good practice to always use the @Override annotation when you intend to override a method. The compiler will generate an error if you are not actually overriding the method, so you can see the issue immediately.) The machinery that checks whether the value you pass to setValue(...) is in the backing list (probably via a call to List.contains(...) ) will use the equals(...) method defined in Object if you haven't overridden it. (一种很好的做法是,当您打算覆盖某个方法时,始终使用@Override批注。如​​果您实际上并未覆盖该方法,则编译器将生成错误,因此您可以立即看到问题。)检查该值是否正确的机制。您传递给后备列表中的setValue(...) (可能通过调用List.contains(...) )将使用Object定义的equals(...)方法equals(...)如果尚未覆盖)。

If you actually override equals(...) : 如果您实际上覆盖equals(...)

@Override
public boolean equals(Object other) {
    System.out.println("TestClass.equals() was called");

    if (this == other) return true ;

    // cue endless arguments about whether to use instanceof or getClass()...
    if (! (other instanceof TestClass)) return false ;

    TestClass tc = (TestClass) other ;
    return this.id == tc.id && Objects.equals(this.name, tc.name); 
}

then this will work as you want it to. 然后它将按您的意愿工作。

Note also that you should always override hashCode() when you override equals(...) : 还请注意,当您覆盖equals(...)时,应始终覆盖hashCode() equals(...)

@Override
public int hashCode() {
    return Objects.hash(id, name);
}

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

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