javafx table view is not showing any data from database - javafx

I created a book management application via javafx. Through this app we can perform add, search and delete operations. For the search i am using Table view to get all the details of the book searched by the user. So in the end i am getting the result displayed in the console but not in the table view
package Managemnet;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import connection.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
public class Searching {
Connection connect;
#FXML
TableView<Book> search_view ;
#FXML
ObservableList<Book> observeList;
#FXML
TableColumn<Book, Integer> Book_I;
#FXML
TableColumn<Book, String> Book_n;
#FXML
TableColumn<Book, String> publish;
#FXML
TableColumn<Book, Integer> ye;
#FXML
TextField sea_Book_name;
#FXML
TextField sea_pub;
#FXML
TextField sea_year;
#FXML
AnchorPane SearchPane;
#FXML
Button search;
public void search() throws ClassNotFoundException, SQLException{
Sql_query sql = new Sql_query();
sql.connect_sql();
connect = sql.getConnect();
observeList = FXCollections.<Book>observableArrayList();
search_view = new TableView<Book>();
Book_I=new TableColumn<Book ,Integer>();
Book_I.setCellValueFactory(new PropertyValueFactory<Book ,Integer>
("Book_id"));
Book_n=new TableColumn<Book ,String>();
Book_n.setCellValueFactory(new PropertyValueFactory<Book ,String>
("Book_name"));
publish=new TableColumn<Book ,String>();
publish.setCellValueFactory(new PropertyValueFactory<Book ,String>
("Publisher"));
ye=new TableColumn<Book ,Integer>();
ye.setCellValueFactory(new PropertyValueFactory<Book ,Integer>("year"));
String s1= sea_Book_name.getText();
String s2= sea_pub.getText();
String s= sea_year.getText();
int s3;
if(!s.isEmpty()){
s3=Integer.parseInt(s);
}
else s3=0;
ResultSet res;
Answer_from_dataset obj= new Answer_from_dataset();
res=obj.getAnswer(s1, s2, s3);
while(res.next()){
observeList.add(new Book(res.getInt(1), res.getString(2),
res.getString(3), res.getInt(4)));
//System.out.println(observeList);
}
search_view.setItems(observeList);
System.out.println(search_view);
}
package Managemnet;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
public class Book {
SimpleStringProperty Book_name;
SimpleStringProperty Publisher;
SimpleIntegerProperty Book_id;
SimpleIntegerProperty year;
public Book(int book_id, String book_name, String publisher, int year) {
this.Book_name =new SimpleStringProperty(book_name);
this.Publisher =new SimpleStringProperty(publisher);
this.Book_id =new SimpleIntegerProperty (book_id);
this.year = new SimpleIntegerProperty(year);
}
public String getBook_name() {
return Book_name.get();
}
public String getPublisher() {
return Publisher.get();
}
public Integer getBook_id() {
return Book_id.get();
}
public Integer getYear() {
return year.get();
}
public void setBook_name(String book_name) {
Book_name.set(book_name);
}
public void setPublisher(String publisher) {
Publisher.set(publisher);;
}
public void setBook_id(int book_id) {
Book_id.set(book_id);;
}
public void setYear(int year) {
this.year.set(year);;
}
#Override
public String toString() {
return getBook_id() + " " + getBook_name() + " " + getPublisher() + " "
+ getYear();
}
}

So I ran into this issue with FXML when I did my first project with JavaFX.
I tried a lot of solutions with FXML but it took time and nothing worked. So I shifted over to using POJO's. This may not be ideally what you're looking for but it would give you a pretty good amount of flexibility with creating your table.
Ideally for the table itself you want to create a TableView
TableView<Book> table = new TableView();
Then for each column you want to create a TableColumn.. Now here you need to be careful
TableColumn<Book, String> col1 = new TableColumn<>("Name");
col.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName()));
The values within the angle brackets is <Object Type, Data Type>
And then of course you can set your preferred width and what not. But the same process of creating TableColumns Repeats itself for the table you have. I get this might be a little more of lengthier process but its relatively easy to understand and most importantly it work.
Try this and let me know if it works! Cheers!!

