2016-10-18 12 views
1

Я создал пользовательскую панель Master-Detail для моего проекта, где я использую разделяемую панель, в каждой из которых есть две анкерные панели. В одном есть TableView, заполненный пользователями (ObservableList). В каждой строке (Пользователь) я внедрил ChangeListener table.getSelectionModel().selectedItemProperty().addListener(listElementChangeListener()); , когда строка выбрана, я передаю UserObject для моей DetailPane и визуализирую данные пользователя в TextFields как детали. Я реализовал элементы управления, чтобы понять, изменяется ли пользователь в деталях, и если это так, я бы хотел предотвратить изменение строки в моем TableView. Я попытался удалить ChangeListener из TableView, когда я изменяю пользователя, но он работает хорошо. Я думаю о решении, таком как установка фокуса и удерживание его в строке до тех пор, пока я не отменит или не сохранит измененный пользователь.JavaFX custom MasterDetail pane

Есть ли какие-нибудь приятные решения?

Благодарим за помощь.

+0

Как отключить таблицу, чтобы предотвратить изменение выбора при изменении данных? После, например, сброс или сохранение (например, кнопок) изменений, которые вы могли бы снова включить в таблицу. С моей точки зрения, это лучший подход к вашей цели, и тот, который обычно используется в таком сценарии. – SSchuette

+1

@ Предложение SSchuette является хорошим. Второй вариант, который дает несколько иной пользовательский опыт, заключается в использовании двунаправленной привязки между вашими текстовыми полями и свойствами в пользовательском объекте, чтобы изменения отражались сразу в объекте (и в таблице). Затем вы можете добавить кнопку «Отменить» в подробном представлении, которое вернется к исходным значениям. Любое из этих решений довольно легко реализовать. –

+0

@James_D Я полностью с тобой! К сожалению, мой опыт в контексте JavaFX состоит в том, что большинство людей перемещаются/перемещаются из Swing в JavaFX, поэтому такие концепции, как, например, MVVM (я из области .NET/WPF ;-)) неизвестен и/или не совсем понятен. – SSchuette

ответ

3

Возможно, я подойду к этому немного по-другому. Я бы привязывал элементы управления в «подробном представлении» в двух направлениях к свойствам объекта 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:

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 описывает в комментариях), просто связать отключить свойство таблицы в изменяя свойство. Это не позволит пользователю изменять выбор во время редактирования данных (т. Е. Не соответствует данным в таблице).Для этого вам просто нужно модифицирующие свойства в модели:

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

     } 
    } 


} 

Это решение требует дополнительного окурок для включения обновления в данные (и таблицу):

<?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 благодарит вас за ваш длинный и подробный ответ, сегодня я попробую ваше решение! Наконец, я понял, как использовать bindig для JavaFX! (im действительно новый для JavaFX, и пытается понять, что лучший способ для impelement вещей!) Спасибо, ребята! – pbex

+0

Добро пожаловать! Это правильный подход (если у вас достаточно времени для «экспериментов»), чтобы реализовать различные способы увидеть и понять различия/преимущества. Когда вы начинаете с «концепции привязки» (например, MVVM [https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel]), изначально это кажется бесполезным накладные расходы, но один раз понял ... – SSchuette