具有事务支持的JavaFX TableView

时间:2014-03-04 21:14:44

标签: transactions javafx-2 jpa-2.0 tableview observablelist

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

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

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

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

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

2 个答案:

答案 0 :(得分:1)

理论上,您可以通过使用extractor创建列表来监视可观察列表中的项目以进行更改。这个想法是,对于TableView,您指定一个Callback,它为列表中的每个对象提供一个Observable数组。列表将观察到observable,并且当现有项目对指定属性进行更改时,将通知任何通过列表注册的ListChangeListener(通过更改isUpdated()返回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);

答案 1 :(得分: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一起使用,它已经可以投入生产了。