9

I want to observe changes made in some field of some java bean class.
This field is enum type.
To bind the field and corresponding ui control I am going to use PropertyChangeListener as described here
But it seems that javaFX does not have an equvalent of enum property type. Something like javafx.beans.property.EnumProperty
I tried to use StringProperty and ObjectProperty instead but it does not work.

UPDATE: Added an sscce

Observable.java

package sample;

    import java.beans.PropertyChangeListener;
    import java.beans.PropertyChangeSupport;

    public class Observable {

        protected PropertyChangeSupport propertyChangeSupport = null;

        public Observable() {}

        public void setObservableObject (Observable observable) {
            propertyChangeSupport = new PropertyChangeSupport(observable);
        }

        public void addPropertyChangeListener(PropertyChangeListener listener){
            System.out.println("added PropertyChangeListener");
            propertyChangeSupport.addPropertyChangeListener(listener);
        }
    }

Item.java

package sample;


    public class Item extends Observable {

        public enum State {
            INIT, STARTED
        }

        private String name;
        private State state = State.INIT;

        public Item() {
            super();
            super.setObservableObject(this);
        }

        public Item(String name, State state) {
            this();
            setName(name);
            setState(state);
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public State getState() {
            return state;
        }

        public void setState(State state) {
            State oldState = this.state;
            this.state = state;
            System.out.println(String.format("%s: change state from %s to %s",name,oldState.name(),state));
            propertyChangeSupport.firePropertyChange("state", oldState, state);
        }


    }

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("Item View");
            primaryStage.setScene(new Scene(root, 200, 100));
            primaryStage.show();
        }


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

sample.fxml

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

    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.CheckBox?>
    <?import javafx.scene.control.ComboBox?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.AnchorPane?>

    <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="108.0" prefWidth="225.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
             <children>
                <Label layoutX="14.0" layoutY="14.0" text="show item" />
                <ComboBox fx:id="cbxItemsSelector" layoutX="14.0" layoutY="31.0" prefWidth="150.0" />
                <Button fx:id="btnChangeState" layoutX="14.0" layoutY="65.0" mnemonicParsing="false" onAction="#changeItemState" prefHeight="25.0" prefWidth="85.0" text="change state" />
             </children>
    </AnchorPane>

Controller.java

package sample;

    import javafx.beans.Observable;
    import javafx.beans.property.adapter.JavaBeanObjectPropertyBuilder;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.fxml.FXML;
    import javafx.scene.control.ComboBox;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.util.Callback;


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

    public class Controller {

        @FXML
        private ComboBox<Item> cbxItemsSelector;

        private ObservableList<Item> items;

        public void initialize() {
            loadItems();
            customizeSelectorCellView();
        }

        private void loadItems() {
            List<Item> itemsList = new ArrayList<>();
            itemsList.add(new Item("first item", Item.State.INIT));
            itemsList.add(new Item("second item", Item.State.INIT));

            items = FXCollections.observableList(itemsList, item -> {
                try {
                    return new Observable[]{
                            new JavaBeanObjectPropertyBuilder().bean(item).name("state").build()
                    };
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                    return new Observable[]{};
                }
            });

            cbxItemsSelector.setItems(items);
        }

        private void customizeSelectorCellView() {


            cbxItemsSelector.setButtonCell(new ListCell<Item>() {
                @Override
                protected void updateItem(Item item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty) {
                        setText("");
                    } else {
                        setText(item.getName());
                    }
                }
            });
            cbxItemsSelector.setCellFactory(
                    new Callback<ListView<Item>, ListCell<Item>>() {
                        @Override
                        public ListCell<Item> call(ListView<Item> p) {
                            ListCell cell = new ListCell<Item>() {
                                @Override
                                protected void updateItem(Item item, boolean empty) {
                                    super.updateItem(item, empty);
                                    if (empty) {
                                        setText("");
                                    } else {
                                        System.out.println(String.format("update %s",item.getName()));
                                        setText(String.format("name: %s\n state: %s\n", item.getName(), item.getState().name()));
                                    }
                                }
                            };
                            return cell;
                        }
                    }
            );
        }

        @FXML
        public void changeItemState() {
            Item selectedItem = cbxItemsSelector.getSelectionModel().getSelectedItem();
            if (selectedItem == null) return;

            selectedItem.setState(Item.State.STARTED);

        }

    }

