JavaFx: I need something similar to this but for a TableView instead of a ListView - tableview

I took a stab at this yesterday but the TableView documentation has me a bit confused. After working on it for a couple of hours I gave up. Just wondering if any javafx experts out there can help me with this. I want to update a TableView in a background thread periodically when items in my database change.
Rather than post my entire application I have tried to break it down to a simple example. Replace all occurrences of ListView with TableView and ....
Then what?
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* An example of triggering a JavaFX ListView when an item is modified.
*
* Displays a list of strings. It iterates through the strings adding
* exclamation marks with 2 second pauses in between. Each modification is
* accompanied by firing an event to indicate to the ListView that the value
* has been modified.
*
* #author Mark Fashing
*/
public class ListViewTest extends Application {
/**
* Informs the ListView that one of its items has been modified.
*
* #param listView The ListView to trigger.
* #param newValue The new value of the list item that changed.
* #param i The index of the list item that changed.
*/
public static <T> void triggerUpdate(ListView<T> listView, T newValue, int i) {
EventType<? extends ListView.EditEvent<T>> type = ListView.editCommitEvent();
Event event = new ListView.EditEvent<>(listView, type, newValue, i);
listView.fireEvent(event);
}
#Override
public void start(Stage primaryStage) {
// Create a list of mutable data. StringBuffer works nicely.
final List<StringBuffer> listData = Stream.of("Fee", "Fi", "Fo", "Fum")
.map(StringBuffer::new)
.collect(Collectors.toList());
final ListView<StringBuffer> listView = new ListView<>();
listView.getItems().addAll(listData);
final StackPane root = new StackPane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root));
primaryStage.show();
// Modify an item in the list every 2 seconds.
new Thread(() -> {
IntStream.range(0, listData.size()).forEach(i -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(listData.get(i));
Platform.runLater(() -> {
// Where the magic happens.
listData.get(i).append("!");
triggerUpdate(listView, listData.get(i), i);
});
});
}).start();
}
public static void main(String[] args) {
launch(args);
}
}
Here is my first attempt:
Create a person class....
package org.pauquette.example;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleStringProperty email;
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getEmail() {
return email.get();
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public void setEmail(String fName) {
email.set(fName);
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public void setLastName(String fName) {
lastName.set(fName);
}
}
Create an extremely simple model class...
package org.pauquette.example;
import javafx.collections.*;
public class PeopleModel {
private ObservableList<Person> people=FXCollections.observableArrayList(
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")
);
public ObservableList<Person> getPeople() {
return people;
}
}
Now create a TableView of just firstName and build the columns then update the firstName every 2 seconds.......
package org.pauquette.example;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.application.Platform;
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.layout.StackPane;
import javafx.stage.Stage;
/**
* An example of triggering a JavaFX TableView when an item is modified.
*
* Displays a list of strings. It iterates through the strings adding
* exclamation marks with 2 second pauses in between. Each modification is
* accompanied by firing an event to indicate to the TableView that the value
* has been modified.
*
* #author Mark Fashing-Modified for TableView by Bryan Pauquette
*/
public class TableViewTest extends Application {
/*
public static <T> void triggerUpdate(TableView<T> listView, T newValue, int i) {
EventType<? extends TableView.EditEvent<T>> type = TableView.editCommitEvent();
Event event = new TableView.EditEvent<>(listView, type, newValue, i);
listView.fireEvent(event);
}*/
#Override
public void start(Stage primaryStage) {
final TableView<Person> listView = new TableView<Person>();
final PeopleModel model=new PeopleModel();
final ObservableList<Person> listData=model.getPeople();
listView.getItems().addAll(listData);
final StackPane root = new StackPane();
buildColumns(listView,listData);
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root));
primaryStage.show();
// Modify an item in the list every 2 seconds.
new Thread(() -> {
IntStream.range(0, listData.size()).forEach(i -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(listData.get(i));
Platform.runLater(() -> {
// Where the magic happens.
Person p=listData.get(i);
p.setFirstName(new StringBuilder(p.getFirstName()).append("!").toString());
//triggerUpdate(listView, listData.get(i), i);
});
});
}).start();
}
private void buildColumns(TableView<Person> listView,ObservableList<Person> listData) {
TableColumn<Person, String> dataCol = new TableColumn<Person, String>("First Name");
dataCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
listView.getColumns().add(dataCol);
listView.setItems(listData);
}
public static void main(String[] args) {
launch(args);
}
}
The method I am struggling with is triggerUpdate.....
I want the firstName column to get updated in the view with an appended exclamation point every 2 seconds just like in the original simple list view.

