JavaFX - поиск строки в табличном представлении по столбцу - PullRequest
0 голосов
/ 20 января 2019

У меня есть просмотр таблицы и возможность поиска по одному столбцу, например,

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

Ответы [ 2 ]

0 голосов
/ 21 января 2019

enter image description here

Вот черновой пример, в котором поиск работает по логике AND, т. Е. Все поисковые тексты учитываются.

Предположим, у вас есть люди в качестве модели данных:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

import javafx.beans.property.SimpleStringProperty;

class Person {
    private final SimpleStringProperty firstName;
    private final SimpleStringProperty lastName;
    private final SimpleStringProperty email;

    Person(String fName, String lName, String email) {
        this.firstName = new SimpleStringProperty(fName);
        this.lastName = new SimpleStringProperty(lName);
        this.email = new SimpleStringProperty(email);
    }

    String getFirstName() {
        return firstName.get();
    }

    String getLastName() {
        return lastName.get();
    }

    String getEmail() {
        return email.get();
    }
}

Стол выполнен в виде:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051.MyCellValueFactory?>

<AnchorPane prefHeight="400.0"
            prefWidth="600.0"
            xmlns="http://javafx.com/javafx/8.0.171"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051.Controller">
    <TableView fx:id="tableView" prefWidth="600">
        <columnResizePolicy>
            <TableView fx:constant="UNCONSTRAINED_RESIZE_POLICY"/>
        </columnResizePolicy>

        <columns>
            <TableColumn text="First Name">
                <cellValueFactory>
                    <MyCellValueFactory property="firstName"/>
                </cellValueFactory>
            </TableColumn>

            <TableColumn text="Last Name">
                <cellValueFactory>
                    <MyCellValueFactory property="lastName"/>
                </cellValueFactory>
            </TableColumn>

            <TableColumn text="E-mail">
                <cellValueFactory>
                    <MyCellValueFactory property="email"/>
                </cellValueFactory>
            </TableColumn>
        </columns>
    </TableView>
</AnchorPane>

У нас есть пользовательские классы строк таблицы.

MyTableRowData:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

public abstract class MyTableRowData<T> {
    public abstract T firstNameProperty();

    public abstract T lastNameProperty();

    public abstract T emailProperty();
}

SearchTableRowData:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class SearchTableRowData extends MyTableRowData<ObjectProperty<String>> {
    private final ObjectProperty<String> firstName = new SimpleObjectProperty<>();
    private final ObjectProperty<String> lastName = new SimpleObjectProperty<>();
    private final ObjectProperty<String> email = new SimpleObjectProperty<>();

    @Override
    public ObjectProperty<String> firstNameProperty() {
        return firstName;
    }

    @Override
    public ObjectProperty<String> lastNameProperty() {
        return lastName;
    }

    @Override
    public ObjectProperty<String> emailProperty() {
        return email;
    }
}

PersonTableRowData:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

class PersonTableRowData extends MyTableRowData<ObjectProperty<String>> {
    private final ObjectProperty<String> firstName;
    private final ObjectProperty<String> lastName;
    private final ObjectProperty<String> email;

    PersonTableRowData(final Person person) {
        this.firstName = new SimpleObjectProperty<>(person.getFirstName());
        this.lastName = new SimpleObjectProperty<>(person.getLastName());
        this.email = new SimpleObjectProperty<>(person.getEmail());
    }

    @Override
    public ObjectProperty<String> firstNameProperty() {
        return firstName;
    }

    @Override
    public ObjectProperty<String> lastNameProperty() {
        return lastName;
    }

    @Override
    public ObjectProperty<String> emailProperty() {
        return email;
    }
}

Класс MyCellValueFactory - это сердце всей магии:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

import java.lang.reflect.Method;

import javafx.beans.NamedArg;
import javafx.beans.Observable;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.text.Text;
import javafx.util.Callback;

public class MyCellValueFactory<P extends MyTableRowData<?>, S extends Node>
        implements Callback<TableColumn.CellDataFeatures<P, S>, ObservableValue<S>> {
    private final String property;

    public MyCellValueFactory(@NamedArg("property") final String property) {
        this.property = property;
    }

    @Override
    public ObservableValue<S> call(final TableColumn.CellDataFeatures<P, S> param) {
        final P tableRowData = param.getValue();

        if (tableRowData instanceof SearchTableRowData) {
            return new SimpleObjectProperty<>(buildSearchTextBox(tableRowData));
        } else if (tableRowData instanceof PersonTableRowData) {
            return new SimpleObjectProperty<>(buildText(tableRowData));
        }

        return new SimpleObjectProperty<>();
    }

    @SuppressWarnings("unchecked")
    private S buildText(final P tableRowData) {
        final Text text = new Text();
        text.textProperty().bind(extractProperty(tableRowData));

        return (S) text;
    }

    @SuppressWarnings("unchecked")
    private S buildSearchTextBox(final P tableRowData) {
        final TextField searchTextField = new TextField();
        searchTextField.promptTextProperty().set(property);
        searchTextField.textProperty().bindBidirectional(extractProperty(tableRowData));

        return (S) searchTextField;
    }

    @SuppressWarnings("unchecked")
    private <T extends Observable> T extractProperty(final P tableRowData) {
        try {
            final Class<?> c = Class.forName(tableRowData.getClass().getName());
            final Method method = c.getDeclaredMethod(property + "Property");

            return (T) method.invoke(tableRowData);
        } catch (final Exception exc) {
            throw new RuntimeException(exc);
        }
    }
}