So, when I run it now I am getting next output:

first item: change state from INIT to INIT
second item: change state from INIT to INIT
added PropertyChangeListener
added PropertyChangeListener
update first item
update second item
update first item
update second item
update first item
update first item
update second item
first item: change state from INIT to STARTED
second item: change state from INIT to STARTED
Process finished with exit code 0

As you can see update item is not called after item's state has been changed.
I use: jre 1.8.0_91-b15 x64 included in jdk

Community
  • 1
  • 1
rvit34
  • 1,967
  • 3
  • 17
  • 33
  • 9
    Object property will work. – James_D Oct 29 '16 at 19:29
  • Ohh. Yeah. I caught the problem. I used control with [FxControls Validation](http://fxexperience.com/controlsfx/features/#decorationvalidation). By some reason it won't work if a validation is enabled – rvit34 Oct 29 '16 at 22:55
  • @James_D Hmm, actually Validation is not a reason of the issue. I cut it down and see the same issue anyway. But something strange happens. Yesterday this example worked and I saw updated state. Today it does not work. I didn't change anything. I've just added a sample. Look at it, please. Can you reproduce the problem? – rvit34 Oct 30 '16 at 14:45
  • I'm baffled by the use of PropertyChangeSupport in a JavaFX context. Unless you're trying to bridge Observables in both a Swing and a JavaFX environment. Why not just compose Item of regular JavaFX properties? If the reason for PropertyChangeSupport was just because the OP didn't know about ObjectProperty, then the whole sample is about 100X more complicated than it needs to be. – DaveB Jul 11 '22 at 18:59

1 Answers1

0

@rvit34 - either rename sample.Observable (i renamed to MyObservable), explicitly import sample.Observable; in sample.Item, or change the line:

public class Item extends Observable {

to:

public class Item extends sample.Observable {

and it should resolve your problems. Below are:

1) the output produced from the modified sample code

2) all changes made to original source files (for brevity I've only included the changes not the whole sample code)

3) an explanation of what was causing the problem and a technique to avoid future issues of a similar nature.

My output:

run:
first item: change state from INIT to INIT...
...
first item: change state from INIT to STARTED
update first item
second item: change state from INIT to STARTED
update second item
first item: change state from STARTED to INIT
update first item
second item: change state from STARTED to INIT
update second item
BUILD SUCCESSFUL (total time: 32 seconds)

MyObservable.java (renamed from Observable.java)

public class MyObservable {
...
}

Item.java

public class Item extends MyObservable {
    ...
    public MyObservable() {}

    public void setObservableObject (MyObservable observable) {
        ...
    }
}

Controller.java (not needed to make it work. Modified to cycle through states, instead of just setting to STARTED)

public void changeItemState() {
        Item selectedItem = cbxItemsSelector.getSelectionModel().getSelectedItem();
        if (selectedItem != null) {

            switch (selectedItem.getState()) {
                case STARTED:
                    selectedItem.setState(Item.State.INIT);
                    break;
                case INIT:
                    selectedItem.setState(Item.State.STARTED);
                    break;
                default:
                    break;
            }
        }

    }

The problem (as well as the chance occurrence of proper behavior) comes from a namespace collision.

sample.Item descends from sample.Observable. Because both classes are members of package sample, you neither have to import sample.Observable nor explicitly state that you mean sample.Observable when you state Item extends Observable, which normally wouldn't be an issue. Because, you use javafx.beans.Observable in Main, it seems to confuse the JRE (which it actually shouldn't, and this might be a bug in the JVM you're discovering). The random successful run is likely due to system load or some other random environment variable or condition causing the jvm to either hang on loading javafx.beans.Observable till after registering Item as an object of type sample.Observable, or sample.Observable had already been loaded into memory at launch.

EDIT - the above doesn't actually answer the question asked in the post title, however it does resolve the issue the discussed in the body of the post. To answer the title question, a SetProperty<E> looks to be rather well suited to be used as an enum property. Haven't tried to implement this in the provided sample code, probably won't, but I'll re-edit with a working example from the project I'm working on currently that got me looking for an answer for the title question.