JavaFX自定义MasterDetail窗格

pbe*_*bex 1 setfocus master-detail tableview javafx-8

我为我的项目创建了一个自定义的“主/从”窗格,在其中使用了一个拆分窗格,每个窗格中都有两个“锚定窗格”。在其中一个TableView中填充了Users(ObservableList)。在每行(用户)上,table.getSelectionModel().selectedItemProperty().addListener(listElementChangeListener()); 当选择了该行时,我都实现了一个ChangeListener ,我为我的DetailPane传递了UserObject,并将TextFields中的用户数据可视化为详细信息。我已经实现了控件,以了解用户是否在详细信息中进行修改,如果是这样,我想防止TableView中的行发生更改。当我修改用户时,我试图从TableView中删除ChangeListener,但是它工作得很好。我正在考虑一种解决方案,例如设置焦点并将其保持在行上,直到我取消或保存修改的User。

有什么好的解决方案吗?

谢谢你的帮助。

Jam*_*s_D 5

我可能会对此有所不同。我将在“详细信息视图”中将控件双向绑定到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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

UserEditorController.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Table.fxml:

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

UserEditor.fxml:

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Main.fxml:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

最后是Main.java:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您更喜欢所描述的用户体验,则可以(如@SSchuette在评论中所述),只需将表的disable属性绑定到Modifying属性。这将防止用户在编辑数据时更改选择(即与表中的数据不一致)。为此,您只需要在模型中修改属性即可:

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);
    }


}
Run Code Online (Sandbox Code Playgroud)

然后在表控制器中可以将disable属性绑定到它:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一需要做的工作就是确保每当数据不同步时,将Modifying属性设置为true(尽管听起来您已经这样做了):

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());

        }
    }


}
Run Code Online (Sandbox Code Playgroud)

此解决方案需要一个附加按钮来强制更新数据(和表):

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);
    }
}
Run Code Online (Sandbox Code Playgroud)