Here is working code.....
package org.pauquette.example;
import java.util.Observable;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
public class Person extends Observable {
private final SimpleStringProperty email;
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getEmail() {
return email.get();
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public void setEmail(String emailIn) {
email.setValue(emailIn);
setChanged();
notifyObservers(email);
}
public void setFirstName(String fNameIn) {
firstName.setValue(fNameIn);
setChanged();
notifyObservers(firstName);
}
public void setLastName(String lNameIn) {
lastName.setValue(lNameIn);
setChanged();
notifyObservers();
}
public ObservableValue<String> firstNameProperty() {
return firstName;
}
}
And......
package org.pauquette.example;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
* An example of triggering a JavaFX TableView when an item is modified.
*
* Displays a list of strings. It iterates through the strings adding
* exclamation marks with 2 second pauses in between. Each modification is
* accompanied by firing an event to indicate to the TableView that the value
* has been modified.
*
* #author Mark Fashing-Modified for TableView by Bryan Pauquette
*/
public class TableViewTest extends Application {
/*
public static <T> void triggerUpdate(TableView<T> listView, T newValue, int i) {
EventType<? extends TableView.EditEvent<T>> type = TableView.editCommitEvent();
Event event = new TableView.EditEvent<>(listView, type, newValue, i);
listView.fireEvent(event);
}*/
#Override
public void start(Stage primaryStage) {
final TableView<Person> listView = new TableView<Person>();
final PeopleModel model=new PeopleModel();
final ObservableList<Person> listData=model.getPeople();
/* int row=0;
for (Person p : listData) {
p.addObserver(new PersonObserver(listView,row));
row++;
}*/
listView.getItems().addAll(listData);
final StackPane root = new StackPane();
buildColumns(listView,listData);
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root));
primaryStage.show();
// Modify an item in the list every 2 seconds.
new Thread(() -> {
IntStream.range(0, listData.size()).forEach(i -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(listData.get(i));
Platform.runLater(() -> {
// Where the magic happens.
Person p=listData.get(i);
p.setFirstName(new StringBuilder(p.getFirstName()).append("!").toString());
//triggerUpdate(listView, listData.get(i), i);
});
});
}).start();
}
private void buildColumns(TableView<Person> listView,ObservableList<Person> listData) {
TableColumn<Person, String> dataCol = new TableColumn<Person, String>("First Name");
//dataCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
dataCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
// p.getValue() returns the Person instance for a particular TableView row
return p.getValue().firstNameProperty();
}
});
listView.getColumns().add(dataCol);
listView.setItems(listData);
}
public static void main(String[] args) {
launch(args);
}
}

Related

How to prevent event firing on drag and drop of columns in JavaFX TableView

