Custom CellFactory adds new nodes - java

i have a weird bug and I cant find the appropriate solution.
I wanted to add a TreeTableView to my application that shows a task taxonomy.
Since the user is able to add tasks himself but these task have to be distinguishable from tasks already exsting I want them to be yellow in my TreeView. Therefore I added a custom CellFactory:
public class TaskLibrary extends AnchorPane {
#FXML
private TreeTableColumn<Task,String> mainColumn;
#FXML
private TreeTableView<Task> taskTreeTableView;
public TaskLibrary(){
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("../layout/TaskLibrary.fxml"));
loader.setRoot(this);
loader.setController(this);
loader.load();
mainColumn.setCellValueFactory(param -> param.getValue().getValue().taskNameProperty());
mainColumn.setCellFactory(param -> new TreeTableCell<Task,String>(){
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
Task listObject = this.getTreeTableRow().getItem();
if (listObject != null) {
if (item == null || empty) {
setText("");
setStyle("");
} else {
if (listObject.getIsDummy()) {
setStyle("-fx-background-color: yellow");
}
setText(listObject.getTaskName());
}
}
}
});
}
catch (IOException e){
throw new RuntimeException(e);
}
}
#FXML
public void addNewTask(){
}
public void setColumnText(String text){
mainColumn.setText(text);
}
public void enableDragAndDrop(){
TaskTreeRowFactory fac = new TaskTreeRowFactory();
taskTreeTableView.setRowFactory(fac::internalMoveFactory);
}
public TreeTableColumn<Task, String> getMainColumn() {
return mainColumn;
}
public void setMainColumn(TreeTableColumn<Task, String> mainColumn) {
this.mainColumn = mainColumn;
}
public TreeTableView<Task> getTaskTreeTableView() {
return taskTreeTableView;
}
public void setTaskTreeTableView(TreeTableView<Task> taskTreeTableView) {
this.taskTreeTableView = taskTreeTableView;
}
}
Now there is this weird bug that expanding the last node in my treeview results in a new node:
is there something i missed?

You only manipulate the cell, if the row's item is not null. However if the row becomes empty the cell becomes empty too but this.getTreeTableRow().getItem() yields null and you don't modify the cell to look empty. You need to always clear the text/style when the cell becomes empty:
if (empty || item == null || listObject == null) {
setText("");
setStyle("");
} else {
setStyle(listObject.getIsDummy() ? "-fx-background-color: yellow" : "");
setText(listObject.getTaskName());
}

Related

Multiple scene nodes in PropertySheet Editor JavaFX

I want to add checkbox and textfield to one property of PropertySheet (ControlsFX library). Is it possible or no? So, i just need to add some GUI elements together to one PropertyEditor, for example checkbox + button, checkbox + label, checkbox + textfield and etc. Is it possible to override PropertyEditor to do it?
you could also wrap multiples nodes inside a single parent .[See Here
Solved by myself. I tried to add checkbox + combobox to HBox. Code below, it works.
public static final <T> PropertyEditor<?> createCheckBoxLinkEditor(PropertySheet.Item property,
final Collection<T> choices) {
ComboBox<T> comboBox = new ComboBox<T>();
comboBox.setCellFactory((ListView<T> p) -> new ListCell<T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
} else if (item instanceof Class) {
setText(((Class) item).getSimpleName());
} else {
setText(item.toString());
}
}
});
HBox hbox = new HBox(5);
CheckBox checkBox = new CheckBox();
hbox.getChildren().add(checkBox);
hbox.getChildren().add(comboBox);
//hbox.getA
//comboBox.setConverter(value);
return new AbstractPropertyEditor<T, HBox>(property, hbox) {
{
comboBox.setItems(FXCollections.observableArrayList(choices));
//new AutoCompleteComboBoxListener(comboBox);
new SelectKeyComboBoxListener(comboBox);
}
#Override
protected ObservableValue<T> getObservableValue() {
return comboBox.getSelectionModel().selectedItemProperty();
}
#Override
public void setValue(T value) {
comboBox.getSelectionModel().select(value);
}
};
}

Setting value to a particular cell using a dialog in table view of javafx

