2014-03-04 46 views
2

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

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

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

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

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

回答

1

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

但是,这似乎只适用于Java 8; JavaFX 2.2可能会存在一个bug。

下面是一个基于通常的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", "[email protected]"), 
       new Person("Isabella", "Johnson", "[email protected]"), 
       new Person("Ethan", "Williams", "[email protected]"), 
       new Person("Emma", "Jones", "[email protected]"), 
       new Person("Michael", "Brown", "[email protected]") 
     ); 
     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][email protected][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", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Michael", "Brown", "[email protected]") 
    ); 

    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); 
+0

(希望有帮助,顺便说一下,我可能错过了你的问题的重点......) –

+0

感谢您花时间修改示例以说明您所描述的内容。我曾尝试过类似的东西,它并不像我在JavaFX 2.2中所称的onChanged方法那样。我不确定该方法的意图是什么。我在调试器中运行了这段代码,但没有看到它到达那里。 Oracle文档的帮助有限,因为它没有详细介绍此功能。我们计划迁移到JavaFX 8,但可能来不及帮助解决这个问题。 – kithril

1

您应该检查花岗岩数据服务,这是一种具有的JPA结构内的JavaFX大整合的框架:https://www.granitedataservices.com/

它可以处理lazy-加载,实体冲突,JavaFX端的EJB缓存,bean验证,多客户端数据同步......

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

这是一个有点复杂,但JPA管理确实是复杂的。我已经在Flex项目中使用了Granite多年,可能会将它与JavaFX一起使用,它已经可以在生产环境中使用。

+0

我希望6个月前我们开始这个项目时就知道这个框架。它会让生活变得更简单。 – kithril