DEMO: https://github.com/CorellianAle/so_tableviewexpandercolumn
GIST: https://gist.github.com/CorellianAle/0f6844edc759a766400b98dda690dfbe
У меня есть приложение JavaFX, которое использует TableView
для отображения спискасотрудников, которые хранятся в базе данных.Я также использую TableRowExpanderColumn
из ControlsFX для предоставления дополнительного пользовательского интерфейса для изменения адресов электронной почты и номеров, потому что я не знаю, как отобразить несколько значений в одном столбце / ячейке.
Проблема: Когда я обновляю элементы, связанные с представлением таблицы, он обновляется сам и закрывает все развернутые строки.Мне нужен способ хранения расширенных строк, чтобы снова открывать их после обновления.
Если я хочу добавить несколько электронных писем или номеров, мне нужно нажимать на расширение строки после каждого добавления.
Мои мысли: Я обнаружил, что невозможно получить доступ к коллекции строк, потому что 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);
}
}