2016-10-18 3 views
1

내 분할 창을 사용하는 프로젝트에 대해 사용자 지정 Master-Detail 창을 만들었습니다. 각 창에는 두 개의 Anchor Panes가 있습니다. 하나에는 Users (ObservableList)로 채워진 TableView가 있습니다. 각 행 (User)에서 행이 선택되면 ChangeListener table.getSelectionModel().selectedItemProperty().addListener(listElementChangeListener()); 을 구현하고, 내 DetailPane에 UserObject를 전달하고 TextFields에 사용자 데이터를 자세히 시각화합니다. 사용자가 세부 사항에서 수정 중인지 이해하기 위해 컨트롤을 구현했으며, 그렇다면 TableView에서 행 변경을 방지하고 싶습니다. 사용자를 수정할 때 TableView에서 ChangeListener를 제거하려고했지만 dosent가 잘 작동합니다. 포커스를 설정하고 내가 취소하거나 사용자가 수정 될 때까지 행에 올려 놓는 것과 같은 솔루션을 생각하고 있습니다.JavaFX 사용자 지정 MasterDetail 창

좋은 해결책이 있습니까?

도움 주셔서 감사합니다.

+0

데이터를 수정하는 동안 선택 사항의 변경을 막기 위해 테이블을 비활성화하는 방법은 무엇입니까? 예를 들어 변경 사항을 재설정하거나 저장 (예 : 버튼 사용)하면 표를 다시 사용할 수 있습니다. 내 관점에서 볼 때 목표와 그 시나리오에서 정상적으로 활용되는 최상의 접근 방법입니다. – SSchuette

+1

@ SSchuette의 제안은 좋은 것입니다. 다소 다른 사용자 경험을 제공하는 두 번째 옵션은, 수정 사항이 대상에 즉시 반영되도록 (테이블에), 사용자 개체에 텍스트 필드와 속성 간의 양방향 바인딩을 사용하는 것입니다. 그런 다음 원래 값으로 되돌릴 세부보기의 "취소"단추를 추가 할 수 있습니다. 이 두 솔루션 중 하나는 구현하기가 쉽습니다. –

+0

@James_D 전 완전히 당신과 함께합니다! 자바 FX의 맥락에서 나의 경험은 불행히도, 대부분의 사람들이 이동하는 것을/자바 FX에 스윙에서 이동, 예를 들어 같은 개념 때문에 MVVM (.NET/WPF 영역에서 왔는데 ;-)) 알 수 없거나 잘 이해되지 않았습니다. – SSchuette

답변

3

아마도 조금 다르게 접근 할 것입니다. "detail view"컨트롤을 User 개체의 속성에 양방향으로 바인딩합니다. 그렇게하면 사용자가 개체를 편집 할 때 개체 (및 테이블)에서 업데이트됩니다. 원하는 경우 '취소'버튼을 사용하여 이전 값으로 되돌릴 수도 있습니다.

User.java :

package usermasterdetail; 

import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.SimpleBooleanProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 

public class User { 

    private final StringProperty firstName = new SimpleStringProperty(); 
    private final StringProperty lastName = new SimpleStringProperty(); 
    private final BooleanProperty admin = new SimpleBooleanProperty(); 

    public User(String firstName, String lastName, boolean admin) { 
     setFirstName(firstName); 
     setLastName(lastName); 
     setAdmin(admin); 
    } 

    public final StringProperty firstNameProperty() { 
     return this.firstName; 
    } 


    public final String getFirstName() { 
     return this.firstNameProperty().get(); 
    } 


    public final void setFirstName(final String firstName) { 
     this.firstNameProperty().set(firstName); 
    } 


    public final StringProperty lastNameProperty() { 
     return this.lastName; 
    } 


    public final String getLastName() { 
     return this.lastNameProperty().get(); 
    } 


    public final void setLastName(final String lastName) { 
     this.lastNameProperty().set(lastName); 
    } 


    public final BooleanProperty adminProperty() { 
     return this.admin; 
    } 


    public final boolean isAdmin() { 
     return this.adminProperty().get(); 
    } 


