I have a TableView with 4 rows and for each one a custom cell. The first one is just an image, which one I want to update a few times, a string, a string with word wrap and an second image as a button.
// image
tcUrlStatus.setCellValueFactory(new ColumnImageFactory());
tcUrlStatus.setCellFactory(new ColumnCallback());
// one line string
tcUrlName.setCellValueFactory(new ColumnNameFactory());
tcUrlName.setCellFactory(new ColumnCallback());
// two line string
tcUrlDate.setCellValueFactory(new ColumnDateFactory());
tcUrlDate.setCellFactory(new ColumnCallback());
// image as a button
tcDelete.setCellValueFactory(new ColumnDeleteFactory());
tcDelete.setCellFactory(new ColumnCallback());
And here are the custom cells
class ColumnCallback implements Callback<TableColumn<Quartet<Boolean, String, String, String>, Object>, TableCell<Quartet<Boolean, String, String, String>, Object>>{
#Override
public TableCell<Quartet<Boolean, String, String, String>, Object> call(TableColumn<Quartet<Boolean, String, String, String>, Object> column) {
return new ColumnCell();
}
}
class ColumnImageFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue0());
}
}
class ColumnNameFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, String>, ObservableValue<String>> {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, String> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue1());
}
}
class ColumnDateFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue2());
}
}
class ColumnDeleteFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Boolean, String, String, String>, Object> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getValue3());
}
}
class ColumnCell extends TableCell<Quartet<Boolean, String, String, String>, Object> {
#Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
if (item instanceof Boolean) {
setText(null);
Image image;
AnchorPane pane = new AnchorPane();
if ((boolean) item) {
image = new Image(Main.class.getResourceAsStream("/hourglass.gif"));
} else {
image = new Image(Main.class.getResourceAsStream("/clean.gif"));
}
ImageView imageView = new ImageView(image);
imageView.setFitWidth(30);
imageView.setY(5);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
pane.getChildren().add(imageView);
setGraphic(pane);
}else {
if (item instanceof String) {
if (item.equals("delete")) {
AnchorPane pane = new AnchorPane();
Image image = new Image(Main.class.getResourceAsStream("/cross.png"));
ImageView imageView = new ImageView(image);
imageView.setFitWidth(20);
imageView.setY(10);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setCursor(Cursor.HAND);
pane.getChildren().add(imageView);
imageView.setOnMouseClicked((Event event) -> {
Quartet<Boolean, String, String, String> row = this.getTableView().getSelectionModel().getSelectedItem();
Controller.localJson.remove(row.getValue1());
this.getTableView().getItems().remove(row);
});
setGraphic(pane);
} else {
HBox pane = new HBox();
Label label = new Label();
label.setText((String) item);
label.setTextAlignment(TextAlignment.CENTER);
if (((String) item).length() < 20) {
label.setWrapText(true);
label.setAlignment(Pos.CENTER);
}
pane.setPrefHeight(40);
label.setPrefHeight(40);
pane.getChildren().add(label);
setGraphic(pane);
}
setText(null);
}
}
} else {
setText(null);
setGraphic(null);
}
}
}
Now I want to change the Image of the tcUrlStatus column/cell. I already figured out how to get the row or the value of it, but I can't figure out how to set the value or rather change from true to false or false to true for updating the image.
get row and value:
private int getTableRowIndex(String url){ // url is the second column
int counter = 0;
for (Object row:tvUrls.getItems()){
if ((((Quartet) row).getValue1() == url)){
return counter;
}
counter ++;
}
return -1;
}
int rowIndex = getTableRowIndex(url);
Object item = tvUrls.getItems().get(rowIndex);
It would be easiest to do this by using javafx properties in your Quartet class, e.g. assuming the type used for the value0 bean is T:
// TODO: Is using a readonly property really needed here ???
// if not, use SimpleObjectProperty instead
private final ReadOnlyObjectWrapper<T> value0 = new ReadOnlyObjectWrapper<>();
public T getValue0() {
return value0.get();
}
// TODO: should the setter really be public ???
public void setValue0(T newValue) {
value0.set(newValue);
}
public ReadOnlyObjectProperty<T> value0Property() {
return value0.getReadOnlyProperty();
}
Which allows you to use new PropertyValueFactory<>("value0") instead of your custom cell value factory, and, even more important, means the TableView components will be notified of changes in the Quartet instances.
This way you can simply use
quartetInstance.setValue0(newValue0);
and the cell will get updated.
Using the PropertyValueFactory would have the same effect as using the following cellValueFactory in this case:
class ColumnImageFactory implements Callback<TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String>, ObservableValue<Object>> {
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Quartet<Object, String, String, String>, String> data) {
return data.getValue().value0Property();
}
}
If you cannot add javaFX properties to the Quartet class you need some other way of updating the TableView after the change. TableView.refresh() would work (provided you use JavaFX version >= 8u60) or writing an adapter in case you have implemented the observer patten in the Quartet class some other way...
I solved it with chaning from the Quartet Class to the SimpleObjectProperty Class.
Init:
tcUrlStatus.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, Boolean>("status"));
tcUrlName.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, String>("url"));
tcUrlDate.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, String>("date"));
tcDelete.setCellValueFactory(new PropertyValueFactory<ColumnCellValue, Boolean>("delete"));
tcUrlStatus.setCellFactory(new ColumnStatusCell());
tcUrlName.setCellFactory(new ColumnStringCell(false));
tcUrlDate.setCellFactory(new ColumnStringCell(true));
tcDelete.setCellFactory(new ColumnDeleteCell());
Adding rows:
tvUrls.getItems().add(new ColumnCellValue(true, url, date));
Updating cells:
ColumnCellValue statusRow = (ColumnCellValue)
tvUrls.getItems().get(rowIndex);
Column classes:
public class ColumnCellValue{
private final ObjectProperty status;
private final ObjectProperty url;
private final ObjectProperty date;
private final ObjectProperty delete = new SimpleObjectProperty<Boolean>(true);
ColumnCellValue(Boolean status, String url, String date) {
this.status = new SimpleObjectProperty<Boolean>(status);
this.url = new SimpleObjectProperty<String>(url);
this.date = new SimpleObjectProperty<String>(date);
}
public Object getDate() {
return date.get();
}
public ObjectProperty dateProperty() {
return date;
}
public void setDate(Object date) {
this.date.set(date);
}
public Object getDelete() {
return delete.get();
}
public ObjectProperty deleteProperty() {
return delete;
}
public void setDelete(Object delete) {
this.delete.set(delete);
}
public Object getStatus() {
return status.get();
}
public ObjectProperty statusProperty() {
return status;
}
public void setStatus(Object status) {
this.status.set(status);
}
public Object getUrl() {
return url.get();
}
public ObjectProperty urlProperty() {
return url;
}
public void setUrl(Object url) {
this.url.set(url);
}
}
class ColumnStatusCell implements Callback<TableColumn<Boolean, Boolean>,TableCell<Boolean, Boolean>>{
#Override
public TableCell<Boolean, Boolean> call(TableColumn<Boolean, Boolean> param) {
AnchorPane pane = new AnchorPane();
ImageView imageView = new ImageView();
imageView.setFitWidth(30);
imageView.setY(5);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
TableCell<Boolean,Boolean> cell = new TableCell<Boolean,Boolean>(){
public void updateItem(Boolean item, boolean empty) {
if(item!=null){
Image image;
if (item) {
image = new Image(Main.class.getResourceAsStream("/hourglass.gif"));
} else {
image = new Image(Main.class.getResourceAsStream("/clean.gif"));
}
imageView.setImage(image);
}
}
};
pane.getChildren().add(imageView);
cell.setGraphic(pane);
return cell;
}
}
class ColumnStringCell implements Callback<TableColumn<String, String>,TableCell<String, String>>{
private boolean wrap = false;
ColumnStringCell(boolean wrap){
this.wrap = wrap;
}
#Override
public TableCell<String, String> call(TableColumn<String, String> param) {
TableCell<String,String> cell = new TableCell<String,String>(){
public void updateItem(String item, boolean empty) {
if(item!=null){
Label label = new Label();
label.setText(item);
label.setPrefHeight(40);
label.setTextAlignment(TextAlignment.CENTER);
label.setWrapText(wrap);
setGraphic(label);
}
}
};
return cell;
}
}
class ColumnDeleteCell implements Callback<TableColumn<Boolean, Boolean>,TableCell<Boolean, Boolean>>{
#Override
public TableCell<Boolean, Boolean> call(TableColumn<Boolean, Boolean> param) {
AnchorPane pane = new AnchorPane();
ImageView imageView = new ImageView();
imageView.setFitWidth(20);
imageView.setY(10);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setCursor(Cursor.HAND);
TableCell<Boolean,Boolean> cell = new TableCell<Boolean,Boolean>(){
public void updateItem(Boolean item, boolean empty) {
if (item != null) {
Image image = new Image(Main.class.getResourceAsStream("/cross.png"));
imageView.setImage(image);
}
}
};
imageView.setOnMouseClicked((Event event) -> {
TableView table = (TableView) ((ImageView) event.getSource()).getParent().getParent().getParent().getParent().getParent().getParent().getParent();
ColumnCellValue row = (ColumnCellValue) (table).getSelectionModel().getSelectedItem();
Controller.localJson.remove(row.getUrl().toString());
table.getItems().remove(row);
table.refresh();
});
pane.getChildren().add(imageView);
cell.setGraphic(pane);
return cell;
}
}
Related
I have JFXTreeTableView which consist of 5 columnsx In that first 2 columns have Delete & Edit Buttons for each cell. After populating table
I want first columns should disable on save Button click.
If above case is not possible then delete Buttons inside first column's cells should be disabled on Save button click.
I did like this but dont know how to disable column or buttons inside cells.
Controller Class
public class FinanceActionsController implements Initializable {
#FXML
private JFXTreeTableView<InvoiceItems> tblInvoiceItemsView;
private JFXButton btnSave;
#FXML
private HBox hbBottonBtnBar;
ObservableList<InvoiceItems> invoiceItems = FXCollections.observableArrayList();
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
tableStructure();
btnSave.setOnAction((ActionEvent event) -> {
if (invoiceItems.isEmpty()) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Please add Atleast one Invoice Item");
alert.showAndWait();
} else {
onClickBtnSaveInvoice();
disableAndAddControlsOnSave();
//tblInvoiceItemsView.setDisable(true);
}
});
}
private void tableStructure() {
JFXTreeTableColumn<InvoiceItems, Boolean> delItem = new JFXTreeTableColumn<>("Delete");
JFXTreeTableColumn<InvoiceItems, String> editItem = new JFXTreeTableColumn<>("Edit");
JFXTreeTableColumn<InvoiceItems, String> billItem = new JFXTreeTableColumn<>("Billable Head");
delItem.setCellValueFactory((TreeTableColumn.CellDataFeatures<InvoiceItems, Boolean> param) -> param.getValue().getValue().getBtnFlag());
delItem.setCellFactory(new Callback<TreeTableColumn<InvoiceItems, Boolean>, TreeTableCell<InvoiceItems, Boolean>>() {
#Override
public TreeTableCell<InvoiceItems, Boolean> call(TreeTableColumn<InvoiceItems, Boolean> param) {
final TreeTableCell<InvoiceItems, Boolean> cell = new TreeTableCell<InvoiceItems, Boolean>() {
MaterialIconView del = new MaterialIconView(MaterialIcon.DELETE_FOREVER, "1.5em");
final JFXButton btnDel = new JFXButton("", del);
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
btnDel.disableProperty().bind(txtN.disableProperty());
del.setFill(Color.RED);
btnDel.setButtonType(JFXButton.ButtonType.RAISED);
btnDel.setOnAction(event -> {
});
setGraphic(btnDel);
setText(null);
}
}
};
return cell;
}
});
billItem.setCellValueFactory((TreeTableColumn.CellDataFeatures<InvoiceItems, String> param) -> param.getValue().getValue().getBillItemDesc());
final TreeItem<InvoiceItems> root = new RecursiveTreeItem<>(invoiceItems, RecursiveTreeObject::getChildren);
tblInvoiceItemsView.getColumns().setAll(delItem, editItem, billItem);
tblInvoiceItemsView.setRoot(root);
tblInvoiceItemsView.setShowRoot(false);
}
Class InvoiceItems -
class InvoiceItems extends RecursiveTreeObject<InvoiceItems> {
StringProperty billItemDesc;
BooleanProperty btnFlag;
public InvoiceItems(String billItemDesc) {
this.billItemDesc = new SimpleStringProperty(billItemDesc);
}
public StringProperty getBillItemDesc() {
return billItemDesc;
}
public BooleanProperty getBtnFlag() {
return btnFlag;
}
public void setBtnFlag(Boolean btnFlag) {
this.btnFlag = new SimpleBooleanProperty(btnFlag);
}
}
I have tried to pass InvoiceItems setBtnFlag as True in Observable list to work in setCellFactory's updateItem method but not working. Please help any help will be appreciable, Thank You.
Find the image for the problem:
I am trying to create a column which displays multiple images which contains clickable events. But instead of image, I am getting the HTML code.
The column config I have written is as follows:
actionsCol = new ColumnConfig<SensorTreeModel,String>(new ValueProvider<SensorTreeModel, String>() {
com.sencha.project.client.Resources resources = GWT.create(com.sencha.project.client.Resources.class);
#Override
public String getValue(SensorTreeModel String) {
//ImageResource image = resources.add();
FlowPanel flowPanel = new FlowPanel();
ImageResource add = com.sencha.project.client.Resources.INSTANCES.add();
Image add1 = new Image(add);
flowPanel.add(add1);
//return add1;
return flowPanel.toString();
}
#Override
public void setValue(SensorTreeModel object, String value) {
if (object.getIsLeaf()) {
}
}
#Override
public String getPath() {
return "actions";
}
});
actionsCol.setHeader("");
In ColumnConfig<M,N> and ValueProvider<T,V>, N and V are the same and type of columns content. So on your example you are returning String as value. If you return ImageResource, column will show Image.
I hope it helps.
actionsCol = new ColumnConfig<SensorTreeModel,ImageResource>(new ValueProvider<SensorTreeModel, ImageResource>() {
com.sencha.project.client.Resources resources = GWT.create(com.sencha.project.client.Resources.class);
#Override
public ImageResource getValue(SensorTreeModel String) {
ImageResource add = com.sencha.project.client.Resources.INSTANCES.add();
return add;
}
#Override
public void setValue(SensorTreeModel object, ImageResource value) {
if (object.getIsLeaf()) {
}
}
#Override
public String getPath() {
return "actions";
}
});
actionsCol.setHeader("");
actionsCol.setCell(new ImageResourceCell());
Is there a default way to allow the users deselect a selected row?
If not, I want to make the row to be deselected on another click. How can I achieve it?
In the bellow I attached the code piece for my tree grid tg
// ---------------------- set up columns ------------------------ \\
// set up name column
ColumnConfig<ProductMapping, String> cc1 = new ColumnConfig<ProductMapping, String>(new ValueProvider<ProductMapping, String>(){
#Override
public String getValue(ProductMapping object) {
return object.getName();
}
#Override
public void setValue(ProductMapping object, String value) {
object.setName(value);
}
#Override
public String getPath() {
return "name";
}
});
cc1.setWidth(200);
cc1.setHeader("Name");
// setup solution column
ColumnConfig<ProductMapping, String> cc2 = new ColumnConfig<ProductMapping, String>(new ValueProvider<ProductMapping, String>() {
#Override
public String getValue(ProductMapping object) {
return object.getSolution();
}
#Override
public void setValue(ProductMapping object, String value) {
object.setSolution(value);
}
#Override
public String getPath() {
return "solution";
}
});
cc2.setHeader("Solution");
cc2.setWidth(200);
// setup condition column
ColumnConfig<ProductMapping, String> cc3 = new ColumnConfig<ProductMapping, String>(new ValueProvider<ProductMapping, String>() {
#Override
public String getValue(ProductMapping object) {
return object.getCondition();
}
#Override
public void setValue(ProductMapping object, String value) {
object.setCondition(value);
}
#Override
public String getPath() {
return "condition";
}
});
cc3.setHeader("Condition");
cc3.setWidth(200);
// create column model
List<ColumnConfig<ProductMapping,?>> ccl = new LinkedList<ColumnConfig<ProductMapping,?>>();
ccl.add( cc1);
ccl.add( cc2);
ccl.add( cc3);
ColumnModel<ProductMapping> cm = new ColumnModel<ProductMapping>(ccl);
// Create the tree grid using the store, column model and column config for the tree column
tg = new TreeGrid<ProductMapping>(treeStore, cm, ccl.get(0));
// tg.getSelectionModel().get
tg.addRowClickHandler(new RowClickEvent.RowClickHandler(){
#Override
public void onRowClick(RowClickEvent event) {
tgRowClicked(event);
}
});
I am trying to detect a doubleclick on a random cell of a tableview.
The detection of the doubleclick is not a problem but rather which cell has been doubleclicked.
table.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Cell text: " + c.getText());
}
}
});
This is how I'm building my table:
private void BuildTable() throws Exception
{
/*Some initialisations etc*/
for(int i=0; i<result.getMetaData().getColumnCount();i++)
{
final int j = i;
TableColumn col = new TableColumn(result.getMetaData().getColumnName(i+1));
col.setCellValueFactory(new Callback<CellDataFeatures<ObservableList,String>,ObservableValue<String>>(){
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param)
{
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
table.getColumns().addAll(col);
}
while(result.next()){
ObservableList<String> row = FXCollections.observableArrayList();
for(int i = 1; i<=result.getMetaData().getColumnCount();i++){
row.add(result.getString(i));
}
data.add(row);
}
table.setItems(data);
}catch(Exception e){
e.printStackTrace();
}
}
The real problem here is that I can't just typecast into a TableCell.
Can someone help me out? I would be very grateful.
Instead of registering a handler with the table, you need to register the handler with the table cells themselves. To do this, use a cell factory on the appropriate TableColumn(s).
As an example, add the following code to the standard table example (listing 13.6).
firstNameCol.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
#Override
public TableCell<Person, String> call(TableColumn<Person, String> col) {
final TableCell<Person, String> cell = new TableCell<Person, String>() {
#Override
public void updateItem(String firstName, boolean empty) {
super.updateItem(firstName, empty);
if (empty) {
setText(null);
} else {
setText(firstName);
}
}
};
cell.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1) {
System.out.println("double click on "+cell.getItem());
}
}
});
return cell ;
}
});
You need set the tableview's selection mode in cell, defined a custom type cell where you catch the keypress event or double clic, for an example you can review this page
https://gist.github.com/james-d/be5bbd6255a4640a5357
I'm testing to display Array List into JavaFX accordion:
public class Navigation {
// Object for storing conenctions
public static List<dataObj> list = new ArrayList<>();
private ObservableList<dataObj> data;
public class dataObj {
private String conenctionname;
public dataObj(String conenctionname) {
this.conenctionname = conenctionname;
}
public String getConenctionname() {
return conenctionname;
}
public void setConenctionname(String conenctionname) {
this.conenctionname = conenctionname;
}
}
public void initNavigation(Stage primaryStage, Group root, Scene scene) {
VBox stackedTitledPanes = createStackedTitledPanes();
ScrollPane scroll = makeScrollable(stackedTitledPanes);
scroll.getStyleClass().add("stacked-titled-panes-scroll-pane");
scroll.setPrefSize(395, 580);
scroll.setLayoutX(5);
scroll.setLayoutY(32);
root.getChildren().add(scroll);
}
private ScrollPane makeScrollable(final VBox node) {
final ScrollPane scroll = new ScrollPane();
scroll.setContent(node);
scroll.viewportBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) {
node.setPrefWidth(bounds.getWidth());
}
});
return scroll;
}
/////////////////////////////////////////////////////////////////////////////////////
// Generate accordition with Connections, Tables and Description
private VBox createStackedTitledPanes() {
VBox stackedTitledPanes = new VBox();
stackedTitledPanes.getChildren().setAll(
createConnectionsList("Connections"));
((TitledPane) stackedTitledPanes.getChildren().get(0)).setExpanded(true);
stackedTitledPanes.getStyleClass().add("stacked-titled-panes");
return stackedTitledPanes;
}
//////////////////////////////////////////////////////////////////////////////
// Generate list with Connections
public TitledPane createConnectionsList(String title) {
initObject();
data = FXCollections.observableArrayList(list);
ListView<dataObj> lv = new ListView<>(data);
lv.setCellFactory(new Callback<ListView<dataObj>, ListCell<dataObj>>() {
#Override
public ListCell<dataObj> call(ListView<dataObj> p) {
return new ConnectionsCellFactory();
}
});
AnchorPane content = new AnchorPane();
content.getChildren().add(lv);
// add to TitelPane
TitledPane pane = new TitledPane(title, content);
return pane;
}
static class ConnectionsCellFactory extends ListCell<dataObj> {
#Override
public void updateItem(dataObj item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
for (int i = 0; i < list.size(); i++) {
setText(list.get(i).getConenctionname());
}
}
}
}
// Insert Some test data
public void initObject() {
dataObj test1 = new dataObj("test data 1");
dataObj test2 = new dataObj("test data 2");
list.add(test1);
list.add(test2);
}
}
But for some reason I cannot get proper list of Objects from the Array list and display them. I get this result:
The proper result should be test data 1 and test data 2. How I can fix this?
The problem is in the ConnectionsCellFactory, the method updateItem is called for every item in List. So in you code, for every cell you are setting the text for every item in the list
you should try:
static class ConnectionsCellFactory extends ListCell<dataObj> {
#Override
public void updateItem(dataObj item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getConenctionname());
}
}
}