【问题标题】:Binding to JavaFX properties of an object that can be switched绑定到可以切换的对象的 JavaFX 属性
【发布时间】:2013-05-11 11:55:47
【问题描述】:

绑定到本身包装在属性中的对象的属性似乎在典型应用程序中做了很多事情,在 JavaFX 中是否有比我在下面做的更好的方法来做到这一点?

需要解释的更多细节:我想在 JavaFX 2.2 中制作 GUI,用于管理多个项目。我创建了一个小示例来测试所有内容,其中项目是人。一组人员以自定义方式显示(不是列表或树,但我认为这不重要),我可以选择一个。

在侧面板中,我可以编辑当前选定的人。更新在一组人员中立即可见,当我选择另一个人时,编辑面板会更新。

JavaFX 的双向绑定似乎非常适合此目的。我目前有这个用于“人员编辑”窗格的 fx:controller:

public class PersonEditor implements ChangeListener<Person> {
    @FXML private TextField nameField;
    @FXML private TextField ageField;
    @FXML private TextField heightField;

    public void setSelection(ObjectProperty<Person> selectedPersonProperty) {
        selectedPersonProperty.addListener(this);
    }

    @Override
    public void changed(ObservableValue<? extends Person> observable, Person oldVal, Person newVal) {
        if (oldVal != null) {
            nameField.textProperty().unbindBidirectional(oldVal.nameProperty());
            ageField.textProperty().unbindBidirectional(oldVal.ageProperty());
            heightField.textProperty().unbindBidirectional(oldVal.heightProperty());
        }
        if (newVal != null) {
            nameField.textProperty().bindBidirectional(newVal.nameProperty());
            ageField.textProperty().bindBidirectional(newVal.ageProperty());
            heightField.textProperty().bindBidirectional(newVal.heightProperty());
        }
    }
}

我想知道是否有更好的方法,也许在 JavaFX 中可以绑定到可以更改的对象的属性?我不喜欢我必须手动取消绑定所有属性的事实,感觉就像重复的代码。 还是像 JavaFx 那样简单?

