2014-03-04 133 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