2

我确信拥有由数据库支持的 tableview 是一种常见的编程范例(在我们的例子中,JPA 2 使用 EclipseLink)。但事实证明,让 UI 正确是非常困难的。我对现代 UI 设计没有太多经验,我敢肯定这会导致我错过一个明显的解决方案。

我们的表应该允许用户插入、删除、进行更改,然后保存或丢弃更改集。需要验证插入和更改。我们目前通过提交更改、失败时回滚并重播数据库的一组更改(失败的更改除外)并保持 UI 不变,以便用户无需重新键入所有内容即可修复错误。

我们的 TableView 由从 JPA 源获得的数据的 ObservableList 支持。插入和删除非常简单。我们可以使用列表上的更改侦听器获取有关已添加到列表或删除的信息。但是,我还没有想出一种可靠的方法来检测对现有项目的更改。

当前的设计是由其他人完成的一项黑客工作,必须重新架构,这取决于 TableRow 焦点更改侦听器,但它非常不可靠。监视表更改、验证每个更改以及在更改无效时回滚数据库更改并将可见更改重新应用到表的常用方法是什么?

现有的示例应用程序会很棒,但我还没有找到任何支持事务的可用应用程序。除此之外,模型图将非常有帮助。

4

2 回答 2

1

您应该检查 Granite Data Services,这是一个在 JavaFX 中很好地集成了 JPA 功能的框架:https ://www.granitedataservices.com/

它可以处理延迟加载、实体冲突、JavaFX 端的 EJB 缓存、bean 验证、多个客户端上的数据同步……

您应该查看 github 上的示例,我认为这是一个很好的开始:https ://github.com/graniteds/shop-admin-javafx

有点复杂,但是 JPA 管理确实很复杂。我多年来一直在 Flex 项目中使用 Granite,并且可能会将它与 JavaFX 一起使用,它已经可以投入生产了。

于 2014-03-10T13:47:17.190 回答
1

理论上,您可以通过使用提取器创建列表来监视可观察列表中的项目的更改。这个想法是,对于 TableView,您指定一个 Callback,它为列表中的每个对象提供一组 Observables。列表将观察可观察对象,并且当现有项目对指定属性进行更改时,将通知任何在列表中注册的 ListChangeListeners(通过 wasUpdated() 返回 true 的更改)。

然而,这似乎只适用于 Java 8;JavaFX 2.2 的某个地方可能存在一个错误。

这是一个示例,基于通常的 TableView 示例。

