JavaFX Как обновить элементы таблицы, но сохранить состояние ячеек для существующих элементов? - PullRequest
0 голосов
/ 26 июня 2019

DEMO: https://github.com/CorellianAle/so_tableviewexpandercolumn

GIST: https://gist.github.com/CorellianAle/0f6844edc759a766400b98dda690dfbe

У меня есть приложение JavaFX, которое использует TableView для отображения спискасотрудников, которые хранятся в базе данных.Я также использую TableRowExpanderColumn из ControlsFX для предоставления дополнительного пользовательского интерфейса для изменения адресов электронной почты и номеров, потому что я не знаю, как отобразить несколько значений в одном столбце / ячейке.

enter image description here

Проблема: Когда я обновляю элементы, связанные с представлением таблицы, он обновляется сам и закрывает все развернутые строки.Мне нужен способ хранения расширенных строк, чтобы снова открывать их после обновления.

Если я хочу добавить несколько электронных писем или номеров, мне нужно нажимать на расширение строки после каждого добавления.

Мои мысли: Я обнаружил, что невозможно получить доступ к коллекции строк, потому что TableView использует VirtualFlow, который создает строки только для видимых элементов.

Я мог бы добавить логический флагпеременная (isExpanded) до EmployeeEx, но это похоже на грязный хак.Я понимаю, что это происходит потому, что каждый раз, когда я обновляю employees, я сохраняю совершенно новый экземпляр коллекции, и это заставляет TableView генерировать совершенно новую коллекцию строк / ячеек, теряя при этом свои настройки.

Я думаю, что для достижения своей цели мне нужно сохранять статичность своей коллекции (тот же адрес памяти) - обновлять каждый элемент текущей коллекции (ListProperty<EmployeeEx> employees) вместо создания новой коллекции при каждом обновлении.

Мне нужно реализовать UPDATE IF EXISTS, INSERT IF NEW, DELETE IF MISSING, чтобы обновить метод.

Я не уверен, что это правильный подход (задерживается ли он?).Как это реализовать, если это верное решение?

class Position
{
    protected IntegerProperty id = new SimpleIntegerProperty();
    protected StringProperty name = new SimpleStringProperty();
    protected StringProperty comment = new SimpleStringProperty();
    //...
}

class EmployeeEmail
{
    protected IntegerProperty id = new SimpleIntegerProperty();
    protected StringProperty email= new SimpleStringProperty();
    protected StringProperty comment = new SimpleStringProperty();
    //...
}

class EmployeeNumber
{
    protected IntegerProperty id = new SimpleIntegerProperty();
    protected StringProperty number= new SimpleStringProperty();
    protected StringProperty comment = new SimpleStringProperty();
    //...
}

class Employee
{
    protected StringProperty firstName = new SimpleStringProperty("");
    protected StringProperty middleName = new SimpleStringProperty("");
    protected StringProperty lastName = new SimpleStringProperty("");

    protected BooleanProperty isOffice = new SimpleBooleanProperty();
    protected IntegerProperty position = new SimpleIntegerProperty();
    //...
}

/**
 * Complete object.
 */
class EmployeeEx
{
    protected StringProperty firstName = new SimpleStringProperty();
    protected StringProperty middleName = new SimpleStringProperty();
    protected StringProperty lastName = new SimpleStringProperty();

    protected BooleanProperty isOffice = new SimpleBooleanProperty();
    protected ObjectProperty<Position> position = new SimpleObjectProperty<>();

    protected ListProperty<EmployeeNumber> numbers = new SimpleListProperty<>();
    protected ListProperty<EmployeeEmail> emails = new SimpleListProperty<>();
    //...
}

/**
 * fxml controller class
 */
class Controller
{
    ListProperty<EmployeeEx> employees; //polulates tableview
    ListProperty<Position> positions; //populates comboboxes

    IEmployeesDAO employeesDAO; //these DAOs get ResultSets from JDBC and convert them to ObservableLists.
    IPositionsDAO positionsDAO;
    IEmployeeNumberDAO employeeNumberDAO;
    IEmployeeEmailDAO employeeEmailDAO;'

    /**
     * Gets positions from database.
     */
    void updatePositions()
    {
        //get data
        ObservableList<Position> positions = positionsDAO.getAll();

        //update table
        this.positions.setValue(positions);
    }

