Phantom object after add in ListView [duplicate] - java

I'm learning JavaFX and i wanted to create a cell factory which is working properly until i want to delete a row from my ListView:
plateList.setCellFactory(new Callback<ListView<Car>, ListCell<Car>>() {
#Override
public ListCell<Car> call(ListView<Car> param) {
ListCell<Car> cell = new ListCell<Car>() {
#Override
protected void updateItem(Car item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getPlate());
}
}
};
return cell;
}
});
I'm populating the ListView with some sample data:
ObservableList<Car> sample = FXCollections.observableArrayList();
sample.add(new Car("123-abc", "opel", "corsa", 5.5));
sample.add(new Car("123-cba", "vw", "passat", 7.5));
plateList.setItems(sample);
Now i will see what i expect the ListView will be the following:
123-abc
123-cba
How ever if delete a row ex: the first row (123-abc), the ListView will look like this:
123-cba
123-cba
This is the delete part:
#FXML
private void deleteBtnAction() {
plateList.getItems().remove(plateList.getSelectionModel().getSelectedItem());
ObservableList<Car> t = plateList.getItems();
plateList.setItems(t);
}
If i remove the cell factory the program works as intended.
Any help is greatly appreciated.

Try changing to the following, This is required as JavaFX reuses the list cells, so the updateItem() needs to blank unused ones too when passed null
super.updateItem(item, empty);
if (item != null) {
setText(item.getPlate());
} else {
setText(""); // <== clear the now empty cell.
}
Full SSCCE
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class JavaFxListView extends Application {
private static class Car {
private String plate;
public Car(String plate, String string2, String string3, double d) {
this.plate = plate;
}
public String getPlate() {
return plate;
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage arg0) throws Exception {
ListView<Car> plateList = new ListView<Car>();
plateList.setCellFactory(new Callback<ListView<Car>, ListCell<Car>>() {
#Override
public ListCell<Car> call(ListView<Car> param) {
ListCell<Car> cell = new ListCell<Car>() {
#Override
protected void updateItem(Car item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getPlate());
} else {
setText("");
}
}
};
return cell;
}
});
Button delete = new Button("Delete");
ObservableList<Car> sample = FXCollections.observableArrayList();
sample.add(new Car("123-abc", "opel", "corsa", 5.5));
sample.add(new Car("123-cba", "vw", "passat", 7.5));
delete.setOnAction((e) -> {
plateList.getItems().remove(plateList.getSelectionModel().getSelectedItem());
ObservableList<Car> t = plateList.getItems();
plateList.setItems(t);
});
plateList.setItems(sample);
arg0.setScene(new Scene(new VBox(plateList, delete)));
arg0.show();
}
}

According to the Java doc of Cell updateItem method, there is slightly different recomended usage than the accepted one:
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.toString());
}
}
The difference is in the usage of parameter empty. But the sollution from #Adam should work correctly in major cases too.

Related

How to reference cell data on CellFactory?

I have a TableView where the last column is an "Action" column, containing a custom ActionBox object, with buttons for several actions. To add behaviour to this ActionBox, I need to pass a Data object to it. However, I don't know how to reference the object.
class TableViewWithActionColumn() {
#FXML TableColumn<Data, Void> actionColumn;
public TableViewWithActionColumn() {
// Code for loading custom component...
}
#FXML
public void initialize() {
populateActionColumn();
}
private void populateActionColumn() {
Callback<TableColumn<Data, Void>, TableCell<Data, Void>> cellFactory = new Callback<TableColumn<Data, Void>, TableCell<Data, Void>>() {
#Override
public TableCell<Data, Void> call(final TableColumn<Data, Void> param) {
return new TableCell<Data, Void>() {
private final ActionBox actionBox = new ActionBox();
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(actionBox);
}
}
};
}
};
actionColumn.setCellFactory(cellFactory);
}
}
I assumed that the reference to the object was in Void item, so I tried replacing all occurrences of Void with Data, and doing setGraphic(new ActionBox(item));, but this led to a NullPointerException, so I suppose that's not the right way to do it. So, how do I reference row's data in CellFactory context?
TableCell has a getTableRow() method, that gives you a reference to the TableRow that contains the cell. The TableRow is itself a cell implementation, so you can call its getItem() method to get the data represented by the row.
In context (and removing all the unnecessary boilerplate from your code):
private void populateActionColumn() {
actionColumn.setCellFactory(col -> new TableCell<Data, Void>() {
private final ActionBox actionBox = new ActionBox();
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
Data rowData = getTableRow().getItem();
// ... something like: actionBox.setData(rowData) ;
setGraphic(actionBox);
}
}
});
}

Click event on ImageView inside TableCell in JavaFX

