JavaFX自定义MasterDetail窗格 [英] JavaFX custom MasterDetail pane

查看:98
本文介绍了JavaFX自定义MasterDetail窗格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have created a custom Master-Detail pane for my project, where i use a split pane, in each i have two Anchor Panes. In one there is a TableView filled with Users (ObservableList). On each row (User) i have implemented a ChangeListener table.getSelectionModel().selectedItemProperty().addListener(listElementChangeListener()); when the row is selected, i pass the UserObject for my DetailPane, and visualize User data in TextFields as detail. I have implemented controls, to understand if the User is under modification in Detail, and if so i would like to prevent a row change in my TableView. I tried to remove the ChangeListener from the TableView when i modify the User, but it dosent work well. I'm thinking of a solution like setting the focus and holding it on the row until i cancel or save the User modified.

有什么好的解决方案吗?

Is there any nice solutions?

感谢您的帮助.

推荐答案

我可能会对此有所不同.我将详细视图"中的控件双向绑定到User对象中的属性.这样,当用户编辑它们时,它们将在对象(和表)中更新.如果您愿意,还可以提供一个取消"按钮以还原为以前的值.

I would probably approach this a little differently. I would bind the controls in the "detail view" bidirectionally to the properties in the User object. That way they will be updated in the object (and the table) as the user edits them. If you like, you can also provide a "cancel" button to revert to the previous values.

这是使用此方法的完整解决方案:

Here's a complete solution that uses this approach:

User.java:

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:

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:

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

UserEditorController.java:

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

Table.fxml:

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:

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:

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:

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:

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


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


If you prefer the user experience you described, you can (as @SSchuette describes in the comments), just bind the table's disable property to the modifying property. This will prevent the user from changing the selection while the data is being edited (i.e. is not consistent with the data in the table). For this you just need the modifying property in the model:

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


}

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

then in the table controller you can bind the disable property to it:

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

要做的唯一工作就是确保在数据不同步时(尽管听起来您已经完成此操作)将Modifying属性设置为true:

The only place there is a bit of work to do is to make sure the modifying property is set to true any time the data are not in sync (though it sounds like you have already done this):

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

        }
    }


}

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

This solution requires an additional button to force the update in the data (and table):

<?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>

这篇关于JavaFX自定义MasterDetail窗格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