    /**
     * Gets employees from database. Uses data from multiple table and constructs a list of complete objects.
     */
    void updateEmployees()
    {
        //get data
        ObservableList<Employee> employees = employeesDAO.getAll();
        ObservableList<EmployeeEx> employeeExs = FXCollections.observableArrayList();

        ObservableList<EmployeeNumber> numbers = employeeNumberDAO.getAll();
        ObservableList<EmployeeEmail> emails = employeeEmailDAO.getAll();

        //contruct complete objects
        for (Employee employee : employees)
        {
            EmployeeEx employeeEx = ExConverters.toEmployeeEx(employee, positions, numbers, emails);
            employeeExs.add(employeeEx);
        }

        //update table
        this.employees.setValue(employeeExs);
    }

    /**
     *
     */
    void generateTable()
    {
        //emails and numbers ui
        expanderColumn = new TableRowExpanderColumn<EmployeeEx>(this::createEditor);
        expanderColumn.setMinWidth(15);
        tableView.getColumns().add(expanderColumn);

        //first name
        String tableColumn_title_firstName = Utility.getResourceString("employees", "tableColumn_firstName", CM.getLocale());
        TableColumn<EmployeeEx, String> tableColumn_firstName = new TableColumn<>(tableColumn_title_firstName);
        tableColumn_firstName.setMinWidth(150);
        tableColumn_firstName.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
        tableColumn_firstName.setCellFactory(TextFieldTableCell.forTableColumn());
        tableColumn_firstName.setOnEditCommit((TableColumn.CellEditEvent<EmployeeEx, String> event) ->
        {
            TablePosition<EmployeeEx, String> position = event.getTablePosition();
            String str = event.getNewValue();
            EmployeeEx employeeEx = event.getTableView().getItems().get(position.getRow());
            employeeEx.setFirstName(str);
        });
        tableView.getColumns().add(tableColumn_firstName);

        //middle name
        String tableColumn_title_middleName = Utility.getResourceString("employees", "tableColumn_middleName", CM.getLocale());
        TableColumn<EmployeeEx, String> tableColumn_middleName = new TableColumn<>(tableColumn_title_middleName);
        tableColumn_middleName.setMinWidth(150);
        tableColumn_middleName.setCellValueFactory(cellData -> cellData.getValue().middleNameProperty());
        tableColumn_middleName.setCellFactory(TextFieldTableCell.forTableColumn());
        tableColumn_middleName.setOnEditCommit((TableColumn.CellEditEvent<EmployeeEx, String> event) ->
        {
            TablePosition<EmployeeEx, String> position = event.getTablePosition();
            String firstName = event.getNewValue();
            EmployeeEx employeeEx = event.getTableView().getItems().get(position.getRow());
            employeeEx.setFirstName(firstName);
        });
        tableView.getColumns().add(tableColumn_middleName);

        //last name
        String tableColumn_title_lastName = Utility.getResourceString("employees", "tableColumn_lastName", CM.getLocale());
        TableColumn<EmployeeEx, String> tableColumn_lastName = new TableColumn<>(tableColumn_title_lastName);
        tableColumn_lastName.setMinWidth(150);
        tableColumn_lastName.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
        tableColumn_lastName.setCellFactory(TextFieldTableCell.forTableColumn());
        tableColumn_lastName.setOnEditCommit((TableColumn.CellEditEvent<EmployeeEx, String> event) ->
        {
            TablePosition<EmployeeEx, String> position = event.getTablePosition();
            String str = event.getNewValue();
            EmployeeEx employeeEx = event.getTableView().getItems().get(position.getRow());
            employeeEx.setLastName(str);
        });
        tableView.getColumns().add(tableColumn_lastName);

        //is office
        String tableColumn_title_isOffice = Utility.getResourceString("employees", "tableColumn_isOffice", CM.getLocale());
        TableColumn<EmployeeEx, Boolean> tableColumn_isOffice = new TableColumn<>(tableColumn_title_isOffice);
        tableColumn_isOffice.setMinWidth(150);
        tableColumn_isOffice.setCellValueFactory(param ->
        {
            EmployeeEx employeeEx = param.getValue();
            return employeeEx.isOfficeProperty();
        });
        tableColumn_isOffice.setCellFactory(p ->
        {
            CheckBoxTableCell<EmployeeEx, Boolean> cell = new CheckBoxTableCell<EmployeeEx, Boolean>();
            cell.setPadding(new Insets(3, 0, 0, 0));
            cell.setAlignment(Pos.TOP_CENTER);
            return cell;
        });

        tableView.getColumns().add(tableColumn_isOffice);

        //position
        String tableColumn_title_company = Utility.getResourceString("employees", "tableColumn_position", CM.getLocale());
        TableColumn<EmployeeEx, Position> tableColumn_position = new TableColumn<>(tableColumn_title_company);
        tableColumn_position.setMinWidth(150);
        tableColumn_position.setCellValueFactory(param ->
        {
            EmployeeEx userEx = param.getValue();
            return userEx.positionProperty();
        });
        tableColumn_position.setCellFactory(ComboBoxTableCell.forTableColumn(positions));
        tableColumn_position.setOnEditCommit((TableColumn.CellEditEvent<EmployeeEx, Position> event) -> {
            TablePosition<EmployeeEx, Position> pos = event.getTablePosition();
            Position posiiton = event.getNewValue();
            EmployeeEx employeeEx = event.getTableView().getItems().get(pos.getRow());
            employeeEx.setPosition(posiiton);
        });

        tableView.getColumns().add(tableColumn_position);

        //comment
        String tableColumn_title_comment = Utility.getResourceString("employees", "tableColumn_comment", CM.getLocale());
        TableColumn<EmployeeEx, String> tableColumn_comment = new TableColumn<>(tableColumn_title_comment);
        tableColumn_comment.setMinWidth(150);
        tableColumn_comment.setCellValueFactory(cellData -> cellData.getValue().commentProperty());
        tableColumn_comment.setCellFactory(TextFieldTableCell.forTableColumn());
        tableColumn_comment.setOnEditCommit((TableColumn.CellEditEvent<EmployeeEx, String> event) ->
        {
            TablePosition<EmployeeEx, String> position = event.getTablePosition();
            String str = event.getNewValue();
            EmployeeEx employeeEx = event.getTableView().getItems().get(position.getRow());
            employeeEx.setLastName(str);
        });
        tableView.getColumns().add(tableColumn_comment);

        //this is where binding happens
        tableView.setItems(employees);
    }
}

