Вот пример приложения.Это следует за MVVM стиль , который подходит для этого вида работы.Приложение было построено с использованием Java 13 и не будет работать в более ранних версиях Java, таких как Java 8. Это довольно длинный ответ, но, ну да, иногда это то, что нужно.
Общий подход - не для создания строки табличного представления для каждого магазина, в котором хранится статья. Вместо этого мы просто создаем строку для каждой статьи, и у нас есть пользовательское средство визуализации ячеек, которое создает одну отформатированную ячейку для всех магазинов и количеств, которыеэтот элемент хранится в.
Теперь вы можете сделать альтернативную реализацию, основанную на пользовательском rowFactory .Тем не менее, я не рекомендую этот подход для этой конкретной задачи, так как считаю, что его реализация и обслуживание будут излишне сложными без предоставления достаточного значения.
Еще один способ сделать это -использовать вложенные столбцы.При таком подходе действительно позволяет вам создать строку таблицы для каждого магазина, в котором хранится статья. Если вы сделаете это, вам потребуется какой-либо способ заполнения разных данных в зависимости от того,строка является либо первой строкой в группе, либо нет.Вы не позволяете пользователю переупорядочивать и сортировать данные в таблице, так как это будет довольно сложно обслуживать, потому что понятие «первая строка в группе» будет меняться навсегда.Чтобы обеспечить соответствующий рендеринг с вложенными столбцами, вы получите немного другую модель представления (класс FlatLineItem
ниже и сопровождающий метод в LineItemService
, который их извлекает).
На рисунке ниже показановывод TableView с пользовательским средством визуализации ячеек слева и TableView с использованием вложенных столбцов справа.Обратите внимание, как выбор работает по-разному в каждом случае.Слева, когда выбрана строка, она включает все хранилища, которые прикреплены к этой строке.Справа, когда используются вложенные столбцы, при выборе строки выбирается только строка для данного магазина.
Класс основного приложения
Это устанавливает пару TableViews.
Для первого табличного представления все, что он делает, - это создает табличное представление со столбцом для каждого из отображаемых элементов.Все данные извлекаются из класса модели представления LineItem
с использованием стандартного PropertyValueFactory
.Немного другая вещь - это настраиваемое средство визуализации ячеек для поля StoredQuantity
через StoredQuantityTableCell
, это объяснено позже.
Во втором представлении используются вложенные столбцы и работает на основе FlatLineItem
просмотр класса модели, также использующий стандартный PropertyValueFactory
и не использующий пользовательский модуль визуализации ячеек.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.List;
public class AggregateViewApp extends Application {
@Override
public void start(Stage stage) throws Exception {
LineItemService lineItemService = new LineItemService();
TableView<LineItem> tableView = createArticleTableView();
tableView.getItems().setAll(lineItemService.fetchAllLineItems());
TableView<FlatLineItem> nestedTableView = createNestedArticleTableView();
nestedTableView.getItems().setAll(lineItemService.fetchAllFlatLineItems());
HBox layout = new HBox(
40,
tableView,
nestedTableView
);
stage.setScene(new Scene(layout));
stage.show();
}
@SuppressWarnings("unchecked")
private TableView<LineItem> createArticleTableView() {
TableView tableView = new TableView();
TableColumn<LineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
TableColumn<LineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
TableColumn<LineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.setCellValueFactory(new PropertyValueFactory<>("storedQuantities"));
storedArticleCol.setCellFactory(lineItemStringTableColumn -> new StoredQuantityTableCell());
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
tableView.getColumns().addAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 150);
return tableView;
}
@SuppressWarnings("unchecked")
private TableView<FlatLineItem> createNestedArticleTableView() {
TableView tableView = new TableView();
TableColumn<FlatLineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
articleIdCol.setSortable(false);
TableColumn<FlatLineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
nameCol.setSortable(false);
TableColumn<FlatLineItem, String> storeCol = new TableColumn<>("Store");
storeCol.setCellValueFactory(new PropertyValueFactory<>("storeName"));
storeCol.setSortable(false);
TableColumn<FlatLineItem, String> storeQuantityCol = new TableColumn<>("Quantity");
storeQuantityCol.setCellValueFactory(new PropertyValueFactory<>("storeQuantity"));
storeQuantityCol.setSortable(false);
TableColumn<FlatLineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.getColumns().setAll(
storeCol,
storeQuantityCol
);
storedArticleCol.setSortable(false);
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
totalCol.setSortable(false);
tableView.getColumns().setAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 200);
return tableView;
}
public static void main(String[] args) {
launch(AggregateViewApp.class);
}
}
StoredQuantityTableCell.java
Для этого требуется список StoredQuantitiesкоторый является кортежем имени магазина и количества вещей, хранящихся в этом магазине, а затем отображает этот список в одну ячейку, форматируя отображение внутри в GridView.Вы можете использовать любой внутренний макет узла или форматирование, которое вы пожелаете, и добавить CSS-стиль, чтобы оживить его, если это необходимо.
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.layout.GridPane;
import java.util.List;
class StoredQuantityTableCell extends TableCell<LineItem, List<StoredQuantity>> {
private GridPane storedQuantityPane;
public StoredQuantityTableCell() {
storedQuantityPane = new GridPane();
storedQuantityPane.setHgap(10);
storedQuantityPane.setVgap(5);
}
@Override
protected void updateItem(List<StoredQuantity> storedQuantities, boolean empty) {
super.updateItem(storedQuantities, empty);
if (storedQuantities == null) {
setGraphic(null);
return;
}
storedQuantityPane.getChildren().removeAll(storedQuantityPane.getChildren());
int row = 0;
for (StoredQuantity storedQuantity: storedQuantities) {
storedQuantityPane.addRow(
row,
new Label(storedQuantity.getStoreName()),
new Label("" + storedQuantity.getQuantity())
);
row++;
}
setGraphic(storedQuantityPane);
}
}
LineItem.java
Класс модели представленияпредставляет строку в таблице.
import java.util.Collections;
import java.util.List;
public class LineItem {
private long articleId;
private String articleName;
private List<StoredQuantity> storedQuantities;
public LineItem(long articleId, String articleName, List<StoredQuantity> storedQuantities) {
this.articleId = articleId;
this.articleName = articleName;
this.storedQuantities = storedQuantities;
}
public long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public List<StoredQuantity> getStoredQuantities() {
return Collections.unmodifiableList(storedQuantities);
}
public int getTotal() {
return storedQuantities.stream()
.mapToInt(StoredQuantity::getQuantity)
.sum();
}
}
StoredQuantity.java
Класс модели представления, представляющий название магазина и количество вещей в магазине.Это используется StoredQuantityTableCell для отображения сохраненных количеств для отдельной позиции.
public class StoredQuantity implements Comparable<StoredQuantity> {
private String storeName;
private int quantity;
StoredQuantity(String storeName, int quantity) {
this.storeName = storeName;
this.quantity = quantity;
}
public String getStoreName() {
return storeName;
}
public int getQuantity() {
return quantity;
}
@Override
public int compareTo(StoredQuantity o) {
return storeName.compareTo(o.storeName);
}
}
FlatLineItem.java
Класс модели представления, поддерживающий табличное представление с вложеннымиколонны.Плоская позиция, которую можно создать для каждого магазина, в котором хранится статья.
public class FlatLineItem {
private Long articleId;
private String articleName;
private final String storeName;
private final Integer storeQuantity;
private final Integer total;
private final boolean firstInGroup;
public FlatLineItem(Long articleId, String articleName, String storeName, Integer storeQuantity, Integer total, boolean firstInGroup) {
this.articleId = articleId;
this.articleName = articleName;
this.storeName = storeName;
this.storeQuantity = storeQuantity;
this.total = total;
this.firstInGroup = firstInGroup;
}
public Long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public String getStoreName() {
return storeName;
}
public Integer getStoreQuantity() {
return storeQuantity;
}
public Integer getTotal() {
return total;
}
public boolean isFirstInGroup() {
return firstInGroup;
}
}
LineItemService.java
Это преобразует значения из базы данных в объекты модели представления (LineItems или FlatLineItems), которые могут отображаться представлениями.Обратите внимание, что getFlatLineItemsForLineItem
, который создает FlatLineItems для представления таблицы вложенных столбцов, имеет представление о том, какой он является первой строкой в группе элементов строки, и соответствующим образом распространяет FlatLineItem, оставляя некоторые значения пустыми, если они просто повторяются.от первого элемента в группе, что приводит к чистому отображению.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LineItemService {
private final DB db = DB.instance();
public List<LineItem> fetchAllLineItems() {
return db.findAllArticles()
.stream()
.map(article -> createLineItemForArticle(article.getArticleId()))
.collect(Collectors.toList());
}
public List<FlatLineItem> fetchAllFlatLineItems() {
return fetchAllLineItems().stream()
.flatMap(lineItem -> getFlatLineItemsForLineItem(lineItem).stream())
.collect(Collectors.toList());
}
private List<FlatLineItem> getFlatLineItemsForLineItem(LineItem lineItem) {
ArrayList<FlatLineItem> flatLineItems = new ArrayList<>();
boolean firstStore = true;
for (StoredQuantity storedQuantity: lineItem.getStoredQuantities()) {
FlatLineItem newFlatLineItem;
if (firstStore) {
newFlatLineItem = new FlatLineItem(
lineItem.getArticleId(),
lineItem.getArticleName(),
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
lineItem.getTotal(),
true
);
firstStore = false;
} else {
newFlatLineItem = new FlatLineItem(
null,
null,
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
null,
false
);
}
flatLineItems.add(newFlatLineItem);
}
return flatLineItems;
}
private LineItem createLineItemForArticle(long articleId) {
DB.Article article =
db.findArticleById(
articleId
).orElse(
new DB.Article(articleId, "N/A")
);
List<DB.StoredArticle> storedArticles =
db.findAllStoredArticlesForArticleId(articleId);
return new LineItem(
article.getArticleId(),
article.getName(),
getStoredQuantitesForStoredArticles(storedArticles)
);
}
private List<StoredQuantity> getStoredQuantitesForStoredArticles(List<DB.StoredArticle> storedArticles) {
return storedArticles.stream()
.map(storedArticle ->
new StoredQuantity(
db.findStoreById(storedArticle.getStoreId())
.map(DB.Store::getName)
.orElse("No Store"),
storedArticle.getQuantity()
)
)
.sorted()
.collect(
Collectors.toList()
);
}
}
Макет класса базы данных
Просто простое представление класса базы данных в памяти,В реальном приложении вы, вероятно, использовали бы что-то вроде SpringData с hibernate, чтобы предоставить репозитории доступа к данным, использующие реляционное отображение объектов на основе JPA.
Классы базы данных вообще не связаны с представлением, а простопредставлены здесь, так что работающее приложение может быть создано в рамках стиля MVVM.
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
class DB {
private static final DB instance = new DB();
public static DB instance() {
return instance;
}
private List<Article> articles = List.of(
new Article(1, "Hp101"),
new Article(3, "Lenovo303"),
new Article(4, "Asus404")
);
private List<Store> stores = List.of(
new Store(1, "S1"),
new Store(2, "S2")
);
private List<StoredArticle> storedArticles = List.of(
new StoredArticle(1, 1, 30),
new StoredArticle(1, 2, 70),
new StoredArticle(3, 1, 50),
new StoredArticle(4, 2, 70)
);
public Optional<Article> findArticleById(long articleId) {
return articles.stream()
.filter(article -> article.getArticleId() == articleId)
.findFirst();
}
public Optional<Store> findStoreById(long storeId) {
return stores.stream()
.filter(store -> store.getStoreId() == storeId)
.findFirst();
}
public List<StoredArticle> findAllStoredArticlesForArticleId(long articleId) {
return storedArticles.stream()
.filter(storedArticle -> storedArticle.articleId == articleId)
.collect(Collectors.toList());
}
public List<Article> findAllArticles() {
return Collections.unmodifiableList(articles);
}
static class Article {
private long articleId;
private String name;
public Article(long articleId, String name) {
this.articleId = articleId;
this.name = name;
}
public long getArticleId() {
return articleId;
}
public String getName() {
return name;
}
}
static class Store {
private long storeId;
private String name;
public Store(long storeId, String name) {
this.storeId = storeId;
this.name = name;
}
public long getStoreId() {
return storeId;
}
public String getName() {
return name;
}
}
static class StoredArticle {
private long articleId;
private long storeId;
private int quantity;
public StoredArticle(long articleId, long storeId, int quantity) {
this.articleId = articleId;
this.storeId = storeId;
this.quantity = quantity;
}
public long getArticleId() {
return articleId;
}
public long getStoreId() {
return storeId;
}
public int getQuantity() {
return quantity;
}
}
}
Ответы на некоторые дополнительные вопросы
Какой подход являетсялучше всего обновлять данные?
Все показанные мной подходы используют модели данных и представления только для чтения.Чтобы сделать это чтение-запись, было бы немного больше работы (и выход за рамки того, что я был бы готов добавить к этому уже длинному ответу).Вероятно, из двух подходов, описанных выше, подход, который использует отдельную строку для каждого хранилища, содержащего элемент, будет проще всего адаптировать для обновления данных.
Какой подход в общем случае следует использоватьобновить данные (данные хранятся точно в БД)?
Определение общего подхода к обновлению данных в базе данных выходит за рамки того, что я бы ответил здесь (это ответ, основанный исключительно на мнениях), так как есть много разных способов сделать это, и, следовательно, это не по теме для StackOverflow).Если бы это был я, я бы создал службу отдыха на основе SpringBoot, которая подключалась к базе данных, и мое клиентское приложение связывалось бы с этим.Если приложению не нужно обмениваться данными через Интернет, а нужно обмениваться данными только с локальной БД через ЛВС, я бы использовал добавление прямого доступа к базе данных, сделав приложение SpringBoot и используя репозитории Spring Data со встроенной базой данных H2..
При изменении в определенной строке изменяются в БД или ждут, пока пользователь изменит всю таблицу, и не нажмут кнопку сохранения?
В любом случае будет работать,У меня нет сильного мнения относительно одного против другого.Вероятно, я бы склонялся к сценарию немедленного обновления, а не к отложенному сохранению, но это будет зависеть от приложения и желаемого пользовательского опыта.
Пожалуйста, не могли бы вы предоставить мне какой-нибудь код для рисованиястрока под каждой ячейкой или чтобы сделать ее такой же, как обычный tableView (одна строка серая, а другая нет и т. д.)
Вы можете задать это как отдельный вопрос.Но, в общем, используйте CSS-стиль .Если вы воспользуетесь вторым подходом, описанным выше, который имеет строку на магазин, то все уже является «обычным табличным представлением» с точки зрения стилевого оформления, с одной строкой серого цвета, а одной строкой нет и т. Д., Поэтому я не знаю, какие дополнительныестиль действительно требуется в таком случае.