在 javafx 的 tableView 中自定义 TableCell
Customise TableCell in a tableView in javafx
假设我们有以下信息:
如您所见,一篇文章可以存储在许多商店中,反之亦然:一个商店可以存储许多文章:这就是 class 模型 (UML)
一些代码:
FXML 部分:
@FXML
private TableView<Article> tblArticles;
@FXML
private TableColumn<Article, String> colStore;
@FXML
private TableColumn<Article, Integer> colQuantity;
吸气剂和 setter :
colStore.setCellValueFactory(new PropertyValueFactory<>("store"));
colStore.setCellValueFactory(new PropertyValueFactory<>("quantity"));
我收到了第一个 table 中的结果,但我无法执行第二个 table 中的结果。
而我想要的应该提供以下信息:
所以我的问题是可以在 TableView 中执行此操作吗?
这是一个示例应用程序。它后面有一个MVVM style,适合这种工作。该应用程序是使用 Java 13 构建的,无法在 Java 早期版本(例如 Java 8)中运行。这是一个相对较长的答案,但是,嗯,有时这就是它所需要的。
总体方法是不是为存储文章的每个商店创建一个table视图行。相反,我们只为每篇文章创建一行我们有一个自定义单元格渲染器,它为存储该项目的所有商店和数量生成一个单一格式的单元格。
现在,您可以根据 custom rowFactory 进行替代实施。但是,我 不 推荐针对此特定任务的方法,因为我认为如果没有提供足够的价值,实施和维护会不必要地复杂。
另一种方法是使用嵌套列。如果采取适当的措施,这种方法 确实 允许您为存储文章的每个商店创建一个 table 视图行。如果这样做,您需要一些根据行是否是组中的第一行来填充不同数据的方法。您不允许用户对 table 中的数据重新排序和排序,因为这很难满足,因为 "first row in the group" 的概念将永远改变。为了允许使用嵌套列进行适当的呈现,您最终会得到一个略有不同的视图模型(下面的 FlatLineItem
class 以及 LineItemService
中检索它们的附带方法)。
下图展示了左侧带有自定义单元格渲染器的 TableView 和右侧使用嵌套列的 TableView 的输出。请注意选择在每种情况下的工作方式不同。在左侧选择一行时,它包括附加到该行的所有商店。右侧使用嵌套列时,行选择仅为给定商店选择行。
主应用程序class
这将设置几个 TableView。
对于第一个 table 视图,它所做的只是为要显示的每个元素创建一个包含一列的 TableView。所有数据都是使用标准 PropertyValueFactory
从 LineItem
视图模型 class 中提取的。稍有不同的是通过 StoredQuantityTableCell
为 StoredQuantity
字段自定义单元格渲染器,这将在稍后解释。
第二个视图使用 nested columns 并基于 FlatLineItem
视图模型 class 工作,也使用标准 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
视图模型 class 表示 table 中的一行。
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
一个视图模型class 表示商店名称和商店中物品的数量。 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);
}
}
平坦LineItem.java
视图模型 class 支持带有嵌套列的 table 视图。可以为存储文章的每个商店创建一个平面项目。
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)。请注意为嵌套列 table 视图构造 FlatLineItems 的 getFlatLineItemsForLineItem
如何知道它是一组行项目中的第一行并基于此适当地传播 FlatLineItem,留下一些值如果它们只是从组中的第一项开始重复,则为 null,这会导致显示清晰。
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()
);
}
}
模拟数据库class
只是数据库的简单内存表示 class。在真实的应用程序中,您可能会使用类似 SpringData with hibernate 的东西来提供使用基于 JPA 的对象到关系映射的数据访问存储库。
数据库 class 与视图完全无关,只是在此处显示,以便可以在 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;
}
}
}
一些后续问题的答案
Which Approach is the best for updating data ?
我展示的所有方法都使用只读数据模型和视图。使其可读写需要更多的工作(并且超出了我准备添加到这个已经很长的答案的范围)。可能,在上面概述的两种方法中,为每个包含商品的商店使用单独的行的方法最容易适应使数据更新table.
Which approach in general I should use to update data ( data are stored for sure in db) ?
定义更新数据库中数据的一般方法超出了我在这里要回答的范围(这纯粹是基于意见的答案,因为有许多不同的方法可以实现这一点,因此不在主题之列对于 Whosebug)。如果是我,我会设置一个连接到数据库的基于 Spring 引导的休息服务,并让我的客户端应用程序与之通信。如果该应用程序不需要通过 Internet 进行通信,而仅通过 LAN 与本地数据库进行通信,则通过使该应用程序成为 Spring 启动应用程序并使用 Spring 数据存储库来添加直接数据库访问我会使用嵌入式 H2 数据库。
Is when modifying in a specific row modify in db or wait until user modify in the whole tableview and click on a save button ?
任何一种方法都行,我对其中一个和另一个没有任何强烈的意见。我可能倾向于立即更新方案而不是延迟保存方案,但这取决于应用程序和所需的用户体验。
Please can you provide me with some code for either to draw a line under every cell or to make it just like usual tableView ( one row gray and one not etc ...)
您可以将其作为一个单独的问题提出。但是,一般来说,使用 CSS styling。如果您使用上面概述的第二种方法,每个商店有一行,那么就样式而言,一切都已经是 "usual tableView",一行是灰色,一行不是,等等,所以我不知道有没有在这种情况下确实需要额外的样式。
假设我们有以下信息:
如您所见,一篇文章可以存储在许多商店中,反之亦然:一个商店可以存储许多文章:这就是 class 模型 (UML)
一些代码: FXML 部分:
@FXML
private TableView<Article> tblArticles;
@FXML
private TableColumn<Article, String> colStore;
@FXML
private TableColumn<Article, Integer> colQuantity;
吸气剂和 setter :
colStore.setCellValueFactory(new PropertyValueFactory<>("store"));
colStore.setCellValueFactory(new PropertyValueFactory<>("quantity"));
我收到了第一个 table 中的结果,但我无法执行第二个 table 中的结果。
而我想要的应该提供以下信息:
所以我的问题是可以在 TableView 中执行此操作吗?
这是一个示例应用程序。它后面有一个MVVM style,适合这种工作。该应用程序是使用 Java 13 构建的,无法在 Java 早期版本(例如 Java 8)中运行。这是一个相对较长的答案,但是,嗯,有时这就是它所需要的。
总体方法是不是为存储文章的每个商店创建一个table视图行。相反,我们只为每篇文章创建一行我们有一个自定义单元格渲染器,它为存储该项目的所有商店和数量生成一个单一格式的单元格。
现在,您可以根据 custom rowFactory 进行替代实施。但是,我 不 推荐针对此特定任务的方法,因为我认为如果没有提供足够的价值,实施和维护会不必要地复杂。
另一种方法是使用嵌套列。如果采取适当的措施,这种方法 确实 允许您为存储文章的每个商店创建一个 table 视图行。如果这样做,您需要一些根据行是否是组中的第一行来填充不同数据的方法。您不允许用户对 table 中的数据重新排序和排序,因为这很难满足,因为 "first row in the group" 的概念将永远改变。为了允许使用嵌套列进行适当的呈现,您最终会得到一个略有不同的视图模型(下面的 FlatLineItem
class 以及 LineItemService
中检索它们的附带方法)。
下图展示了左侧带有自定义单元格渲染器的 TableView 和右侧使用嵌套列的 TableView 的输出。请注意选择在每种情况下的工作方式不同。在左侧选择一行时,它包括附加到该行的所有商店。右侧使用嵌套列时,行选择仅为给定商店选择行。
主应用程序class
这将设置几个 TableView。
对于第一个 table 视图,它所做的只是为要显示的每个元素创建一个包含一列的 TableView。所有数据都是使用标准 PropertyValueFactory
从 LineItem
视图模型 class 中提取的。稍有不同的是通过 StoredQuantityTableCell
为 StoredQuantity
字段自定义单元格渲染器,这将在稍后解释。
第二个视图使用 nested columns 并基于 FlatLineItem
视图模型 class 工作,也使用标准 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
视图模型 class 表示 table 中的一行。
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
一个视图模型class 表示商店名称和商店中物品的数量。 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);
}
}
平坦LineItem.java
视图模型 class 支持带有嵌套列的 table 视图。可以为存储文章的每个商店创建一个平面项目。
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)。请注意为嵌套列 table 视图构造 FlatLineItems 的 getFlatLineItemsForLineItem
如何知道它是一组行项目中的第一行并基于此适当地传播 FlatLineItem,留下一些值如果它们只是从组中的第一项开始重复,则为 null,这会导致显示清晰。
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()
);
}
}
模拟数据库class
只是数据库的简单内存表示 class。在真实的应用程序中,您可能会使用类似 SpringData with hibernate 的东西来提供使用基于 JPA 的对象到关系映射的数据访问存储库。
数据库 class 与视图完全无关,只是在此处显示,以便可以在 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;
}
}
}
一些后续问题的答案
Which Approach is the best for updating data ?
我展示的所有方法都使用只读数据模型和视图。使其可读写需要更多的工作(并且超出了我准备添加到这个已经很长的答案的范围)。可能,在上面概述的两种方法中,为每个包含商品的商店使用单独的行的方法最容易适应使数据更新table.
Which approach in general I should use to update data ( data are stored for sure in db) ?
定义更新数据库中数据的一般方法超出了我在这里要回答的范围(这纯粹是基于意见的答案,因为有许多不同的方法可以实现这一点,因此不在主题之列对于 Whosebug)。如果是我,我会设置一个连接到数据库的基于 Spring 引导的休息服务,并让我的客户端应用程序与之通信。如果该应用程序不需要通过 Internet 进行通信,而仅通过 LAN 与本地数据库进行通信,则通过使该应用程序成为 Spring 启动应用程序并使用 Spring 数据存储库来添加直接数据库访问我会使用嵌入式 H2 数据库。
Is when modifying in a specific row modify in db or wait until user modify in the whole tableview and click on a save button ?
任何一种方法都行,我对其中一个和另一个没有任何强烈的意见。我可能倾向于立即更新方案而不是延迟保存方案,但这取决于应用程序和所需的用户体验。
Please can you provide me with some code for either to draw a line under every cell or to make it just like usual tableView ( one row gray and one not etc ...)
您可以将其作为一个单独的问题提出。但是,一般来说,使用 CSS styling。如果您使用上面概述的第二种方法,每个商店有一行,那么就样式而言,一切都已经是 "usual tableView",一行是灰色,一行不是,等等,所以我不知道有没有在这种情况下确实需要额外的样式。