    public final void setAdmin(final boolean admin) { 
     this.adminProperty().set(admin); 
    } 

} 

DataModel.java :

package usermasterdetail; 

import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.SimpleObjectProperty; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 

public class DataModel { 

    private final ObservableList<User> userList = FXCollections.observableArrayList(
      new User("Jacob", "Smith", false), 
      new User("Isabella", "Johnson", true), 
      new User("Ethan", "Williams", false), 
      new User("Emma", "Jones", true), 
      new User("Michael", "Brown", true) 
    ); 

    private final ObjectProperty<User> currentUser = new SimpleObjectProperty<>(); 

    public final ObjectProperty<User> currentUserProperty() { 
     return this.currentUser; 
    } 


    public final User getCurrentUser() { 
     return this.currentUserProperty().get(); 
    } 


    public final void setCurrentUser(final User currentUser) { 
     this.currentUserProperty().set(currentUser); 
    } 


    public ObservableList<User> getUserList() { 
     return userList; 
    } 

} 

TableController.java :

package usermasterdetail; 

import javafx.fxml.FXML; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.control.cell.CheckBoxTableCell; 

public class TableController { 

    @FXML 
    private TableView<User> table ; 
    @FXML 
    private TableColumn<User, String> firstNameColumn ; 
    @FXML 
    private TableColumn<User, String> lastNameColumn ; 
    @FXML 
    private TableColumn<User, Boolean> adminColumn ; 

    private DataModel model ; 

    public void initialize() { 
     firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty()); 
     lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty()); 
     adminColumn.setCellValueFactory(cellData -> cellData.getValue().adminProperty()); 
     adminColumn.setCellFactory(CheckBoxTableCell.forTableColumn(adminColumn)); 
    } 

    public void setDataModel(DataModel dataModel) { 
     if (model != null) { 
      model.currentUserProperty().unbind(); 
     } 
     this.model = dataModel ; 
     dataModel.currentUserProperty().bind(table.getSelectionModel().selectedItemProperty()); 
     table.setItems(model.getUserList()); 
    } 
} 

다음은이 방법을 사용하는 완벽한 솔루션입니다 UserEditorContro ller.java :

package usermasterdetail; 

import javafx.beans.value.ChangeListener; 
import javafx.fxml.FXML; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.TextField; 

public class UserEditorController { 

    @FXML 
    private TextField firstNameField ; 
    @FXML 
    private TextField lastNameField ; 
    @FXML 
    private CheckBox adminCheckBox ; 

    private String cachedFirstName ; 
    private String cachedLastName ; 
    private boolean cachedAdmin ; 

    private ChangeListener<User> userListener = (obs, oldUser, newUser) -> { 
     if (oldUser != null) { 
      firstNameField.textProperty().unbindBidirectional(oldUser.firstNameProperty()); 
      lastNameField.textProperty().unbindBidirectional(oldUser.lastNameProperty()); 
      adminCheckBox.selectedProperty().unbindBidirectional(oldUser.adminProperty()); 
     } 

     if (newUser == null) { 
      firstNameField.clear(); 
      lastNameField.clear(); 
      adminCheckBox.setSelected(false); 
     } else { 
      firstNameField.textProperty().bindBidirectional(newUser.firstNameProperty()); 
      lastNameField.textProperty().bindBidirectional(newUser.lastNameProperty()); 
      adminCheckBox.selectedProperty().bindBidirectional(newUser.adminProperty()); 

      cachedFirstName = newUser.getFirstName(); 
      cachedLastName = newUser.getLastName(); 
      cachedAdmin = newUser.isAdmin(); 
     } 
    }; 


    private DataModel model ; 

    public void setDataModel(DataModel dataModel) { 
     if (this.model != null) { 
      this.model.currentUserProperty().removeListener(userListener); 
     } 
     this.model = dataModel ; 
     this.model.currentUserProperty().addListener(userListener); 
    } 

    @FXML 
    private void cancel() { 
     firstNameField.setText(cachedFirstName); 
     lastNameField.setText(cachedLastName); 
     adminCheckBox.setSelected(cachedAdmin); 
    } 
} 

Table.fxml :

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