I am sucessfull in making a table column editable and updating value to it when the cell is double clicked. Now what I want to do is get the value from a txt field and set the value to a particular cell (column) of the selected row. I have gone through many research but could not find a proper answer. Javafx doesn't allow to directly edit values to a table except directly editing the cell and setting its value.
Thank you
This is a sample of so far what I have done.
Setting cellValueFactory to teh table columns
tblColQuantity.setCellValueFactory(cellData -> cellData.getValue()
.quantityProperty());
tblColQuantity.setCellFactory(col -> new IntegerEditingCell());
tblColRateWithoutvat.setCellValueFactory(cellData -> cellData
.getValue().rateWithoutvatProperty());
tblColRateWithoutvat.setCellFactory(col -> new IntegerEditingCell());
tblColTotalWithvat.setCellValueFactory(cellData -> cellData.getValue().totalWithvatProperty());
tblColTotalWithvat.setCellFactory(col -> new IntegerEditingCell());
The Inner class which helps me update the cell data
public class IntegerEditingCell extends TableCell<AddBillTable, Number> {
private final TextField textField = new TextField();
private final Pattern intPattern = Pattern.compile("\\d*\\.\\d+");
// -?\\d+
public IntegerEditingCell() {
textField.focusedProperty().addListener(
(obs, wasFocused, isNowFocused) -> {
if (!isNowFocused) {
processEdit();
}
});
textField.setOnAction(event -> processEdit());
}
private void processEdit() {
String text = textField.getText();
if (intPattern.matcher(text).matches()) {
commitEdit(Float.parseFloat(text));
} else {
cancelEdit();
}
}
#Override
public void updateItem(Number value, boolean empty) {
super.updateItem(value, empty);
if (empty || value.equals(null)) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
setText(null);
textField.setText(value.toString());
setGraphic(textField);
}/*
* else if (!empty){ textField.setText(value.toString()); }
*/else {
// if((!value.toString().equals(null)) || (value==null)){
setText(value.toString());
setGraphic(null);
System.out.println("Updated");
System.out.println(this.textField.getText());
// }
}
}
#Override
public void startEdit() {
super.startEdit();
Number value = getItem();
if (value != null) {
textField.setText(value.toString());
setGraphic(textField);
setText(null);
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem().toString());
setGraphic(null);
}
// This seems necessary to persist the edit on loss of focus; not sure
// why:
#Override
public void commitEdit(Number value) {
super.commitEdit(value);
// ((PurchaseDetail)this.getTableRow().getItem()).setQuantity(value.floatValue());
System.out.println("Commit edit " + value);
detectEditedCell(value);
}
}
You can just get the selected item from the table, and call the appropriate set method corresponding to the property that the column represents.
For example, if you wanted a text field to update the quantity of the currently selected row, you would do:
TextField textField = new TextField();
textField.setOnAction(e -> {
AddBillTable selectedItem = table.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
selectedItem.setQuantity(Integer.parseInt(textField.getText()));
}
});
As long as you are implementing your model class (AddBillTable in your example) with JavaFX observable properties (StringProperty, IntegerProperty, etc), then changing the property value will automatically update the table.

Javafx Bug? Tableview is not rendering correctly some rows

Actually I have a problem in my JavaFX app using TableView. I don't no why but, when I load data to TableView during runtime, the JavaFX is not rendering some rows, as you can see in the picture bellow:
But, when I resize the column, the data is displayed:
Bellow follow the source code used:
public void loadData()
{
// Define the TableView columns using Reflection defined by ResultSetMetadata
ArrayList<TableColumn> gridColumns = defineGridColumns(data.get(0));
this.tableView.getColumns().clear();
this.tableView.getColumns().addAll(gridColumns);
// Load data to TableView
this.tableView.setItems(FXCollections.observableArrayList(data));
}
private void defineGridColumns(Object singleData)
{
ArrayList<TableColumn> gridColumns = new ArrayList<>();
Field[] fields = singleData.getClass().getFields();
for (int i = 0; i < fields.length; i++)
{
TableColumn column = createTableColumn(fields[i].getName());
this.gridColumns.add(column);
}
return gridColumns;
}
private TableColumn createTableColumn(String columnName)
{
TableColumn column = new TableColumn(columnName);
column.setCellValueFactory(new PropertyValueFactory(columnName));
column.setPrefWidth(columnName.length() * 20);
HBox box = new HBox();
box.setAlignment(Pos.CENTER);
box.prefWidthProperty().bind(column.widthProperty().subtract(5));
box.setSpacing(10.0);
box.getChildren().addAll(new Label(column.getText()));
column.setGraphic(box);
// Align the cell content in center
column.setCellFactory(new Callback<TableColumn, TableCell>()
{
#Override
public TableCell call(TableColumn param)
{
TableCell cell = new TableCell()
{
#Override
public void updateItem(Object item, boolean empty)
{
if (item != null)
{
setText(item.toString());
}
}
};
cell.setAlignment(Pos.CENTER);
return cell;
}
});
return column;
}
Well, what I'm doing wrong? I already update my Java to the lastest version (JDK 1.8.11).
Thanks to everybody!
Palliative Solution
As resizing column width triggers JavaFX to display the data, I did a method that changes all columns sizes after the data is loaded:
public void loadData()
{
// Define the TableView columns using Reflection defined by ResultSetMetadata
ArrayList<TableColumn> gridColumns = defineGridColumns(data.get(0));
this.tableView.getColumns().clear();
this.tableView.getColumns().addAll(gridColumns);
// Load data to TableView
this.tableView.setItems(FXCollections.observableArrayList(data));
// HACK to force JavaFX display all data
final TrendAnalysisTabController thisController = this;
Platform.runLater(new Runnable()
{
#Override
public void run()
{
thisController.adjustAllColumnWidths();
}
});
}
private void adjustAllColumnWidths()
{
TableViewSkin<?> skin = (TableViewSkin<?>) this.tableView.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
NestedTableColumnHeader rootHeader = headerRow.getRootHeader();
for (TableColumnHeader columnHeader : rootHeader.getColumnHeaders())
{
try
{
TableColumn<?, ?> column = (TableColumn<?, ?>) columnHeader.getTableColumn();
if (column != null)
{
// Changes the width column and rollback it
double prefWidth = column.getPrefWidth();
column.setPrefWidth(prefWidth + 0.01);
column.setPrefWidth(prefWidth);
}
}
catch (Throwable e)
{
log.log(Level.SEVERE, "Error adjusting columns widths: " + e.getMessage(), e);
}
}
}
because TableCells are reused, you need to explicitly "clear" them in your updateItem method:
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else{
setText(item.toString());
}
}
I'm not sure if this fixes your problem, but when creating a TableCell implementation, your updateItem(...) method must call super.updateItem(...):
column.setCellFactory(new Callback<TableColumn, TableCell>()
{
#Override
public TableCell call(TableColumn param)
{
TableCell cell = new TableCell()
{
#Override
public void updateItem(Object item, boolean empty)
{
// Don't omit this:
super.updateItem(item, empty);
if (item != null)
{
setText(item.toString());
}
}
};
cell.setAlignment(Pos.CENTER);
return cell;
}
});

