2017-01-23 1 views
0

updateItem(T item,boolean empty)을 덮어 쓰면 ListView이 작동하지만 이제는 내 도메인 객체 데이터와 양방향으로 연결된 노드를 ListView에 표시해야합니다. 편집 모드를 요청하는 기존 시스템을 사용하면 작업하기가 실제로 어렵습니다 (예 : 편집 모드로 들어가면 TextField에서 포커스가 제거되므로 TextField을 선택하여 포커스를 입력 할 수 없으므로 셀 또는 그래픽에서 로직을 정의해야합니다). 바인딩을 사용하여이 작업을 수행하려고하지만 바인딩을 추가하고 제거해야하며 그래픽을 표시할지 여부를 알아야합니다.JavaFX ListView 셀 생성자에서 그래픽과 itemProperty 간의 셀 양방향 바인딩

다음 코드는 내가 원하는 것을 더 잘 이해할 수는 있지만 분명히 좋지 않거나 작동하지 않습니다. 나는 몇 가지 방법으로 원하는 결과를 얻으려고 노력했지만 유일한 좋은 결과는 내 도메인과 논리와 제대로 연결된다는 것입니다. 나쁜 소식은 목록을 추가, 제거 또는 스크롤 할 때마다 예기치 않은 결과가 발생하지만 올바르게 작동하지 않는다는 것입니다.

MyCell extends ListCell<MyModel> { 
    private SomethingThatExtendsNode view = new SomethingThatExtendsNode(); 

    public MyCell(){ 
     BooleanBinding remove = itemProperty().isNull()or(emptyProperty()); 
     remove.addListener((observable, oldValue, newValue) -> {setGraphic(null)}); 
     BooleanBinding exists = itemProperty().isNotNull().and(emptyProperty().not); 
     exists.addListener((observable, oldValue, newValue) -> { 
      if (newValue){ 
       setGraphic(view); 
       MonadicObservableValue<MyModel> model = EasyBind.monadic(itemProperty()); 
       view.getTextField1.textProperty().bindBidirectional(model.selectProperty(MyModel::text1)); 
       //etc.. 
      } 
     }); 
    } 
} 

이 디자인은 올바른 방법인가요? 내 견해가 도메인에 바인딩 된 속성을 갖길 바란다. 비즈니스 논리는 대부분 바인딩을 통해 도메인에 있습니다.

편집 : 답변에서와 같이 설정을 시도했지만 여전히 이상하게 작동하지만 문제가 다른 곳에서 발생해야합니다. MCVE의 힘내 여기 https://github.com/PopescuStefanRadu/JavaFX-listView-MCVE

또한이 오류는 텍스트 필드에있는 모든 이제 다음 제공하는 입력을 팝업 것으로 보인다

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException 
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361) 
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81) 
    at javafx.scene.control.TextInputControl$TextProperty.fireValueChangedEvent(TextInputControl.java:1389) 
    at javafx.scene.control.TextInputControl$TextProperty.markInvalid(TextInputControl.java:1393) 
    at javafx.scene.control.TextInputControl$TextProperty.controlContentHasChanged(TextInputControl.java:1332) 
    at javafx.scene.control.TextInputControl$TextProperty.access$1600(TextInputControl.java:1300) 
    at javafx.scene.control.TextInputControl.lambda$new$162(TextInputControl.java:139) 
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137) 
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81) 
    at javafx.scene.control.TextField$TextFieldContent.insert(TextField.java:87) 
    at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:1204) 
    at javafx.scene.control.TextInputControl.updateContent(TextInputControl.java:556) 
    at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:548) 
    at com.sun.javafx.scene.control.skin.TextFieldSkin.replaceText(TextFieldSkin.java:576) 
    at com.sun.javafx.scene.control.behavior.TextFieldBehavior.replaceText(TextFieldBehavior.java:202) 
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.defaultKeyTyped(TextInputControlBehavior.java:238) 
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:139) 
    at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218) 
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127) 
    at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135) 
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218) 
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191) 
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74) 
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54) 
    at javafx.event.Event.fireEvent(Event.java:198) 
    at javafx.scene.Scene$KeyHandler.process(Scene.java:3964) 
    at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910) 
    at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040) 
    at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:248) 
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247) 
    at com.sun.glass.ui.View.handleKeyEvent(View.java:546) 
    at com.sun.glass.ui.View.notifyKey(View.java:966) 
    at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method) 
    at com.sun.glass.ui.gtk.GtkApplication.lambda$null$49(GtkApplication.java:139) 
    at java.lang.Thread.run(Thread.java:745) 

