Настройте TableCell в табличном представлении в javafx - PullRequest
0 голосов
/ 20 сентября 2019

Давайте рассмотрим следующую информацию:

enter image description here

Как видите, статья может храниться во многих магазинах, и наоборот: магазин может хранить много статей: этомодель класса (UML)

некоторый код: FXML Part:

 @FXML
     private  TableView<Article> tblArticles;
    @FXML
    private TableColumn<Article, String> colStore;

    @FXML
    private TableColumn<Article, Integer> colQuantity;

методы получения и установки:

colStore.setCellValueFactory(new PropertyValueFactory<>("store"));
colStore.setCellValueFactory(new PropertyValueFactory<>("quantity"));

Я получаю результат, показанный в первой таблице, ноЯ не могу сделать то, что во второй таблице.enter image description here

И что я хочу точно, должен дать следующую информацию:

enter image description here

Так что мой вопросМожно ли сделать это в TableView?

1 Ответ

3 голосов
/ 21 сентября 2019

Вот пример приложения.Это следует за MVVM стиль , который подходит для этого вида работы.Приложение было построено с использованием Java 13 и не будет работать в более ранних версиях Java, таких как Java 8. Это довольно длинный ответ, но, ну да, иногда это то, что нужно.

Общий подход - не для создания строки табличного представления для каждого магазина, в котором хранится статья. Вместо этого мы просто создаем строку для каждой статьи, и у нас есть пользовательское средство визуализации ячеек, которое создает одну отформатированную ячейку для всех магазинов и количеств, которыеэтот элемент хранится в.

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

Еще один способ сделать это -использовать вложенные столбцы.При таком подходе действительно позволяет вам создать строку таблицы для каждого магазина, в котором хранится статья. Если вы сделаете это, вам потребуется какой-либо способ заполнения разных данных в зависимости от того,строка является либо первой строкой в ​​группе, либо нет.Вы не позволяете пользователю переупорядочивать и сортировать данные в таблице, так как это будет довольно сложно обслуживать, потому что понятие «первая строка в группе» будет меняться навсегда.Чтобы обеспечить соответствующий рендеринг с вложенными столбцами, вы получите немного другую модель представления (класс FlatLineItem ниже и сопровождающий метод в LineItemService, который их извлекает).

На рисунке ниже показановывод TableView с пользовательским средством визуализации ячеек слева и TableView с использованием вложенных столбцов справа.Обратите внимание, как выбор работает по-разному в каждом случае.Слева, когда выбрана строка, она включает все хранилища, которые прикреплены к этой строке.Справа, когда используются вложенные столбцы, при выборе строки выбирается только строка для данного магазина.

sample app

Класс основного приложения

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...