ArrayList displayed incorrectly

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());
}
}
}

JavaFX 2 TreeView - How to change default behavior of entering edit mode?

Inspired by the JavaFX tutorial on http://docs.oracle.com/javafx/2/ui_controls/tree-view.htm I am wondering how could I change the behaviour to enter a cell in edit mode. The behaviour I would like to get is
on one left mouse-click: just select the cell
on two left mouse-clicks: select cell and invoke some action
on right-mouse-click: enter cell in edit mode
I tried to install a mouse event handler on the TreeView/TreeCell but it seems that the event is already consumed by TreeCellBehavior.
In class TreeCellBehvior there is the following method:
private void simpleSelect(MouseEvent e) {
TreeView tv = getControl().getTreeView();
TreeItem treeItem = getControl().getTreeItem();
int index = getControl().getIndex();
MultipleSelectionModel sm = tv.getSelectionModel();
boolean isAlreadySelected = sm.isSelected(index);
tv.getSelectionModel().clearAndSelect(index);
// handle editing, which only occurs with the primary mouse button
if (e.getButton() == MouseButton.PRIMARY) {
if (e.getClickCount() == 1 && isAlreadySelected) {
tv.edit(treeItem);
} else if (e.getClickCount() == 1) {
// cancel editing
tv.edit(null);
} else if (e.getClickCount() == 2/* && ! getControl().isEditable()*/) {
if (treeItem.isLeaf()) {
// attempt to edit
tv.edit(treeItem);
} else {
// try to expand/collapse branch tree item
treeItem.setExpanded(! treeItem.isExpanded());
}
}
}
}
I am not sure if can replace the TreeCellBehavior with my own implementation. Though this method is private I am not sure if this would be the right way to go. Any idea?
I worked it out by myself. I disable the editable of TreeView by default. For each TreeItem there is a context menu allowing to change the items name. If context menu action is invoked the TreeView is set to editable and TreeView.edit() with the current TreeItem is invoked. Now startEdit() is called behind the scenes and edit mode is active.
However I have got some strange behavior after enter is pressed and commitEdit() is called. This method checks if the cell is still in edit mode (which it is and therefore returns true) causing an internal invocation of cancelEdit()?!?! As a workaround I introduced a commitModeProperty and check in cancelEdit() if it is set.. otherwise the new text value would never be set.
Here is my code:
public class FolderTreeCell extends TreeCell<FolderCellType> {
// workaround for a strange behaviour in commitEdit.. see initTextFieldListener()
private BooleanProperty commitModeProperty = new SimpleBooleanProperty(false);
public FolderTreeCell() {
assert Platform.isFxApplicationThread();
}
private ContextMenu createContextMenu() {
MenuItem menuItem = new MenuItem("Change folder name");
menuItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
getTreeView().setEditable(true);
getTreeView().edit(getTreeItem());
}
});
return new ContextMenu(menuItem);
}
private void initTextFieldListener() {
getItem().textFieldProperty().get().setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent evt) {
if (evt.getCode() == KeyCode.ENTER) {
commitEdit(getItem()); // TODO calls updateItem() when isEditing() is true causing invocation of cancelEdit() ?!?!
}
}
});
}
#Override
public void commitEdit(FolderCellType newFolderCellType) {
commitModeProperty.set(true);
super.commitEdit(newFolderCellType);
commitModeProperty.set(false);
}
#Override
public void startEdit() {
super.startEdit();
setGraphic(getItem().getEditBox());
if (getItem().textFieldProperty().get().getOnKeyReleased() == null) {
initTextFieldListener();
}
getItem().textFieldProperty().get().selectAll();
getItem().textFieldProperty().get().requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
getTreeView().setEditable(false);
if (!commitModeProperty.getValue()) {
getItem().resetCurrentEntry();
}
setGraphic(getItem().getViewBox());
}
#Override
public void updateItem(FolderCellType item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
setGraphic(item.getEditBox());
} else {
setGraphic(item.getViewBox());
if (getContextMenu() == null) {
setContextMenu(createContextMenu());
}
}
}
getTreeView().setEditable(false);
}
}

Categories