<?import javafx.scene.layout.StackPane?> 
<?import javafx.scene.control.TableView?> 
<?import javafx.scene.control.TableColumn?> 

<StackPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.TableController"> 
    <TableView fx:id="table"> 
     <columns> 
      <TableColumn fx:id="firstNameColumn" text="First Name"/> 
      <TableColumn fx:id="lastNameColumn" text="Last Name"/> 
      <TableColumn fx:id="adminColumn" text="Administrator"/> 
     </columns> 
    </TableView> 
</StackPane> 

UserEditor.fxml :

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

<?import javafx.scene.layout.GridPane?> 
<?import javafx.scene.layout.ColumnConstraints?> 
<?import javafx.scene.control.Label?> 
<?import javafx.scene.control.TextField?> 
<?import javafx.scene.control.CheckBox?> 
<?import javafx.scene.control.Button?> 
<?import javafx.geometry.Insets?> 

<GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.UserEditorController" 
     hgap="5" vgap="5" alignment="CENTER"> 

    <columnConstraints> 
     <ColumnConstraints halignment="RIGHT" hgrow="NEVER"/> 
     <ColumnConstraints halignment="LEFT" hgrow="SOMETIMES"/> 
    </columnConstraints> 

    <padding> 
     <Insets top="5" left="5" bottom="5" right="5"/> 
    </padding> 

    <Label text="First Name:" GridPane.columnIndex="0" GridPane.rowIndex="0"/> 
    <Label text="Last Name:" GridPane.columnIndex="0" GridPane.rowIndex="1"/> 
    <Label text="Admin Priviliges:" GridPane.columnIndex="0" GridPane.rowIndex="2"/> 

    <TextField fx:id="firstNameField" GridPane.columnIndex="1" GridPane.rowIndex="0"/> 
    <TextField fx:id="lastNameField" GridPane.columnIndex="1" GridPane.rowIndex="1"/> 
    <CheckBox fx:id="adminCheckBox" GridPane.columnIndex="1" GridPane.rowIndex="2"/> 
    <Button text="Cancel" onAction="#cancel" GridPane.columnIndex="0" GridPane.rowIndex="3" GridPane.columnSpan="2" 
     GridPane.halignment="CENTER"/> 

</GridPane> 

MainController.java :

package usermasterdetail; 

import javafx.fxml.FXML; 

public class MainController { 
    @FXML 
    private TableController tableController ; 
    @FXML 
    private UserEditorController editorController ; 

    private final DataModel model = new DataModel(); 

    public void initialize() { 
     tableController.setDataModel(model); 
     editorController.setDataModel(model); 
    } 
} 

Main.fxml :

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

<?import javafx.scene.control.SplitPane?> 

<SplitPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.MainController"> 
    <items> 
     <fx:include fx:id="table" source="Table.fxml"/> 
     <fx:include fx:id="editor" source="UserEditor.fxml"/> 
    </items> 
</SplitPane> 

그리고 마지막으로 Main.java : 당신은 당신이 설명 된 사용자 경험을 선호하는 경우 (@SSchuette이 코멘트에 대해 설명대로)

package usermasterdetail; 

import java.io.IOException; 

import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 

public class Main extends Application { 

    @Override 
    public void start(Stage primaryStage) throws IOException { 
     primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("Main.fxml")), 800, 600)); 
     primaryStage.show(); 
    } 

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

, 당신은 단지에 테이블의 비활성화 속성을 바인딩 할 수 있습니다 속성을 수정. 이렇게하면 데이터를 수정하는 동안 (즉 표의 데이터와 일치하지 않음) 사용자가 선택 항목을 변경하지 못하게됩니다.이를 위해 당신은 모델의 수정 속성을 필요

package usermasterdetail; 

import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.SimpleBooleanProperty; 
import javafx.beans.property.SimpleObjectProperty; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 

public class DataModel { 

    private final ObservableList<User> userList = FXCollections.observableArrayList(
      new User("Jacob", "Smith", false), 
      new User("Isabella", "Johnson", true), 
      new User("Ethan", "Williams", false), 
      new User("Emma", "Jones", true), 
      new User("Michael", "Brown", true) 
    ); 