답변

0

문제가 해결되었습니다.

ListView 그래픽이 기본 데이터를 반영하도록 설계되었으므로 그래픽을 모델에 바인딩 할 필요가 없습니다. 모델이 변경 될 때마다 UI가 업데이트됩니다.

내 모델 데이터의 변경으로 인해 ObservableList이 변경 사항을 인식했는지 확인해야했습니다. 이를 위해 나는 추출기 사용 :

ObservableList<Model> myModels = FXCollections.observableArrayList(model -> new Observable[]{model.nameProperty()}); 

다음으로는 UI의 변화뿐만 아니라 모델의 변화의 원인이 있는지 확인했다을, 그래서 cellFactory 내부 ChangeListener을 사용했다. ListCell을 확장하는 클래스의 생성자에 배치 할 수도 있습니다. 바인딩을 사용하려고했지만 작동하지 않는 것 같습니다. 왜 그런지 나중에 살펴볼 것입니다. 모든 속성에 대해 청취자보다 덜 자세한 내용을 사용하고자하지만 그럼에도 불구하고 작동합니다.

@Override 
public void start(Stage primaryStage) throws Exception { 
    ObservableList<Model> myModels = FXCollections.observableArrayList(model -> new Observable[]{model.nameProperty()}); 

    ListView<Model> modelListView = new ListView<>(); 
    modelListView.setCellFactory(c -> { 
     MyListCell cell = new MyListCell(); 
     //TODO is there a way to replace this listener with something less verbose? 
     cell.getTextField().textProperty().addListener((observable, oldValue, newValue) -> { 
      if (newValue!=null){ 
       cell.getItem().setName(newValue); 
      } 
     }); 

     return cell; 
    }); 
    modelListView.setItems(myModels); 
    myModels.add(new Model()); 

    Button addButton = new Button("Add"); 
    addButton.setOnAction(event -> myModels.add(new Model())); 

    StackPane root = new StackPane(); 
    root.getChildren().addAll(new HBox(20, modelListView, addButton)); 
    primaryStage.setTitle("ListView with bidirectional binding"); 
    primaryStage.setScene(new Scene(root, 600, 500)); 
    primaryStage.show(); 
} 

private class Model { 
    private StringProperty name = new SimpleStringProperty(this, "name", ""); 
    private ReadOnlyStringWrapper computedName = new ReadOnlyStringWrapper(this, "computedName"); 

    public Model() { 
     computedName.bind(Bindings.createStringBinding(() -> name.get().toUpperCase(), name)); 
    } 

    public String getName() { 
     return name.get(); 
    } 

    public StringProperty nameProperty() { 
     return name; 
    } 

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

    public String getComputedName() { 
     return computedName.get(); 
    } 

    public ReadOnlyStringWrapper computedNameProperty() { 
     return computedName; 
    } 
} 

private class MyListCell extends ListCell<Model> { 
    private HBox content = new HBox(10); 
    private TextField textField = new TextField(); 
    private Label label = new Label(); 

    public MyListCell() { 
     content.getChildren().addAll(textField, label); 
    } 

    @Override 
    protected void updateItem(Model item, boolean empty) { 
     super.updateItem(item, empty); 
     if (item == null || empty) { 
      setGraphic(null); 
     } else { 
      setGraphic(content); 
      textField.textProperty().set(item.getName()); 
      label.textProperty().set(item.getComputedName()); 
     } 
    } 