I have a JavaFX TableView populated with many TableColumns, one of which is matImageColumn. The code for it looks like:
matImageColumn.setCellFactory(new Callback<TableColumn<CustomObject, ImageView>, TableCell<CustomObject, ImageView>>() {
#Override
public TableCell call(final TableColumn<CustomObject, ImageView> param) {
final TableCell<CustomObject, ImageView> cell = new TableCell<CustomObject, ImageView>() {
ImageView img = new ImageView();
#Override
public void updateItem(ImageView item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
CustomObject co = getTableView().getItems().get(getIndex());
img = co.getImageViewOfMat();
setGraphic(img);
setText(null);
}
}
};
return cell;
}
});
Now as it is, I am looking to include a click event on my imageview but I am clueless from where to start.Can you show me possible solutions to this issue? Thanks in advance.

checkbox select and deselect

package com.example.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import com.example.aayushchaubey.meetdax.R;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.QuerySnapshot;
import java.util.ArrayList;
import java.util.HashMap;
public class DifferentGenderServicesAdapter2 extends RecyclerView.Adapter {
private Task<QuerySnapshot> task;
String id, servicename;
ArrayList<String> serviceArray = new ArrayList<>();
ArrayList<Integer> costArray = new ArrayList<>();
ArrayList<ArrayList<String>> stylistIdArray = new ArrayList<>();
ArrayList<Integer>durationArray=new ArrayList<>();
TextView serviceTv, durationTv;
int count = 0;
String strService;
ArrayList<String> keyArr = new ArrayList<>();
ArrayList<String>serviceDocumentId=new ArrayList<>();
ArrayList<String>selectedServiceId=new ArrayList<>();
ArrayList<String>selectedServiceArray=new ArrayList<>();
ArrayList<Integer>selectedServiceCost=new ArrayList<>();
ArrayList<Integer>durationArr=new ArrayList<>();
String serviceId,strStylist,strDuration;
ArrayList<String>stylist=new ArrayList<>();
ArrayList<ArrayList<String>>selectedStylistId=new ArrayList<>();
public DifferentGenderServicesAdapter2(Task<QuerySnapshot> task, ArrayList<String> id, TextView servicesTv, TextView durationTv) {
this.task = task;
this.serviceTv = servicesTv;
this.durationTv = durationTv;
this.serviceDocumentId=id;
count = task.getResult().size();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.services_item, parent, false);
return new DifferentGenderServicesAdapter2.listViewHolder(view);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
try {
((listViewHolder) holder).servicenameTv.setText(task.getResult().getDocuments().get(position).get("name").toString());
((listViewHolder) holder).serviceCost.setText(task.getResult().getDocuments().get(position).get("price").toString());
servicename = task.getResult().getDocuments().get(position).get("name").toString();
Integer price = task.getResult().getDocuments().get(position).getLong("price").intValue();
serviceArray.add(servicename);
costArray.add(price);
ArrayList<HashMap<String,String>>stylistArr=new ArrayList<>();
stylistArr=(ArrayList<HashMap<String, String>>)task.getResult().getDocuments().get(position).get("services");
ArrayList<String> stylistsId = new ArrayList<String>();
stylistsId.clear();
for (int i=0;i<stylistArr.size();i++){
strStylist=stylistArr.get(i).get("stylist").toString();
strDuration=stylistArr.get(i).get("duration").toString();
stylistsId.add(strStylist);
durationArray.add(Integer.parseInt(strDuration));
}
stylistIdArray.add(stylistsId);
((listViewHolder) holder).checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(((listViewHolder) holder).checkBox.isChecked()){
String service=serviceArray.get(position);
selectedServiceArray.add(service);
serviceTv.setText(selectedServiceArray.toString().replaceAll("\\[|\\]", ""));
}else {
selectedServiceArray.remove(position);
if (selectedServiceArray.size()== 0) {
serviceTv.setText("0");
} else {
serviceTv.setText(selectedServiceArray.toString());
}
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public ArrayList<String> getServiceId() {
return selectedServiceId;
}
public ArrayList<String> getServices() {
return selectedServiceArray;
}
public Integer getCostArray() {
Integer cost = null;
if(selectedServiceCost.size()>1){
cost=selectedServiceCost.get(0)+selectedServiceCost.get(1);
}else {
cost=selectedServiceCost.get(0);
}
return cost;
}
public Integer getDurationArr() {
Integer duration=null;
if(durationArr.size()>1){
duration=durationArr.get(0)+durationArr.get(1);
}else {
duration=durationArr.get(0);
}
return duration;
}
public ArrayList<ArrayList<String>> getSelectedStylistId() {
return selectedStylistId;
}
#Override
public int getItemCount() {
return count;
}
private class listViewHolder extends RecyclerView.ViewHolder {
private CheckBox checkBox;
private TextView servicenameTv, serviceCost;
int count = 0;
String strService;
public listViewHolder(final View itemView) {
super(itemView);
try {
servicenameTv = (TextView) itemView.findViewById(R.id.damerServiceTV);
serviceCost = (TextView) itemView.findViewById(R.id.damerRsTV);
checkBox = (CheckBox) itemView.findViewById(R.id.checkBox);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
This is the recycleradapter i am using to populate array and checkbox.i want to setText for selected items.If i deselect the items i want to remove text from textview.When i select one item select and deselect is working properly.If i had more than one or if i select and deselct multiples times its not working giving index error
You are trying to delete an item at the given ReciclerView selection, but this position is wrong because you are doing:
selectedServiceArray.add(service);
So you don't know the position which is added because could be other items in this array. You have to request delete the object instead the position. Try this:
if(checkbox.isChecked){
String service=serviceArray.get(getAdapterPosition);
selectedServiceArray.add(service);
holder.serviceNameTv.setText(selectedServiceArray.toString);
} else {
String service=serviceArray.get(getAdapterPosition);
selectedServiceArray.remove(service);
holder.serviceNameTv.setText(selectedServiceArray.toString);
}
Do it like this : -
if(checkbox.isChecked){
String service=serviceArray.get(getAdapterPosition)
selectedServiceArray.add(service);
holder.serviceNameTv.setText(selectedServiceArray.toString);
}
else{
selectedServiceArray.remove(getAdapterPosition);
if (selectedServiceArray.size = 0) {
holder.serviceNameTv.setText("0");
} else{
holder.serviceNameTv.setText(selectedServiceArray.toString);
}

Modify the value before display in TableView javafx

Controller code
#Override
public void initialize(URL url, ResourceBundle rb) {
try {
Connection con = db.connect();
list = FXCollections.observableArrayList();
ResultSet rs = con.createStatement().executeQuery("Select * from Student");
while (rs.next()) {
list.add(new Student(rs.getInt(1), rs.getString(2),new java.util.Date(rs.getDate(3).getTime()) ));
}
} catch (SQLException ex) {
Logger.getLogger(DisplayAllStudentController.class.getName()).log(Level.SEVERE, null, ex);
}
studentID.setCellValueFactory(new PropertyValueFactory<>("studentID"));
name.setCellValueFactory(new PropertyValueFactory<>("name"));
admissionDate.setCellValueFactory(new PropertyValueFactory<>("admissionDate"))
tableView.setItems(null);
tableView.setItems(list);
I want to do some operation on data before display in table view
LIKE SWING
tableRow[3] = DateConverter.toString(c.getAdmissionDate());
In swing i do like this and it worked perfectly.
but don't know how to do operation on tableview javafx.
You'll probably want to add a CellFactory to the given column (don't mix with CellValueFactory). You don't mention what exactly you want to do with what data but if you want to format date to string in your own way you can write a class like:
public class LocalDateCellFactory<T> implements Callback<TableColumn<T, LocalDate>, TableCell<T, LocalDate>> {
#Override
public TableCell<T, LocalDate> call(TableColumn<T, LocalDate> col) {
return new TableCell<T, LocalDate>() {
#Override
protected void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if ((item == null) || empty) {
setText(null);
return;
}
setText(item.format(...yourOwnFormat...));
}
};
}
}
...and then just use it anywhere you want to display date in a tableview:
admissionDate.setCellFactory(new LocalDateCellFactory<>());

How to get row value inside updateItem() of CellFactory

ageColumn.setCellFactory(param -> new TableCell<Person, String>(){
#Override
protected void updateItem(String item, boolean empty) {
param.getCellFactory().
super.updateItem(item, empty);
setText(empty ? null : String.valueOf(item));
if(person.getName.equals("MATEUS")) {
setStyle("-fx-background-color: red;");
}
}
});
How to get this "Person" which is the row value from the Table? I can only get the value from the cell, but not the entire object.
You can do
Person person = getTableView().getItems().get(getIndex());
You can also do
Person person = (Person) getTableRow().getItem();
but this is less desirable (in my opinion) because getTableRow() returns a raw type, and consequently it requires the unchecked downcast.
Obviously either of these only works if empty is false, so they should be inside a check for that:
ageColumn.setCellFactory(param -> new TableCell<Person, String>(){
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setStyle("");
} else {
setText(item);
Person person = getTableView().getItems().get(getIndex());
if(person.getName.equals("MATEUS")) {
setStyle("-fx-background-color: red;");
} else {
setStyle("");
}
}
}
});

Categories