【问题讨论】:

    标签: javafx-2


    【解决方案1】:

    就我个人而言,我认为您在事件处理程序中编写的代码并没有那么笨拙或笨拙。这是事件处理程序通常在 imo 的 GUI 中所做的那种事情。

    问问你自己,虽然……在你的情况下真的有必要绑定吗?

    如果您必须实时更新您在一个面板中所做的编辑以反映在另一个面板中,那么您可能已经实施了最简单的解决方案。然而,这种 UI 设计存在固有的困难,它可能并不适合所有情况。如果用户需要取消他所做的编辑怎么办?如果他改变主意,你有办法回滚编辑吗?有时,不希望通过编辑进行实时更改,在这种情况下,将数据模型对象绑定到 UI 对象可能不是一个好主意。

    【讨论】:

    • 嗯,是的,我看到您指出并不总是需要绑定。但在这种情况下,我最终会遇到类似的情况:我将通过创建 2 个辅助方法、1 个方法“selectedPersonToFields”和 1 个方法“fieldsToSelectedPerson”来实现它。在这两种方法中,您都将数据从一组字段复制到一组人员属性(或反向)。再一次,这是几乎相同代码的 2 倍!不知何故,我想在这两种情况下避免这种情况。我觉得它可以更优雅。我正在寻找一些现有的 JavaFX 方法来做到这一点,或者一些模式或我可以实现的东西......
    【解决方案2】:

    这似乎不是在 JavaFX 中可以更优雅地完成的事情。绑定和解除绑定似乎是最干净的方式。

    我自己实现了一种方法来做到这一点。不确定我最终是否会使用它(因为它只是用难以阅读的代码替换了代码重复)。但它有效,它是我自己问题的答案,所以我在这里添加了它。

    新的 PersonEditor 类:

    public class PersonEditor implements Initializable {
        private SelectedObjectPropertyBinder<Person> selectedObjectPropertyBinder = 
              new SelectedObjectPropertyBinder<Person>();
    
        @FXML private TextField nameField;
        @FXML private TextField ageField;
        @FXML private TextField heightField;
    
         @Override
        public void initialize(URL url, ResourceBundle rb) {
            selectedObjectPropertyBinder.getBinders().add(
                new ObjectPropertyBindHelper<Person>(nameField.textProperty()) {
                    @Override public Property objectProperty(Person p) 
                    { return p.nameProperty(); }
            });
            selectedObjectPropertyBinder.getBinders().add(
                new ObjectPropertyBindHelper<Person>(ageField.textProperty()) {
                    @Override public Property objectProperty(Person p) 
                    { return p.ageProperty(); }
            });
            selectedObjectPropertyBinder.getBinders().add(
                new ObjectPropertyBindHelper<Person>(heightField.textProperty()) {
                    @Override public Property objectProperty(Person p) 
                    { return p.heightProperty(); }
            });
        }
    
        public void setSelection(ObjectProperty<Person> selectedPersonProperty) {
            selectedObjectPropertyBinder.
                setSelectedObjectProperty(selectedPersonProperty);
        }
    }
    

    助手类:

    public class SelectedObjectPropertyBinder<T> implements ChangeListener<T> {
        private List<ObjectPropertyBindHelper<T>> binders = 
               new ArrayList<ObjectPropertyBindHelper<T>>();
    
        public void setSelectedObjectProperty(Property<T> selectionProperty) {
            selectionProperty.addListener(this);
        }
    
        public List<ObjectPropertyBindHelper<T>> getBinders() {
            return binders;
        }
    
        @Override
        public void changed(ObservableValue<? extends T> observable, 
                            T oldVal, T newVal) {
            if (oldVal != null)
                for (ObjectPropertyBindHelper b : binders)
                    b.unbindBi(oldVal);
            if (newVal != null)
                for (ObjectPropertyBindHelper b : binders)
                    b.bindBi(newVal);
        }
    }
    
    public abstract class ObjectPropertyBindHelper<T> {
        private Property boundProperty;
    
        public ObjectPropertyBindHelper(Property boundProperty) {
            this.boundProperty = boundProperty;
        }
        public void bindBi(T o) {
            boundProperty.bindBidirectional(objectProperty(o));
        }
        public void unbindBi(T o) {
            boundProperty.unbindBidirectional(objectProperty(o));
        }
        public abstract Property objectProperty(T t);
        public Property getBoundProperty() {
            return boundProperty;
        }
    }
    

    正如 scottb 在他的回答中指出的那样,绑定链接并不总是您想要的。如果您希望能够取消/提交更改,您也可以使用类似的方法来实现(但可能会更难阅读生成的代码)。

    【讨论】:

      【解决方案3】:

      很久以前就问过这个问题了:)反正最近我在找解决方案,用这种方式实现——单例Form类实现抽象类:

          public abstract class InputForm {
      
          private ArrayList<BindingPair> bindedProps;
          private ArrayList<ListenerPair> listenerPairs;
      
          public InputForm() {
              bindedProps = new ArrayList();
              listenerPairs = new ArrayList();
          }
      
          public void resetBindings() {
              unbindAll();
              bindedProps = new ArrayList();
          }
      
          public void resetListeners() {
              removeAllListeners();
              listenerPairs = new ArrayList();
          }
      
          private void unbindAll() {
              for (BindingPair pair : bindedProps) {
                  unbind(pair);
              }
          }
      
          private void removeAllListeners() {
              for (ListenerPair listenerPair : listenerPairs) {
                  removeListener(listenerPair);
              }
          }
      
          public void bind(BindingPair bindingPair) {
              BindingUtils.bind(bindingPair);
              bindedProps.add(bindingPair);
          }
      
          public void addListener(ListenerPair listenerPair) {
              ListenerUtils.addListener(listenerPair);
              listenerPairs.add(listenerPair);
          }
      
          private void unbind(BindingPair bindingPair) {
              BindingUtils.unbind(bindingPair);
          }
      
          private void removeListener(ListenerPair listenerPair) {
              ListenerUtils.removeListener(listenerPair);
          }
      }
      
      public class BindingUtils {
      
          public static void bind(BindingPair bindingPair) {
              if (bindingPair.isBidirectionalBinding()) {
                  if (bindingPair.getStringConverter() != null)
                      Bindings.bindBidirectional(bindingPair.propertyToBindProperty(), bindingPair.propertyBindToProperty(), bindingPair.getStringConverter());
                  else
                      bindingPair.propertyToBindProperty().bindBidirectional(bindingPair.propertyBindToProperty());
              } else {
                  bindingPair.propertyToBindProperty().bind(bindingPair.propertyBindToProperty());
              }
          }
      
          public static void unbind(BindingPair bindingPair) {
              if (bindingPair.isBidirectionalBinding()) {
                  bindingPair.propertyToBindProperty().unbindBidirectional(bindingPair.propertyBindToProperty());
              } else {
                  bindingPair.propertyToBindProperty().unbind();
              }
      
          }
      }
      
      public class ListenerUtils {
      
          public static void addListener(ListenerPair listenerPair) {
              if (listenerPair.propertyProperty() != null)
                  listenerPair.propertyProperty().addListener(listenerPair.getListener());
              if (listenerPair.roPropertyProperty() != null)
                  listenerPair.roPropertyProperty().addListener(listenerPair.getListener());
          }
      
          public static void removeListener(ListenerPair listenerPair) {
              if (listenerPair.propertyProperty() != null)
                  listenerPair.propertyProperty().removeListener(listenerPair.getListener());
              if (listenerPair.roPropertyProperty() != null)
                  listenerPair.roPropertyProperty().removeListener(listenerPair.getListener());
          }
      }
      
      
      public class BindingPair {
      
          private Property propertyToBind;
          private Property propertyBindTo;
      
          private boolean bidirectionalBinding;
          private StringConverter stringConverter;
      
          public BindingPair(Property propertyToBind, Property propertyBindTo, boolean bidirectionalBinding, StringConverter stringConverter) {
              this.propertyToBind = propertyToBind;
              this.propertyBindTo = propertyBindTo;
              this.bidirectionalBinding = bidirectionalBinding;
              this.stringConverter = stringConverter;
          }
      
          public Property propertyToBindProperty() {
              return propertyToBind;
          }
      
          public Property propertyBindToProperty() {
              return propertyBindTo;
          }
      
          public boolean isBidirectionalBinding() {
              return bidirectionalBinding;
          }
      
          public StringConverter getStringConverter() {
              return stringConverter;
          }
      }
      
      public class ListenerPair {
      
          private Property property = null;
          private ChangeListener listener;
          private ReadOnlyObjectProperty roProperty = null;
      
          public ListenerPair(Property property, ChangeListener listener) {
              this.property = property;
              this.listener = listener;
          }
      
          public ListenerPair(ChangeListener listener, ReadOnlyObjectProperty roProperty) {
              this.listener = listener;
              this.roProperty = roProperty;
          }
      
          public Property propertyProperty() {
              return property;
          }
      
          public ChangeListener getListener() {
              return listener;
          }
      
          public Object getRoProperty() {
              return roProperty.get();
          }
      
          public ReadOnlyObjectProperty roPropertyProperty() {
              return roProperty;
          }
      }
      
      

      在 Form 类(实现 InputForm)中,每次通过该表单“渲染”新对象时都会调用两个方法:

      private void doBinding() {
      
              resetBindings();
      
              bind(new BindingPair(dataXlsFileUrlProp, getControllerMain().getSceneRenderer().getLoadedSceneInfo().xlsDataFileProperty(), false, null));
      
          }
      
          private void addListeners() {
      
              resetListeners();
      
              addListener(new ListenerPair(dataXlsFileUrlProp, (observable, oldValue, newValue) -> {
                  //do smth
              }));
          }
      

      希望有人能帮忙。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-12
      • 1970-01-01
      • 2013-03-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多