Related

TestFX with JUnit4: How can I press the ENTER key in a certain TextField?

I want to test, if a certain TextField (maybe there are several TextFields) has an EventHandler set via setOnAction. In the test code I can set the content (e.g. "HelloWorld") into the TextField. In my understanding I have to place the curser at the end of the text in the TextField and then, call press(KeyCode.ENTER). Is there a TestFX call to place the curser a a certain point within a certain TextField? Or is there another way to test this?
Regards, Jörg
[EDIT]
Here is an example:
package testestfx;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class SimpleGui extends VBox {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(SimpleGui.class);
private TextField field1;
private TextField field2;
private Label label;
public SimpleGui() {
field1 = new TextField("Textfield1");
field2 = new TextField("Textfield2");
field1.setOnAction((event) -> onActionDoThis());
field2.setOnAction((event) -> onActionDoThat());
label = new Label("Label");
getChildren().addAll(field1, field2 );
}
public TextField getField1() {
return field1;
}
public TextField getField2() {
return field2;
}
public Label getLabel() {
return label;
}
void onActionDoThis() {
logger.info("This");
label.setText("This");
}
void onActionDoThat() {
logger.info("That");
label.setText("That");
}
}
And here is the test:
package testtestfx;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.stage.Stage;
import org.junit.Test;
import org.testfx.framework.junit.ApplicationTest;
import testestfx.SimpleGui;
import static org.junit.Assert.assertEquals;
public class TestSimpleGUI extends ApplicationTest {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(TestSimpleGUI.class);
private SimpleGui gui;
#Override
public void start(final Stage stage) throws Exception {
super.start(stage);
gui = new SimpleGui();
stage.setScene(new Scene(gui));
stage.show();
}
#Test
public void testEnterKeystroke() {
TextField t2 = gui.getField2();
TextField t1 = gui.getField1();
Label l1 = gui.getLabel();
t2.setText("bar");
t1.setText("foo");
press(KeyCode.ENTER);
assertEquals("That", l1.getText());
}
}
The console output is:
15:43:29.366 [JavaFX Application Thread] [INFO] SimpleGui - This
org.junit.ComparisonFailure:
Expected :That
Actual :This
```
You can set the focus on a TextField like this:
myTextField.requestFocus();
kind regards
Amadeus

JavaFX Populating TableView by using ComboBox selction

I am trying to populate a TableView called CustomerTableViewusing a ComboBox called ComboBoxSelectCustomer. Basically the user selects a customer from the list of customers inside the ComboBox and than the TableView will populate with data that matches that customer's name. I have multiple tables in an SQL file with each customer but for some reason when I select a customer name from the combobox, nothing happens on the TableView, it just remains empty. There are no errors or any issues with the code, I just think that the way it is setup is whats causing the problem. Please review my code and advise me on how I can set these methods up in a better way to have the ComboBox trigger an SQL statement to populate the TableView
//MainController
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package supremeinkcalcmk2;
import java.net.URL;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javax.swing.DefaultComboBoxModel;
/**
* FXML Controller class
*
*/
public class MainController implements Initializable {
#FXML
public ComboBox<String> ComboBoxSelectCustomer;
#FXML
private TableView CustomerTableView;
#FXML
private TableColumn<BaseColor, String> BaseColor;
#FXML
private TableColumn<BaseColor, String> Price;
Connection connection;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
//Customer combo box
ComboBoxSelectCustomer.setOnAction(e -> System.out.println(ComboBoxSelectCustomer.getValue()));
buildDataComboBox();
buildDataTableView();
}
public void buildDataTableView() {
//viewtable db connect
ObservableList<BaseColor> dataCustomerViewTable = FXCollections.observableArrayList();
BaseColor.setCellValueFactory(new PropertyValueFactory<BaseColor, String>("BaseColor"));
Price.setCellValueFactory(new PropertyValueFactory<BaseColor, String>("Price"));
connection = SqlConnection.CustomerConnection();
try {
//problem
String SQL = "Select BaseColor, Price FROM " + ComboBoxSelectCustomer.getValue();
connection = SqlConnection.CustomerConnection();
ResultSet rs = connection.createStatement().executeQuery(SQL);
while (rs.next()) {
BaseColor BS = new BaseColor();
BS.BaseColor.set(rs.getString("BaseColor"));
BS.Price.set(rs.getString("Price"));
dataCustomerViewTable.add(BS);
}
CustomerTableView.setItems(dataCustomerViewTable);
} catch (Exception e) {
}
}
//combobox sql connection and fill data
public void buildDataComboBox() {
ObservableList<String> dataComboBox = FXCollections.observableArrayList();
connection = SqlConnection.CustomerConnection();
try {
String SQL = "Select Name From CustomerList";
ResultSet rs = connection.createStatement().executeQuery(SQL);
while (rs.next()) {
dataComboBox.add(rs.getString("Name"));
}
ComboBoxSelectCustomer.setItems(dataComboBox);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error Building ComboBox Data");
}
if (connection == null) {
System.exit(1);
System.out.println("Connection failed");
}
}
public boolean isDbConnected() {
try {
return connection.isClosed();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}
Created a setOnAction event on ComboBoxSelectionCustomer which allowed the TableView to populate whenever the user changes options on the ComboBox.
ComboBoxSelectCustomer.setOnAction((event) -> {
});

JavaFX TableView: copy text as rendered in cell

I want to implement copy functionality in a TableView. The text to be copied should be the actual text that is rendered in the cell, not the .toString version of the data model to be rendered, that is, it should be the .getText of the cell.
There are several ways of getting the data from a cell. However to get the rendered cell text contents, the procedure seems to be like this:
Get the cell data.
Get the cell factory.
Use the factory to create a cell.
Use the cell's updateItem method to render the data, then getText to get the rendered text.
The last step is not possible due to updateItem being protected.
How can I access the rendered text of any given cell in a TableView?
The process you outline involves getting the text (i.e. data) from the view (the cell), which violates the principles behind the MVC/MVP design. From a practical perspective, it involves creating UI elements (which are expensive to create) to essentially manipulate data (which is typically much less expensive to create and process). Additionally, depending on exactly what you're doing, the UI elements may impose additional threading constraints on your code (as they are essentially single-threaded).
If you need to use the "formatting text" functionality outside of the cell, you should factor it out elsewhere and reuse it in both the "copy" functionality you need and in the cell. At a minimum, this could be done by making the "format text" functionality part of the cell factory:
import java.util.function.Function;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class FormattingTableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private final Function<T, String> formatter ;
public FormattingTableCellFactory(Function<T, String> formatter) {
this.formatter = formatter ;
}
public FormattingTableCellFactory() {
this(T::toString);
}
public final Function<T, String> getFormatter() {
return formatter ;
}
#Override
public TableCell<S,T> call(TableColumn<S,T> col) {
return new TableCell<S,T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText(item == null ? null : formatter.apply(item));
}
};
}
}
(Obviously you could extend this to produce more sophisticated cells with graphical content, etc.)
And now your copy functionality can simply apply the formatter to the data, without reference to any actual cells. Here's a SSCCE:
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
private String copy(TableView<Product> table) {
StringBuilder sb = new StringBuilder();
for (Product p : table.getSelectionModel().getSelectedItems()) {
List<String> data = new ArrayList<>();
for (TableColumn<Product, ?> column : table.getColumns()) {
Function<Object, String> formatter = ((FormattingTableCellFactory) column.getCellFactory()).getFormatter();
data.add(formatter.apply(column.getCellObservableValue(p).getValue()));
}
sb.append(String.join("\t", data)).append("\n");
}
return sb.toString() ;
}
#Override
public void start(Stage primaryStage) {
TableView<Product> table = new TableView<>();
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.getColumns().add(column("Product", Product::nameProperty, String::toString));
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
table.getColumns().add(column("Price", Product::priceProperty, currencyFormat::format));
Random rng = new Random();
for (int i = 1; i <= 100; i++) {
table.getItems().add(new Product("Product "+i, rng.nextDouble()*100));
}
Button copy = new Button("Copy");
copy.setOnAction(e -> System.out.println(copy(table)));
copy.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));
BorderPane root = new BorderPane(table);
BorderPane.setAlignment(copy, Pos.CENTER);
BorderPane.setMargin(copy, new Insets(10));
root.setBottom(copy);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S,ObservableValue<T>> property, Function<T,String> formatter) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(new FormattingTableCellFactory<>(formatter));
return col ;
}
public static class Product {
private final StringProperty name = new SimpleStringProperty();
private final DoubleProperty price = new SimpleDoubleProperty() ;
public Product(String name, double price) {
setName(name);
setPrice(price);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final DoubleProperty priceProperty() {
return this.price;
}
public final double getPrice() {
return this.priceProperty().get();
}
public final void setPrice(final double price) {
this.priceProperty().set(price);
}
}
public static void main(String[] args) {
launch(args);
}
}
You can get rid of the less typesafe code at the expense of less flexibility:
private final Function<String, String> defaultFormatter = Function.identity() ;
private final Function<Number, String> priceFormatter = DecimalFormat.getCurrencyInstance()::format ;
private String copy(TableView<Product> table) {
return table.getSelectionModel().getSelectedItems().stream().map(product ->
String.format("%s\t%s",
defaultFormatter.apply(product.getName()),
priceFormatter.apply(product.getPrice()))
).collect(Collectors.joining("\n"));
}
and
table.getColumns().add(column("Product", Product::nameProperty, defaultFormatter));
table.getColumns().add(column("Price", Product::priceProperty, priceFormatter));

Trying to set text of a textField with no luck

I'm trying to read data from a database and set it as text in a textfield when I click a button. I can't for the life of me figure out why this code doesn't work. Any help is appreciated. Label works, and textfield doesn't. They're in the same anchor pane.
Here's the code from my FXMLcontroller.java file. I used SceneBuilder to create the UI.
package winfin_test;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
/**
*
* #author Sam
*/
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
private TextField textField1 = new TextField();
#FXML
private void handleButtonData(ActionEvent event) {
try {
//Connect to the database
String host = "jdbc:mysql://localhost:3306/my_database";
String uName = "root";
String uPass = "data";
Connection con = DriverManager.getConnection(host, uName, uPass);
//Execute some SQL and load the records into the resultset
Statement stmt = con.createStatement();
String SQL = "Select * FROM data_test";
ResultSet rs = stmt.executeQuery(SQL);
//Move the cursor to the first record and get data
rs.next();
int id_col = rs.getInt("Auto_ID");
String id = Integer.toString(id_col);
String first = rs.getString("FirstName");
String last = rs.getString("LastName");
String dob = rs.getString("Birthday");
String phone = rs.getString("Phone");
//Display the first record in the text fields
label.setText(first);
textField1.setText(last);
}
catch (SQLException err) {
System.out.println(err.getMessage());
}
System.out.println("You clicked me!");
//label.setText("Well Done!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
The problem is that you never add the textField to your scene, you have the #FXML label for your Label, but then the textField you are trying to create dynamically, but never displaying. Instead, define the textfield in your .fxml document, and then edit your code to the following:
package winfin_test;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
/**
*
* #author Sam
*/
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private TextField textField1;
#FXML
private void handleButtonData(ActionEvent event) {
try {
//Connect to the database
String host = "jdbc:mysql://localhost:3306/my_database";
String uName = "root";
String uPass = "data";
Connection con = DriverManager.getConnection(host, uName, uPass);
//Execute some SQL and load the records into the resultset
Statement stmt = con.createStatement();
String SQL = "Select * FROM data_test";
ResultSet rs = stmt.executeQuery(SQL);
//Move the cursor to the first record and get data
rs.next();
int id_col = rs.getInt("Auto_ID");
String id = Integer.toString(id_col);
String first = rs.getString("FirstName");
String last = rs.getString("LastName");
String dob = rs.getString("Birthday");
String phone = rs.getString("Phone");
//Display the first record in the text fields
label.setText(first);
textField1.setText(last);
}
catch (SQLException err) {
System.out.println(err.getMessage());
}
System.out.println("You clicked me!");
//label.setText("Well Done!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
I know it seems silly that you need to write #FXML before every single variable declaration that you are linking to an fx:id, but that's just the way it is. If you have multiple variables of the same type, (eg: a group of Labels) you only need to put it once and separate then with commas, like so:
#FXML
Label label1, label2, label3, label4;
Which saves you a bit of code.

Populate TableView with ObservableMap JavaFX

I wanted to know if it is possible to use a ObservableMap to populate a TableView ?
I use ObservableMap instead of ObservableList because I need to add and delete often, so I need to minimize the cost.
My hashMap use an BigInteger as key field and a type with many properties as value field.
In my tableView I just want to display the values with a column per properties. I hope that is clear
Thanks
I've been trying to do this. I guess the post is old but I don't see any answers anywhere on the net. The examples use the map key for columns and then a list of maps for every row. I'd like to see the rows as keys and their associated values. It's a long example.
package tablemap;
import static java.lang.Math.random;
import java.util.Map;
import java.util.TreeMap;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableMap extends Application {
#Override
public void start(Stage primaryStage) {
VBox root = new VBox();
Map<String,LineItem> mapData = new TreeMap<>();
for (int i = 0; i < 3; i++)
mapData.put(String.valueOf(random()), new LineItem(String.valueOf(i),"i"));
ObservableList<Map.Entry<String,LineItem>> listData =
FXCollections.observableArrayList(mapData.entrySet());
TableView<Map.Entry<String,LineItem>> tv = new TableView(listData);
TableColumn<Map.Entry<String,LineItem>,String> keyCol = new TableColumn("Key");
keyCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getKey()));
TableColumn<Map.Entry<String,LineItem>,String> lineNoCol = new TableColumn("Line No");
lineNoCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getValue().getLineNo()));
TableColumn<Map.Entry<String,LineItem>,String> descCol = new TableColumn("Desc");
descCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getValue().getDesc()));
descCol.setCellFactory(TextFieldTableCell.forTableColumn());
descCol.setOnEditCommit((CellEditEvent<Map.Entry<String,LineItem>, String> t) -> {
t.getTableView().getItems().get(t.getTablePosition().getRow())
.getValue().setDesc(t.getNewValue());
});
tv.getColumns().addAll(keyCol,lineNoCol, descCol);
tv.setEditable(true);
tv.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Button btnOut = new Button("out");
btnOut.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
for (Map.Entry<String,LineItem> me : mapData.entrySet()){
System.out.println("key "+me.getKey()+" entry "+me.getValue().toCSVString());
}
for (Map.Entry<String,LineItem> me : listData){
System.out.println("key "+me.getKey()+" entry "+me.getValue().toCSVString());
}
}
});
root.getChildren().addAll(tv,btnOut);
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle("Map Table Test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
And the LineItem Class Code
package tablemap;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/* LineItem class */
public class LineItem {
private final StringProperty lineNo = new SimpleStringProperty();
private final StringProperty desc = new SimpleStringProperty();
public LineItem(String ln, String dsc) {
lineNo.set(ln); desc.set(dsc);
}
public String getLineNo() {return (lineNo.getValue() != null) ?lineNo.get():"";}
public void setLineNo(String lineNo) {this.lineNo.set(lineNo);}
public StringProperty lineNoProperty() {return lineNo;}
public String getDesc() {return (desc.getValue() != null) ?desc.get():"";}
public void setDesc(String desc) {this.desc.set(desc);}
public StringProperty descProperty() {return desc;}
public String toCSVString(){
return lineNo.getValueSafe()+","+
desc.getValueSafe()+"\n";
}
}
You can see after editing data and clicking out that changes in the list are reflected in the map. I still have to check the other way and handle insertions and deletions but that shouldn't be to hard.
I packaged up my Map Table listeners in a subclass of TableView.
package tablemap;
import java.util.AbstractMap;
import java.util.Map;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.scene.control.TableView;
public class MapTableView<K,V> extends TableView<Map.Entry<K,V>>{
private final ObservableList<Map.Entry<K,V>> obsList;
private final ObservableMap<K,V> map;
private final MapChangeListener<K,V> mapChange;
private final ListChangeListener<Map.Entry<K,V>> listChange;
public MapTableView(ObservableMap<K,V> map) {
this.map = map;
obsList = FXCollections.observableArrayList(map.entrySet());
setItems(obsList);
mapChange = new MapChangeListener<K, V>() {
#Override
public void onChanged(MapChangeListener.Change<? extends K, ? extends V> change) {
obsList.removeListener(listChange);
if (change.wasAdded())
obsList.add(new AbstractMap.SimpleEntry(change.getKey(),change.getValueAdded()));
if (change.wasRemoved()){
//obsList.remove(new AbstractMap.SimpleEntry(change.getKey(),change.getValueRemoved()));
// ^ doesn't work always, use loop instead
for (Map.Entry<K,V> me : obsList){
if (me.getKey().equals(change.getKey())){
obsList.remove(me);
break;
}
}
}
obsList.addListener(listChange);
}
};
listChange = (ListChangeListener.Change<? extends Map.Entry<K, V>> change) -> {
map.removeListener(mapChange);
while (change.next()){
//maybe check for uniqueness here
if (change.wasAdded()) for (Map.Entry<K, V> me: change.getAddedSubList())
map.put(me.getKey(),me.getValue());
if (change.wasRemoved()) for (Map.Entry<K, V> me: change.getRemoved())
map.remove(me.getKey());
}
map.addListener(mapChange);
};
map.addListener(mapChange);
obsList.addListener(listChange);
}
//adding to list should be unique
public void addUnique(K key, V value){
boolean isFound = false;
//if a duplicate key just change the value
for (Map.Entry<K,V> me : getItems()){
if (me.getKey().equals(key)){
isFound = true;
me.setValue(value);
break;//only first match
}
}
if (!isFound) // add new entry
getItems().add(new AbstractMap.SimpleEntry<>(key,value));
}
//for doing lenghty map operations
public void removeMapListener(){
map.removeListener(mapChange);
}
//for resyncing list to map after many changes
public void resetMapListener(){
obsList.removeListener(listChange);
obsList.clear();
obsList.addAll(map.entrySet());
obsList.addListener(listChange);
map.addListener(mapChange);
}
}
It seems to work so far. I create with the following code :
final ObservableMap<String, LineItem> obsMap = FXCollections.observableHashMap();
final MapTableView<String,LineItem> mtv = new MapTableView(obsMap);
You can even edit the keys.
final TableColumn<Map.Entry<String,LineItem>,String> keyCol = new TableColumn("Key");
keyCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getKey()));
keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
keyCol.setOnEditCommit((CellEditEvent<Map.Entry<String,LineItem>, String> t) -> {
final String oldKey = t.getOldValue();
final LineItem oldLineItem = obsMap.get(oldKey);
obsMap.remove(oldKey);//should remove from list but maybe doesn't always
obsMap.put(t.getNewValue(),oldLineItem);
});
You can see I added a method to remove and re add the map listeners. To add and remove 100k entries takes .65 secs w/out listeners and 5.2 secs with them.
Here's the whole thing in one file on pastebin. http://pastebin.com/NmdTURFt

Resources