import java.util.Arrays;
import java.util.List;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TableUpdatePropertyExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        final TableView<Person> table = new TableView<>();
        table.setEditable(true);
        final TableColumn<Person, String> firstNameCol = createTableColumn("First Name");
        final TableColumn<Person, String> lastNameCol = createTableColumn("Last Name");
        final TableColumn<Person, String> emailCol = createTableColumn("Email");
        table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));
        final List<Person> data = Arrays.asList(
                new Person("Jacob", "Smith", "jacob.smith@example.com"),
                new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person("Ethan", "Williams", "ethan.williams@example.com"),
                new Person("Emma", "Jones", "emma.jones@example.com"),
                new Person("Michael", "Brown", "michael.brown@example.com")
        );
        table.setItems(FXCollections.observableList(data, new Callback<Person, Observable[]>() {
            @Override
            public Observable[] call(Person person) {
                return new Observable[] {person.firstNameProperty(), person.lastNameProperty(), person.emailProperty()};
            }
        }));

        table.getItems().addListener(new ListChangeListener<Person>() {
            @Override
            public void onChanged(
                    javafx.collections.ListChangeListener.Change<? extends Person> change) {
                while (change.next()) {
                    if (change.wasUpdated()) {
                        Person updatedPerson = table.getItems().get(change.getFrom());
                        System.out.println(updatedPerson+" was updated");
                    }
                }
            }
        });
        final BorderPane root = new BorderPane();
        root.setCenter(table);
        final Scene scene = new Scene(root, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private TableColumn<Person, String> createTableColumn(String title) {
        TableColumn<Person, String> col = new TableColumn<>(title);
        col.setCellValueFactory(new PropertyValueFactory<Person, String>(makePropertyName(title)));
        col.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
        return col ;
    }

    private String makePropertyName(String text) {
        boolean first = true ;
        StringBuilder prop = new StringBuilder();
        for (String word : text.split("\\s")) {
            if (first) {
                prop.append(word.toLowerCase());
            } else {
                prop.append(Character.toUpperCase(word.charAt(0)));
                if (word.length() > 1) {
                    prop.append(word.substring(1));
                }
            }
            first=false ;
        }
        return prop.toString();
    }

    public static class Person {
        private final StringProperty firstName ;
        private final StringProperty lastName ;
        private final StringProperty email ;
        public Person(String firstName, String lastName, String email) {
            this.firstName = new SimpleStringProperty(this, "firstName", firstName);
            this.lastName = new SimpleStringProperty(this, "lastName", lastName);
            this.email = new SimpleStringProperty(this, "email", email);
        }
        public String getFirstName() {
            return firstName.get();
        }
        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }
        public StringProperty firstNameProperty() {
            return firstName ;
        }
        public String getLastName() {
            return lastName.get();
        }
        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }
        public StringProperty lastNameProperty() {
            return lastName ;
        }
        public String getEmail() {
            return email.get();
        }
        public void setEmail(String email) {
            this.email.set(email);
        }
        public StringProperty emailProperty() {
            return email ;
        }
        @Override
        public String toString() {
            return getFirstName() + " " + getLastName();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

对于验证,您可以考虑覆盖模型类属性中的 set 和 setValue 方法。我还没有尝试过,我怀疑您可能需要弄乱 TableCell 才能使其正常工作,但是类似于:

this.email = new StringPropertyBase(email) {

            final Pattern pattern = Pattern.compile("[a-zA-Z_0-9]+@[a-zA-Z0-9.]+");

            @Override
            public String getName() {
                return "email";
            }

            @Override
            public Object getBean() {
                return Person.this;
            }

            @Override
            public void set(String email) {
                if (pattern.matcher(email).matches()) {
                    super.set(email);
                }
            }

            @Override
            public void setValue(String email) {
                if (pattern.matcher(email).matches()) {
                    super.setValue(email);
                }
            }
        };

代替上面 Person 类中的一个衬垫。

更新:要在 JavaFX 2.2 中进行这项工作,您基本上可以推出自己的“提取器”版本。这有点工作,但还不错。像下面这样的东西似乎工作,例如:

final List<Person> data = Arrays.asList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com")
    );

    final ChangeListener<String> firstNameListener = new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> obs,
                String oldFirstName, String newFirstName) {
            Person person = (Person)((StringProperty)obs).getBean();
            System.out.println("First name for "+person+" changed from "+oldFirstName+" to "+newFirstName);
        }
    };

    final ChangeListener<String> lastNameListener = new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> obs,
                String oldLastName, String newLastName) {
            Person person = (Person)((StringProperty)obs).getBean();
            System.out.println("Last name for "+person+" changed from "+oldLastName+" to "+oldLastName);
        }
    };

    final ChangeListener<String> emailListener = new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> obs,
                String oldEmail, String newEmail) {
            Person person = (Person)((StringProperty)obs).getBean();
            System.out.println("Email for "+person+" changed from "+oldEmail+" to "+oldEmail);
        }
    };

    table.getItems().addListener(new ListChangeListener<Person>() {
        @Override
        public void onChanged(
                javafx.collections.ListChangeListener.Change<? extends Person> change) {
            while (change.next()) {
                for (Person person : change.getAddedSubList()) {
                    person.firstNameProperty().addListener(
                            firstNameListener);
                    person.lastNameProperty().addListener(lastNameListener);
                    person.emailProperty().addListener(emailListener);
                }
                for (Person person : change.getRemoved()) {
                    person.firstNameProperty().removeListener(
                            firstNameListener);
                    person.lastNameProperty().removeListener(
                            lastNameListener);
                    person.emailProperty().removeListener(emailListener);
                }
            }
        }
    });

    table.getItems().addAll(data);
于 2014-03-06T02:17:35.597 回答