    private final ObjectProperty<User> currentUser = new SimpleObjectProperty<>(); 

    private final BooleanProperty modifying = new SimpleBooleanProperty(); 

    public final ObjectProperty<User> currentUserProperty() { 
     return this.currentUser; 
    } 


    public final usermasterdetail.User getCurrentUser() { 
     return this.currentUserProperty().get(); 
    } 


    public final void setCurrentUser(final usermasterdetail.User currentUser) { 
     this.currentUserProperty().set(currentUser); 
    } 


    public ObservableList<User> getUserList() { 
     return userList; 
    } 


    public final BooleanProperty modifyingProperty() { 
     return this.modifying; 
    } 



    public final boolean isModifying() { 
     return this.modifyingProperty().get(); 
    } 



    public final void setModifying(final boolean modifying) { 
     this.modifyingProperty().set(modifying); 
    } 


} 

다음 테이블 컨트롤러에 당신이 그것에 비활성화 속성을 바인딩 할 수 있습니다 :

package usermasterdetail; 

import javafx.fxml.FXML; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.control.cell.CheckBoxTableCell; 

public class TableController { 

    @FXML 
    private TableView<User> table ; 
    @FXML 
    private TableColumn<User, String> firstNameColumn ; 
    @FXML 
    private TableColumn<User, String> lastNameColumn ; 
    @FXML 
    private TableColumn<User, Boolean> adminColumn ; 

    private DataModel model ; 

    public void initialize() { 
     firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty()); 
     lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty()); 
     adminColumn.setCellValueFactory(cellData -> cellData.getValue().adminProperty()); 
     adminColumn.setCellFactory(CheckBoxTableCell.forTableColumn(adminColumn)); 
    } 

    public void setDataModel(DataModel dataModel) { 
     if (model != null) { 
      model.currentUserProperty().unbind(); 
     } 
     this.model = dataModel ; 
     dataModel.currentUserProperty().bind(table.getSelectionModel().selectedItemProperty()); 
     table.setItems(model.getUserList()); 
     table.disableProperty().bind(model.modifyingProperty()); 
    } 
} 

약간의 작업이 거기에있다 유일한 장소

package usermasterdetail; 

import javafx.beans.value.ChangeListener; 
import javafx.fxml.FXML; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.TextField; 

public class UserEditorController { 

    @FXML 
    private TextField firstNameField ; 
    @FXML 
    private TextField lastNameField ; 
    @FXML 
    private CheckBox adminCheckBox ; 

    private DataModel model ; 

    private ChangeListener<Object> modifyingListener = (obs, oldValue, newValue) -> { 
     if (model != null) { 
      if (model.getCurrentUser() == null) { 
       model.setModifying(false); 
      } else { 
       model.setModifying(! (model.getCurrentUser().getFirstName().equals(firstNameField.getText()) 
         && model.getCurrentUser().getLastName().equals(lastNameField.getText()) 
         && model.getCurrentUser().isAdmin() == adminCheckBox.isSelected())); 
      } 
     } 

    }; 

    private ChangeListener<User> userListener = (obs, oldUser, newUser) -> { 
     if (oldUser != null) { 
      oldUser.firstNameProperty().removeListener(modifyingListener); 
      oldUser.lastNameProperty().removeListener(modifyingListener); 
      oldUser.adminProperty().removeListener(modifyingListener); 
     } 
     if (newUser == null) { 
      firstNameField.clear(); 
      lastNameField.clear(); 
      adminCheckBox.setSelected(false); 
     } else { 
      firstNameField.setText(newUser.getFirstName()); 
      lastNameField.setText(newUser.getLastName()); 
      adminCheckBox.setSelected(newUser.isAdmin()); 

      newUser.firstNameProperty().addListener(modifyingListener); 
      newUser.lastNameProperty().addListener(modifyingListener); 
      newUser.adminProperty().addListener(modifyingListener); 
     } 
    }; 


    public void setDataModel(DataModel dataModel) { 
     if (this.model != null) { 
      this.model.currentUserProperty().removeListener(userListener); 
     } 
     this.model = dataModel ; 
     this.model.currentUserProperty().addListener(userListener); 
    } 