    public TextField getTextField() { 
     return textField; 
    } 

} 
0
목록 셀은 한 번 생성하기 때문에 당신은, 세포 생성자의 모든 바인딩 또는 리스너를 추가해서는 안

새 항목을 표시해야 할 때마다 목록보기에서 다시 사용됩니다. 생성자에서 속성을 바인딩하면 목록보기에서 변경할 때 이전 항목에서 속성을 제거 할 수 없습니다. 그래서 스크롤하거나 목록을 변경할 때 이상한 행동을 보입니다.

사용자 지정 목록 셀에서 항목을 업데이트하기 전에 양방향 바인딩과 수신기를 제거해야합니다. 다음 코드를 확인하십시오 :

@Override 
public void start(Stage primaryStage) { 
    ObservableList<Model> myModels = FXCollections.observableArrayList(
      IntStream.range(0, 100).mapToObj(i -> new Model("MyModel " + i)).collect(Collectors.toList()) 
    ); 

    ListView<Model> modelListView = new ListView<>(); 
    modelListView.setCellFactory(c -> new MyListCell()); 
    modelListView.setItems(myModels); 

    StackPane root = new StackPane(); 
    root.getChildren().addAll(modelListView); 

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

    primaryStage.setTitle("Custom list cell"); 
    primaryStage.setScene(scene); 
    primaryStage.show(); 
} 

private class MyListCell extends ListCell<Model> { 

    private HBox content = new HBox(); 
    private TextField textField = new TextField(); 
    private Button actionButton = new Button("Action"); 

    private final ChangeListener<String> textListener = (ObservableValue<? extends String> observable, String oldValue, String newValue) -> { 
     checkIfDisableButton(newValue); 
    }; 

    public MyListCell() { 
     content.getChildren().addAll(textField, actionButton); 
     content.setSpacing(10); 
     setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 
     setGraphic(content); 
    } 

    @Override 
    protected void updateItem(Model item, boolean empty) { 
     if (getItem() != null) { // get old item 
      //remove all bidirectional bindings and listeners 
      textField.textProperty().unbindBidirectional(getItem().nameProperty()); 
      getItem().nameProperty().removeListener(textListener); 
     } 
     super.updateItem(item, empty); 
     //new item 
     if (item == null || empty) { 
      setText(null); 
      setGraphic(null); 
     } else { 
      setGraphic(content); 
      checkIfDisableButton(item.getName()); 
      item.nameProperty().addListener(textListener); 
      textField.textProperty().bindBidirectional(item.nameProperty()); 
      actionButton.setOnAction(e -> { 
       System.out.println(item.getName()); 
      }); 
     } 
    } 

    private void checkIfDisableButton(String value) { 
     actionButton.setDisable("MyModel 2".equals(value)); 
    } 
} 

private class Model { 

    StringProperty name = new SimpleStringProperty(); 

    public Model(String name) { 
     this.name.set(name); 
    } 

    public String getName() { 
     return name.get(); 
    } 

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

    public StringProperty nameProperty() { 
     return this.name; 
    } 

} 
+0

이 내 설정에 작동하지 않습니다

여기에 코드입니다. https : // github.com/PopescuStefanRadu/JavaFX-listView-MCVE 여러 항목을 추가하면 이전에 값을 가진 일부 항목이 null로 설정되는 것 같습니다 –

+0

해결책을 찾았습니다. 검토 해 주실 수 있습니까? 설정이 작동하지 않는 이유를 잘 모릅니다. ListCells와 ListView는 다소 나에게 블랙 박스로, 나는 렌더링 흐름이 어떻게 작동하는지 모른다. 양방향 바인딩은 모델에 심각한 영향을 미칩니다. 어떤 이유로 설치 프로그램을 실행할 때 여전히 null로 설정하고있었습니다. –

관련 문제