Контроллер Класс:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javafx.beans.property.SimpleListProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableView;

import org.apache.commons.lang3.StringUtils;

public class Controller {
    @FXML
    private TableView<MyTableRowData<?>> tableView;

    private final ObservableValue<? extends ObservableList<MyTableRowData<?>>> observableRows =
            new SimpleListProperty<>(FXCollections.observableArrayList());

    private final SearchTableRowData searchTableRowData = new SearchTableRowData();

    private final List<Person> originalPersons = Arrays.asList(
            new Person("A", "B", "c@c.com"),
            new Person("AA", "BB", "cc@c.com"),
            new Person("AAA", "BBB", "ccc@c.com"),
            new Person("AAAA", "BBBB", "cccc@c.com"));

    @FXML
    void initialize() {
        tableView.itemsProperty().bind(observableRows);

        observableRows.getValue().add(searchTableRowData);

        searchTableRowData.firstNameProperty().addListener((o, oldValue, newValue) -> fillPersons());
        searchTableRowData.lastNameProperty().addListener((o, oldValue, newValue) -> fillPersons());
        searchTableRowData.emailProperty().addListener((o, oldValue, newValue) -> fillPersons());

        fillPersons();
    }

    private void fillPersons() {
        ((ObservableList) observableRows).remove(1, ((ObservableList) observableRows).size());

        observableRows
                .getValue()
                .addAll(originalPersons
                        .stream()
                        .filter(getFirstNamePredicate())
                        .filter(getLastNamePredicate())
                        .filter(getEmailPredicate())
                        .map(PersonTableRowData::new)
                        .collect(Collectors.toList()));
    }

    private Predicate<Person> getFirstNamePredicate() {
        final String value = searchTableRowData.firstNameProperty().get();
        return StringUtils.isNoneEmpty(value) ?
                person -> StringUtils.containsIgnoreCase(person.getFirstName(), value) :
                person -> true;
    }

    private Predicate<Person> getLastNamePredicate() {
        final String value = searchTableRowData.lastNameProperty().get();
        return StringUtils.isNoneEmpty(value) ?
                person -> StringUtils.containsIgnoreCase(person.getLastName(), value) :
                person -> true;
    }

    private Predicate<Person> getEmailPredicate() {
        final String value = searchTableRowData.emailProperty().get();
        return StringUtils.isNoneEmpty(value) ?
                person -> StringUtils.containsIgnoreCase(person.getEmail(), value) :
                person -> true;
    }
}

Наконец-то вы запускаете все вместе этим Main классом:

package com.dmaslenko.stackexchange.stackoverflow.javafx.q54280051;

import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import com.dmaslenko.stackexchange.stackoverflow.q54033646.CsvReader;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    @SuppressWarnings("Duplicates")
    public void start(final Stage stage) throws Exception {
        final URL fxmlFileResource =
                CsvReader.class.getResource("/com/dmaslenko/stackexchange/stackoverflow/javafx/q54280051/main.fxml");
        final FXMLLoader loader = new FXMLLoader(fxmlFileResource);
        final Parent rootPane = loader.load();

        loader.getController();

        final Scene scene = new Scene(rootPane, 600, 400);
        stage.setScene(scene);
        stage.show();
    }
}

Это черновой, но рабочий пример, без особой оптимизации.

Известные области для улучшения:

  1. любое изменение в полях поиска теряет фокус и курсор.
  2. первый ряд выбирается, но не должен.
0 голосов
/ 20 января 2019

Если вам нужна строка текстовых полей, по одному на столбец, вы можете разместить контейнер HBox над TableView и поместить экземпляры TextField в HBox. Чтобы получить правильные размеры, вы можете привязать ширину текстового поля (предпочтительно!) К соответствующей ширине столбца, а ширину HBox (предпочтительно!) С виджетом TableView:

hbox.prefWidthProperty().bind(tableview.widthProperty());
textfield1.prefWidthProperty().bind(col1.widthProperty());
textfield2.prefWidthProperty().bind(col2.widthProperty());

Довольно простой, но эффективный подход к фильтрации заключается в том, чтобы обернуть данные TableView в FilteredList и обновить предикат фильтра, если текст в одном из полей поиска изменяется:

ObservableList<Person> data = FXCollections.observableArrayList();
data.add(....); // add data ...

// wrap the data collection in a filterd list:
filteredList = new FilteredList<>(data);
filteredList.setPredicate(p -> true); // Initial: show all rows

// set the FilteredList as TableView data:
tableview.itemsProperty().set(filteredList);

// Create a changelistener for the search box:
// "dataObject" is an instance of your data class/entity etc.
 textfield1.textProperty().addListener((observable, oldValue, newValue) -> {
  updatePredicate();
});
textfield2.textProperty().addListener((observable, oldValue, newValue) -> {
  updatePredicate();
});

Если ваши столбцы не помещаются в области просмотра TabelView, то вы можете разместить поля поиска в заголовке столбца, т.е. установить TextFields в качестве графического свойства для TableColumn:

col1.setGraphic(textfield1);
col2.setGraphic(textfield2);

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

Метод обновления предиката фильтра:

private void updatePredicate() {
  filteredList.setPredicate((data) -> {
      boolean showItem = true;
      if (!textfield1.getText().isEmpty()) {
        showItem = showItem && (data.getField1().contains(textfield1.getText()));
      }
      if (!textfield2.getText().isEmpty()) {
        showItem = showItem && (data.getField2().contains(textfield2.getText()));
      }
  return showItem;
  });
}

Надеюсь, это поможет :-)

...