/**
 * Converts {@link Employee} to {@link EmployeeEx}.
 * It is not optimal sure =(
 */
public static EmployeeEx toEmployeeEx(Employee employee, 
                                      ObservableList<Position> positions, 
                                      ObservableList<EmployeeNumber> numbers, 
                                      ObservableList<EmployeeEmail> emails)
{
    EmployeeEx employeeEx = new EmployeeEx();

    employeeEx.setId(employee.getId());
    employeeEx.setFirstName(employee.getFirstName());
    employeeEx.setMiddleName(employee.getMiddleName());
    employeeEx.setLastName(employee.getLastName());
    employeeEx.setIsOffice(employee.isIsOffice());

    //
    Optional<Position> position = positions.stream().filter(c -> c.getId() == employee.getPosition()).findFirst();

    if (position.isPresent())
    {
        employeeEx.setPosition(position.get());
    }

    //
    numbers.stream().filter(n -> n.getEmployee() == employee.getId()).forEach(number -> {
        employeeEx.getNumbers().add(number);
    });

    //
    emails.stream().filter(e -> e.getEmployee() == employee.getId()).forEach(email -> {
        employeeEx.getEmails().add(email);
    });

    employeeEx.setDateTimeLastModified(employee.getDateTimeLastModified());

    return employeeEx;
}

/**
 * From {@link IEmployeesDAO}.
 * Connects to the database and pulls a list of employees.
 */
@Override
public ObservableList<Employee> getAll() throws Exception
{
    Connection connection = null;
    PreparedStatement statement = null;
    ResultSet resultSet = null;

    try
    {
        connection = Database.getInstance().getConnection();
        statement = connection.prepareStatement("SELECT * FROM " + TABLE + " ORDER BY lastname");

        if (statement.execute())
        {
            resultSet = statement.getResultSet();
            ObservableList<Employee> employees = FXCollections.observableArrayList();

            while (resultSet.next())
            {
                Employee employee = new Employee();
                employee.setId(resultSet.getInt("id"));
                employee.setName(resultSet.getString("name"));
                employee.setComment(resultSet.getString("comment"));
                employee.setFirstName(resultSet.getString("firstname"));
                employee.setMiddleName(resultSet.getString("middlename"));
                employee.setLastName(resultSet.getString("lastname"));
                employee.setIsOffice(resultSet.getBoolean("isoffice"));
                employee.setPosition(resultSet.getInt("positionref"));

                Timestamp created_timestamp = resultSet.getTimestamp("lastmodified");
                DateTime created_datetime = DateTime.fromLocalDateTime(created_timestamp.toLocalDateTime());
                employee.setDateTimeLastModified(created_datetime);

                employees.add(employee);
            }

            return employees;
        }

        return null;
    }
    catch (SQLException ex)
    {
        Log.error("Unable to get all employees. " + ex.getMessage());
        throw ex;
    }
    finally
    {
        Database.closeQuietly(resultSet);
        Database.closeQuietly(statement);

    }
}
...