In my Spring Boot JavaFX application I have multiple TableViews. The user is allowed to reorder the columns by using the default drag-and-drop functionality. I also have a listener to detect that another row in one of those TableViews is selected and take some action accordingly:
/*
* Processing when a selection in a table changes.
*/
getTableView().getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
this.detailsController.get().showDetails(newValue);
});
Problem is that this listener gets activated when a column is dragged and then dropped (on the drop part of the action). This has undesired side effects, since the variable newValue is 'null' in that case (which in itself is a valid value for processing, I just don't want to pass that value when dropping a column after dragging). Is there a way to bypass this listener when the column is dropped?
I have tried various ways to catch the drag-drop events, but to no avail...I was thinking I could deactivate the listener when the drag starts and reactivate after the drop is done.
Here is some sample code:
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestDragDrop extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty));
table.getColumns().add(column("Last Name", Person::lastNameProperty));
table.getColumns().add(column("Email", Person::emailProperty));
table.getItems().addAll(createData());
table.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
System.out.println("===>>> Oops");
} else {
System.out.println("===>>> Hi there " + newValue.getFirstName());
}
});
VBox checkBoxes = new VBox(5);
checkBoxes.getStyleClass().add("controls");
BorderPane root = new BorderPane(table);
root.setTop(checkBoxes);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S, T> TableColumn<S, T> column(String text, Function<S, ObservableValue<T>> property) {
TableColumn<S, T> col = new TableColumn<>(text);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col;
}
private List<Person> createData() {
return 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"));
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
Select a row in the table: ===>>> Hi there .... is output to the console. Now drag the first column to a different place in the table: ===>>> Oops is output to the console.
So one way to prevent this is by adding a buffer to prevent changes for a period of time once the column has been released.
In my case I used 50ms as the buffer because it will be hard for a person to finish dragging and click on a name in that time as it comes out to .05 Seconds in my testing this worked fine(No null were passed) but increase/decrease as you see fit
Here I initialize the PauseTransition which will fire after a given time
private final PauseTransition bufferReset = new PauseTransition(Duration.millis(50));
private boolean isBuffering = false;
Once initialized set the variable to flip back to no longer buffering
bufferReset.setOnFinished(event -> isBuffering = false);
The next block of code is where we flip the buffer variable after the column has been released and start the timer to flip the variable back
Platform.runLater(() -> {
for (Node header : table.lookupAll("TableHeaderRow")) {
if(header instanceof TableHeaderRow) {
header.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
isBuffering = true;
bufferReset.play();
});
}
}
});
From there wrap your code in a isBuffering if statement
if(!isBuffering) {
if (newValue == null) {
System.out.println("===>>> Oops");
} else {
System.out.println("===>>> Hi there " + newValue.getFirstName());
}
}
Full Code(Not including the person class):
public class TestDragDrop extends Application {
private final PauseTransition bufferReset = new PauseTransition(Duration.millis(50));
private boolean isBuffering = false;
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty));
table.getColumns().add(column("Last Name", Person::lastNameProperty));
table.getColumns().add(column("Email", Person::emailProperty));
table.getItems().addAll(createData());
table.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if(!isBuffering) {
if (newValue == null) {
System.out.println("===>>> Oops");
} else {
System.out.println("===>>> Hi there " + newValue.getFirstName());
}
}
});
bufferReset.setOnFinished(event -> isBuffering = false);
Platform.runLater(() -> {
for (Node header : table.lookupAll("TableHeaderRow")) {
if(header instanceof TableHeaderRow) {
header.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
isBuffering = true;
bufferReset.play();
});
}
}
});
VBox checkBoxes = new VBox(5);
checkBoxes.getStyleClass().add("controls");
BorderPane root = new BorderPane(table);
root.setTop(checkBoxes);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S, T> TableColumn<S, T> column(String text, Function<S, ObservableValue<T>> property) {
TableColumn<S, T> col = new TableColumn<>(text);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col;
}
private List<Person> createData() {
return 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"));
}
public static void main(String[] args) { launch(args); }
}

Recyclerview Filter not working.its not searching the elements

when i filter recyclerview it shows Not found .My Searchview not working.when i run the code its result in Not Found i think there is problem in onQueryTextChange
myfilter function also did not work
#Override
public boolean onQueryTextSubmit(String query) {
Toast.makeText(SecondActivity1.this, "Name is : " + query, Toast.LENGTH_SHORT).show();
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
final List<DatabaseModel> filteredModelList = filter(dbList, newText);
if (filteredModelList.size() > 0) {
// Toast.makeText(SecondActivity1.this, "Found", Toast.LENGTH_SHORT).show();
recyclerAdapter.setFilter(filteredModelList);
return true;
} else {
Toast.makeText(SecondActivity1.this, "Not Found", Toast.LENGTH_SHORT).show();
return false;
}
private List filter(List models, String query) {
query = query.toLowerCase();
recyclerAdapter.notifyDataSetChanged();
final List<DatabaseModel> filteredModelList = new ArrayList<>();
// mRecyclerView.setLayoutManager(new LinearLayoutManager(SecondActivity1.this));
// mRecyclerView.setAdapter(RecyclerAdapter);
for (DatabaseModel model : models) {
final String text = model.getName().toLowerCase();
if (text.contains(query)) {
filteredModelList.add(model);
}
}
return filteredModelList;
//
}
here is filter method which recieve parameter(dblist,newtext) filter method recieves these method when i use toast its show that it takes newText But didnot filter this.i checked many sites but this is same in many sites points.when i enter name toast shows name which i enter but it did not filter
RecyclerAdapter.java
package com.example.prabhu.databasedemo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* Created by user_adnig on 11/14/15.
*/
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
List<DatabaseModel> dbList;
static Context context;
RecyclerAdapter(Context context, List<DatabaseModel> dbList ){
this.dbList = new ArrayList<>();
this.context = context;
this.dbList = (ArrayList<DatabaseModel>) dbList;
}
#Override
public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_row, null);
// create ViewHolder
ViewHolder viewHolder = new ViewHolder(itemLayoutView);
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerAdapter.ViewHolder holder, int position) {
holder.name.setText(dbList.get(position).getName());
holder.email.setText(dbList.get(position).getEmail());
}
#Override
public int getItemCount() {
return dbList.size();
}
public void setFilter(List<DatabaseModel> countryModels) {
// Toast.makeText(RecyclerAdapter.this,"Method is called", Toast.LENGTH_SHORT).show();
dbList = new ArrayList<>();
dbList.addAll(countryModels);
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView name,email;
public ViewHolder(View itemLayoutView) {
super(itemLayoutView);
name = (TextView) itemLayoutView
.findViewById(R.id.rvname);
email = (TextView)itemLayoutView.findViewById(R.id.rvemail);
itemLayoutView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Intent intent = new Intent(context,DetailsActivity.class);
Bundle extras = new Bundle();
extras.putInt("position",getAdapterPosition());
intent.putExtras(extras);
/*
int i=getAdapterPosition();
intent.putExtra("position", getAdapterPosition());*/
context.startActivity(intent);
Toast.makeText(RecyclerAdapter.context, "you have clicked Row " + getAdapterPosition(), Toast.LENGTH_LONG).show();
}
}
}
this is my recyclerAdapterCode.i also used Recycleradapter.setFilter(filterModeList) method but it did not work for me.i think in my set filter method error which i did not solve yet.
. But when I clear the search widget I don't get the full list instead I get the empty RecyclerView.