    public void initialize() { 
     firstNameField.textProperty().addListener(modifyingListener); 
     lastNameField.textProperty().addListener(modifyingListener); 
     adminCheckBox.selectedProperty().addListener(modifyingListener); 
    } 


    @FXML 
    private void cancel() { 

     if (model != null) { 
      firstNameField.setText(model.getCurrentUser().getFirstName()); 
      lastNameField.setText(model.getCurrentUser().getLastName()); 
      adminCheckBox.setSelected(model.getCurrentUser().isAdmin()); 
     } 
    } 

    @FXML 
    private void update() { 
     if (model != null && model.getCurrentUser() != null) { 
      model.getCurrentUser().setFirstName(firstNameField.getText()); 
      model.getCurrentUser().setLastName(lastNameField.getText()); 
      model.getCurrentUser().setAdmin(adminCheckBox.isSelected()); 

     } 
    } 


} 

이 솔루션은 별도의 엉덩이가 필요합니다 할 수는 수정 속성이 true로 (이미 이런 짓을했는지처럼 소리지만) 데이터가 동기화되지 않은 시간이 설정되어 있는지 확인하는 것입니다 데이터 (및 테이블)에서 업데이트를 강제 실행하십시오.

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

<?import javafx.scene.layout.GridPane?> 
<?import javafx.scene.layout.ColumnConstraints?> 
<?import javafx.scene.control.Label?> 
<?import javafx.scene.control.TextField?> 
<?import javafx.scene.control.CheckBox?> 
<?import javafx.scene.control.Button?> 
<?import javafx.geometry.Insets?> 
<?import javafx.scene.layout.HBox?> 

<GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.UserEditorController" 
     hgap="5" vgap="5" alignment="CENTER"> 

    <columnConstraints> 
     <ColumnConstraints halignment="RIGHT" hgrow="NEVER"/> 
     <ColumnConstraints halignment="LEFT" hgrow="SOMETIMES"/> 
    </columnConstraints> 

    <padding> 
     <Insets top="5" left="5" bottom="5" right="5"/> 
    </padding> 

    <Label text="First Name:" GridPane.columnIndex="0" GridPane.rowIndex="0"/> 
    <Label text="Last Name:" GridPane.columnIndex="0" GridPane.rowIndex="1"/> 
    <Label text="Admin Priviliges:" GridPane.columnIndex="0" GridPane.rowIndex="2"/> 

    <TextField fx:id="firstNameField" GridPane.columnIndex="1" GridPane.rowIndex="0"/> 
    <TextField fx:id="lastNameField" GridPane.columnIndex="1" GridPane.rowIndex="1"/> 
    <CheckBox fx:id="adminCheckBox" GridPane.columnIndex="1" GridPane.rowIndex="2"/> 
    <HBox spacing="5" alignment="CENTER" GridPane.columnIndex="0" GridPane.rowIndex="3" GridPane.columnSpan="2"> 
     <Button text="Update" onAction="#update"/> 
     <Button text="Cancel" onAction="#cancel"/> 
    </HBox> 

</GridPane> 
+1

가 구현하는 정말 정직하고 "쉬운"솔루션이었다으로 지금이 순간 내가 @SSchuette의 솔루션을 구현했습니다. James_D는 길고 상세한 답변을 주셔서 감사합니다. 오늘은 해결책을 찾아 보겠습니다. 마지막으로 JavaFX의 bindig 사용법을 이해합니다! (정말로 JavaFX에 처음 익숙해졌고, 무엇을 impelement하는 최선의 방법을 이해하려고 노력했습니다!) 감사합니다! – pbex

+0

여러분을 환영합니다! 차이점/장점을보고 이해할 수있는 다양한 방법을 구현하는 것은 적절한 접근 방법입니다 ("실험"을위한 충분한 시간을 확보 한 경우). 당신은 "바인딩 개념"로 시작하는 경우 (예를 들어, MVVM [https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel])는 처음에 쓸모 오버 헤드 것 같다 -하지만 한 번 이해했다 ... – SSchuette