Deleting Data From Database Through JavaFX GUI

What I want to be able to do is load my database through a table view select an item and have it deleted into the database. I do not have users entering the id of a particular song so it makes it harder for me to accomplish this. I have the GUI set up and all the code that I have so far.
GUI CODE:
SongContent Code:
/*
* 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 playmymusic;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author man
*/
public class SongContent
{
private final StringProperty artist;
private final StringProperty title;
private final StringProperty genre;
private final IntegerProperty id;
public SongContent(int id, String artist, String title, String genre)
{
this.artist = new SimpleStringProperty(artist);
this.title = new SimpleStringProperty(title);
this.genre = new SimpleStringProperty(genre);
this.id = new SimpleIntegerProperty(id);
}
public Integer getId()
{
return id.get();
}
public void setID(int paramId)
{
id.set(paramId);
}
public String getArtist()
{
return artist.get();
}
public void setArtist(String paramArtist)
{
artist.set(paramArtist);
}
public String getTitle()
{
return title.get();
}
public void setTitle(String paramTitle)
{
title.set(paramTitle);
}
public String getGenre()
{
return genre.get();
}
public void setGenre(String paramGenre)
{
genre.set(paramGenre);
}
public StringProperty artistProperty(){return artist;}
public StringProperty titleProperty(){return title;}
public StringProperty genreProperty(){return genre;}
public IntegerProperty idProperty() { return id;}
}
Controller Code:
/*
* 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 playmymusic;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.beans.property.IntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
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.Pane;
import javafx.stage.Stage;
import javax.swing.JOptionPane;
import org.apache.derby.jdbc.ClientDriver;
/**
*
* #author man
*/
public class FXMLDocumentController implements Initializable {
public LoginModel loginModel = new LoginModel();
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private TextField txtArtist;
#FXML
private TextField fxTitle;
#FXML
private TextField fxGenre;
#FXML
private TableView<SongContent> tableView;
#FXML
private TableColumn<SongContent, Integer> id;
#FXML
private TableColumn<SongContent, String> artist;
#FXML
private TableColumn<SongContent, String> title;
#FXML
private TableColumn<SongContent, String> genre;
private ObservableList<SongContent> data;
#FXML
private void Login(ActionEvent event) throws SQLException {
try {
if(loginModel.isLogin(txtUsername.getText(), txtPassword.getText()))
{
Stage primaryStage = new Stage();
FXMLLoader loader = new FXMLLoader();
Pane root = loader.load(getClass().getResource("PopUpWindow.fxml").openStream());
Scene scene = new Scene(root, 785, 809);
primaryStage.setScene(scene);
primaryStage.show();
PlayMyMusic.primaryStage.close();
}else
{
System.out.println("WOOPS");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#FXML
private void songs(ActionEvent e) throws SQLException, ClassNotFoundException
{
loginModel.insertSongs(txtArtist.getText(), fxTitle.getText(), fxGenre.getText());
try
{
int i = 1;
Connection conn = DriverManager.getConnection("jdbc:derby://localhost:1527/PlayMyMusicDB;user=test;password=test");
data = FXCollections.observableArrayList();
ResultSet rs = conn.createStatement().executeQuery("select * from Song");
while(rs.next())
{
data.add(new SongContent(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4)));
i++;
}
}catch(SQLException ex) {
System.err.println("Error" + ex);
}
id.setCellValueFactory(new PropertyValueFactory<>("id"));
artist.setCellValueFactory(new PropertyValueFactory<>("artist"));
title.setCellValueFactory(new PropertyValueFactory<>("title"));
genre.setCellValueFactory(new PropertyValueFactory<>("genre"));
tableView.setItems(null);
tableView.setItems(data);
txtArtist.clear();
fxTitle.clear();
fxGenre.clear();
}
#FXML
public void deleteItems(ActionEvent e) throws SQLException, ClassNotFoundException
{
Connection c = DriverManager.getConnection("jdbc:derby://localhost:1527/PlayMyMusicDB;user=test;password=test");
int action = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this item?");
if(action == 0)
{
try
{
IntegerProperty i = SongContent.idProperty();
ResultSet rs = c.createStatement().executeQuery("DELETE FROM Song where i = " + i);
}catch(Exception e1)
{
e1.printStackTrace();
}
}
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
}
}
`
Any explination of why this could not be deleting my data? I would also love it if someone explained to me a strategy of resetting the SongNumberID every time the GUI opened and closed. But, the main goal for me is to figure out how to delete songs.
Thanks so much
-Aaron
The result of calling toString on a SimpleIntegerProperty is something like IntegerProperty [value: 10]. You should use the value, not the IntegerProperty. Furthermore it's better to use a PreparedStatement to create the query. Also you should get the selected item from the table instead of trying to reference a instance method as if it was static:
SongContent song = tableView.getSelectionModel().getSelectedItem();
if (song != null) {
// there is a selection -> delete
...
PreparedStatement statement = c.prepareStatement("DELETE FROM Song WHERE i = ?");
statement.setInt(1, song.getId());
statement.executeUpdate();
...
}
Furthermore you should make sure i is actually the column name of the id column (and not id).

Can't stop javafx tables from ignoring my the setter function validation

I'm using javafx to do some table stuff. I want to validate my textfields in the myTextRow Class. In the "setText2" method I check the input if it is not bigger than 6 symbols, but it has no effects at all.
import java.util.ArrayList;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextArea;
import javafx.util.Callback;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Supermain extends Application {
#Override
public void start(Stage primaryStage) {
ArrayList myindizes=new ArrayList();
final TableView<myTextRow> table = new TableView<>();
table.setEditable(true);
table.setStyle("-fx-text-wrap: true;");
//Table columns
TableColumn<myTextRow, String> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(new PropertyValueFactory<>("ID"));
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(new TextFieldCellFactory());
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(new TextFieldCellFactory());
//Add data
final ObservableList<myTextRow> data = FXCollections.observableArrayList(
new myTextRow(5, "Lorem","bla"),
new myTextRow(2, "Ipsum","bla")
);
table.getColumns().addAll(clmID, clmtext,clmtext2);
table.setItems(data);
HBox hBox = new HBox();
hBox.setSpacing(5.0);
hBox.setPadding(new Insets(5, 5, 5, 5));
Button btn = new Button();
btn.setText("Get Data");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (myTextRow data1 : data) {
System.out.println("data:" + data1.getText2());
}
}
});
hBox.getChildren().add(btn);
BorderPane pane = new BorderPane();
pane.setTop(hBox);
pane.setCenter(table);
primaryStage.setScene(new Scene(pane, 640, 480));
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public static class TextFieldCellFactory
implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {
#Override
public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
TextFieldCell textFieldCell = new TextFieldCell();
return textFieldCell;
}
public static class TextFieldCell extends TableCell<myTextRow, String> {
private TextArea textField;
private StringProperty boundToCurrently = null;
public TextFieldCell() {
textField = new TextArea();
textField.setWrapText(true);
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
this.setGraphic(textField);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// myindizes.add(getIndex());
// Retrieve the actual String Property that should be bound to the TextField
// If the TextField is currently bound to a different StringProperty
// Unbind the old property and rebind to the new one
ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
SimpleStringProperty sp = (SimpleStringProperty) ov;
if (this.boundToCurrently == null) {
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(sp);
} else if (this.boundToCurrently != sp) {
this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(this.boundToCurrently);
}
double height = real_lines_height(textField.getText(), this.getWidth(), 30, 22);
textField.setPrefHeight(height);
textField.setMaxHeight(height);
textField.setMaxHeight(Double.MAX_VALUE);
// if height bigger than the biggest height in the row
//-> change all heights of the row(textfields ()typeof textarea) to this height
// else leave the height as it is
//System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
//this.textField.setText(item); // No longer need this!!!
} else {
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
}
public class myTextRow {
private final SimpleIntegerProperty ID;
private final SimpleStringProperty text;
private final SimpleStringProperty text2;
public myTextRow(int ID, String text,String text2) {
this.ID = new SimpleIntegerProperty(ID);
this.text = new SimpleStringProperty(text);
this.text2 = new SimpleStringProperty(text2);
}
public void setID(int id) {
this.ID.set(id);
}
public void setText(String text) {
this.text.set(text);
}
public void setText2(String text) {
if(text2check(text)){
this.text2.set(text);}
else
{System.out.println("wrong value!!!");}
}
public int getID() {
return ID.get();
}
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public String getText2() {
return text2.get();
}
public StringProperty text2Property() {
return text2;
}
public IntegerProperty IDProperty() {
return ID;
}
public boolean text2check(String t)
{
if(t.length()>6)return false;
return true;
}
}
private static double real_lines_height(String s, double width, double heightCorrector, double widthCorrector) {
HBox h = new HBox();
Label l = new Label("Text");
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
double line_height = l.prefHeight(-1);
int new_lines = s.replaceAll("[^\r\n|\r|\n]", "").length();
// System.out.println("new lines= "+new_lines);
String[] lines = s.split("\r\n|\r|\n");
// System.out.println("line count func= "+ lines.length);
int count = 0;
//double rest=0;
for (int i = 0; i < lines.length; i++) {
double text_width = get_text_width(lines[i]);
double plus_lines = Math.ceil(text_width / (width - widthCorrector));
if (plus_lines > 1) {
count += plus_lines;
//rest+= (text_width / (width-widthCorrector)) - plus_lines;
} else {
count += 1;
}
}
//count+=(int) Math.ceil(rest);
count += new_lines - lines.length;
return count * line_height + heightCorrector;
}
private static double get_text_width(String s) {
HBox h = new HBox();
Label l = new Label(s);
l.setWrapText(false);
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
// System.out.println("dubbyloop.FXMLDocumentController.get_text_width(): "+l.prefWidth(-1));
return l.prefWidth(-1);
}
}
A rule of the JavaFX Properties pattern is that for a property x, invoking xProperty().setValue(value) should always be identical to invoking setX(value). Your validation makes this not true. The binding your cell implementation uses invokes the setValue method on the property, which is why it bypasses your validation check.
(Side note: in all the code I am going to change the names so that they adhere to proper naming conventions.)
The default way to implement a property in this pattern is:
public class MyTextRow {
private final StringProperty text = new SimpleStringProperty();
public StringProperty textProperty() {
return text ;
}
public final void setText(String text) {
textProperty().set(text);
}
public final String getText() {
return textProperty().get();
}
}
By having the set/get methods delegate to the appropriate property methods, you are guaranteed these rules are enforced, even if the textProperty() methods is overridden in a subclass. Making the set and get methods final ensures that the rule is not broken by a subclass overriding those methods.
One approach might be to override the set and setValue methods in the property, as follows:
public class MyTextRow {
private final StringProperty text2 = new StringPropertyBase() {
#Override
public String getName() {
return "text2";
}
#Override
public Object getBean() {
return MyTextRow.this ;
}
#Override
public void setValue(String value) {
if (text2Check(value)) {
super.setValue(value);
}
}
#Override
public void set(String value) {
if (text2Check(value)) {
super.set(value);
}
}
}
public StringProperty text2Property() {
return text2 ;
}
public final void setText2(String text2) {
text2Property().set(text2);
}
public final String getText2() {
return text2Property().get();
}
// ...
}
however, I think this will break the bidirectional binding that you have with the text property in the TextArea (basically, there is no way to communicate back to the text area when a change is vetoed, so the text area will not know to revert to the previous value). One fix would be to implement your cell using listeners on the properties instead of bindings. You could use a TextFormatter on the text area that simply updates the property and vetoes the text change if the change doesn't occur.
Here is a complete SSCCE using this approach:
import java.util.function.Function;
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.stage.Stage;
public class VetoStringChange extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Description", Item::descriptionProperty));
for (int i = 1; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i, ""));
}
primaryStage.setScene(new Scene(table, 600, 600));
primaryStage.show();
}
public static <S> TableColumn<S,String> column(String title, Function<S,Property<String>> property) {
TableColumn<S,String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(tc -> new TextAreaCell<S>(property));
col.setPrefWidth(200);
return col ;
}
public static class TextAreaCell<S> extends TableCell<S, String> {
private TextArea textArea ;
public TextAreaCell(Function<S, Property<String>> propertyAccessor) {
textArea = new TextArea();
textArea.setWrapText(true);
textArea.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textArea.setMaxHeight(Double.MAX_VALUE);
UnaryOperator<Change> filter = c -> {
String proposedText = c.getControlNewText() ;
Property<String> prop = propertyAccessor.apply(getTableView().getItems().get(getIndex()));
prop.setValue(proposedText);
if (prop.getValue().equals(proposedText)) {
return c ;
} else {
return null ;
}
};
textArea.setTextFormatter(new TextFormatter<String>(filter));
this.setGraphic(textArea);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
if (! textArea.getText().equals(item)) {
textArea.setText(item);
}
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
public static class Item {
private final StringProperty name = new StringPropertyBase() {
#Override
public Object getBean() {
return Item.this;
}
#Override
public String getName() {
return "name" ;
}
#Override
public void set(String value) {
if (checkValue(value)) {
super.set(value);
}
}
#Override
public void setValue(String value) {
if (checkValue(value)) {
super.setValue(value);
}
}
};
private final StringProperty description = new SimpleStringProperty();
public Item(String name, String description) {
setName(name);
setDescription(description);
}
private boolean checkValue(String value) {
return value.length() <= 6 ;
}
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 StringProperty descriptionProperty() {
return this.description;
}
public final String getDescription() {
return this.descriptionProperty().get();
}
public final void setDescription(final String description) {
this.descriptionProperty().set(description);
}
}
public static void main(String[] args) {
launch(args);
}
}
Another approach is to allow a "commit and revert" type strategy on your property:
public class MyTextRow {
private final StringProperty text2 = new SimpleStringProperty();
public MyTextRow() {
text2.addListener((obs, oldText, newText) -> {
if (! checkText2(newText)) {
// sanity check:
if (checkText2(oldText)) {
text2.set(oldText);
}
}
});
}
public StringProperty text2Property() {
return text ;
}
public final void setText2(String text2) {
text2Property().set(text2);
}
public final String getText2() {
return text2Property().get();
}
}
In general I dislike validation by listening for an invalid value and reverting like this, because other listeners to the property will see all the changes, including changes to and from invalid values. However, this might be the best option in this case.
Finally, you could consider vetoing invalid changes as in the first option, and also setting a TextFormatter on the control in the cell that simply doesn't allow text entry that results in an invalid string. This isn't always possible from a usability perspective (e.g. if empty strings are invalid, you almost always want to allow the user to temporarily delete all the text), and it means keeping two validation checks in sync in your code, which is a pain.

JavaFX - Disable Tab when invalid data entered in TableView

I have a TabPane where users enter/edit data on each tab and can freely switch between tabs without having to save changes before switching to a new tab. One tab has a TableView, and I'd like to prevent users from leaving that tab if they enter invalid data. My original approach was along the same lines as this question, which does not quite work - the tab is not reliably changed back. I liked James_D's answer and tried to implement something similar. However, most of the time the data being entered into a table is optional, so disabling other tabs until a user enters data is not an option.
What I ultimately did was extend TableColumn to add a BooleanProperty 'invalid' which I then bind to Tab's disableProperty. In that column's commit event, I validate the new value and, if it doesn't pass, set invalid = true, which disables the appropriate tab. This also does not quite work. I have custom table cells that commit edits on loss of focus. If focus is lost to clicking a different tab, the commit event is too late - the tab is first selected, then disabled. I've been wracking my brain for a workaround, but am out of ideas. If anyone has any suggestions, I would really appreciate it!
Short example (clear out any last name and click Tab 2):
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TabPaneTableTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
ObservableList<Person> data = FXCollections.observableArrayList();
table.setEditable(true);
MyTableColumn<Person, String> firstNameCol = new MyTableColumn<>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
MyTableColumn<Person, String> lastNameCol = new MyTableColumn<>("Last Name");
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory = (TableColumn<Person, String> p) -> new MyEditingCell<Person>();
firstNameCol.setCellFactory(cellFactory);
lastNameCol.setCellFactory(cellFactory);
firstNameCol.setOnEditCommit((CellEditEvent<Person, String> event) -> {
event.getRowValue().setFirstName(event.getNewValue());
});
lastNameCol.setOnEditCommit((CellEditEvent<Person, String> event) -> {
if(event.getNewValue().trim().isEmpty()) {
new Alert(AlertType.ERROR, "Last name must be filled out!", ButtonType.OK).showAndWait();
lastNameCol.setInvalid(true);
}
else {
event.getRowValue().setLastName(event.getNewValue());
lastNameCol.setInvalid(false);
}
});
table.getColumns().addAll(firstNameCol, lastNameCol);
table.setItems(data);
data.add(new Person("Luke", "Skywalker"));
data.add(new Person("Han", "Solo"));
data.add(new Person("R2", "D2"));
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("Tab 1");
tab1.setClosable(false);
tab1.setContent(table);
Tab tab2 = new Tab("Tab 2");
tab2.setClosable(false);
tab2.disableProperty().bind(lastNameCol.invalidProperty());
tabPane.getTabs().addAll(tab1, tab2);
Scene scene = new Scene(tabPane, 400, 200);
primaryStage.setTitle("Tab Pane Table Validation Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public class MyEditingCell<S> extends TableCell<S, String> {
private TextField editingField;
private void createEditingField() {
editingField = new TextField(getString());
editingField.focusedProperty().addListener((ov, oldValue, newValue) -> {
if(!newValue) {
commitEdit(editingField.getText());
}
});
}
#Override
public void startEdit() {
super.startEdit();
createEditingField();
setText(null);
setGraphic(editingField);
Platform.runLater(() -> {
editingField.requestFocus();
editingField.selectAll();
});
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String)getItem());
setGraphic(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(empty) {
setText(null);
setGraphic(null);
}
else {
if(isEditing()) {
if(editingField != null) {
editingField.setText(getString());
}
setText(null);
setGraphic(editingField);
}
else {
setText(getString());
setGraphic(null);
}
}
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
public class MyTableColumn<S, T> extends TableColumn<S, T> {
private BooleanProperty invalid = new SimpleBooleanProperty(false);
public MyTableColumn(String header) {
super(header);
setEditable(true);
}
public BooleanProperty invalidProperty() {
return invalid;
}
public boolean getInvalid() {
return invalid.get();
}
public void setInvalid(boolean value) {
invalid.set(value);
}
}
public class Person {
private StringProperty firstName;
private StringProperty lastName;
public Person(String first, String last) {
firstName = new SimpleStringProperty(this, "firstName", first);
lastName = new SimpleStringProperty(this, "lastName", last);
}
public void setFirstName(String value) {
firstNameProperty().set(value);
}
public String getFirstName() {
return firstNameProperty().get();
}
public StringProperty firstNameProperty() {
if(firstName == null)
firstName = new SimpleStringProperty(this, "firstName", "First");
return firstName;
}
public void setLastName(String value) {
lastNameProperty().set(value);
}
public String getLastName() {
return lastNameProperty().get();
}
public StringProperty lastNameProperty() {
if(lastName == null)
lastName = new SimpleStringProperty(this, "lastName", "Last");
return lastName;
}
}
public static void main(String[] args) {
launch(args);
}
}
If you create your observable list with an extractor, for example:
ObservableList<Person> data = FXCollections.observableArrayList(person ->
new Observable[] { person.lastNameProperty() });
then the list will fire update notifications any time any of the specified properties change in any of the elements (in this case, any time the lastName changes on anything in the list).
So now you can create a binding for invalid:
BooleanBinding invalid = Bindings.createBooleanBinding(
() -> data.stream().anyMatch(person -> person.getLastName().isEmpty()),
data);
And then you can just observe that binding:
invalid.addListener((obs, wasInvalid, isNowInvalid) -> {
if (isNowInvalid) {
// show alert, etc...
}
});
or disable a node by binding to it:
someNode.disableProperty().bind(invalid);
You could similarly bind this invalid property in your TableColumn subclass (if you still need that) to this binding.

Resources