i have override the update item of TableCell in javafx 8 but when i scroll the items of tableview ,some items will be updated randomly
Column.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Person, Boolean>>() {
#Override
public TableCell<Person, Boolean> call(TableColumn<Person, Boolean> param) {
return new TableCell<Person, Boolean>() {
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
if (item.booleanValue()) {
setGraphic(new ImageView(IconResource.getImage(ICON.FLAG)));
}
} else {
setGraphic(null);
}
}
};
}
});
You need to make sure that the graphic property of the cell is updated properly regardless of the item (or it the fact that it's "missing").
In your case you do not properly handle cells that go from true as item to false as item: You simply keep the graphic for true instead of removing it.
Furthermore I recommend keeping a reference to the ImageView to avoid recreating it:
Column.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Spectrum, Boolean>>() {
#Override
public TableCell<Person, Boolean> call(TableColumn<Person, Boolean> param) {
return new TableCell<Person, Boolean>() {
private final ImageView image = new ImageView(IconResource.getImage(ICON.FLAG));
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
setGraphic(item == null || !item ? null : image);
}
};
}
});
Related
I have create a ComboBox in JavaFx as in the following image
Then i have selected a particular value which shows up in the button cell as shown in the figure.
Then i updated its value so if there is a change in the value i can observe it. But it is not displaying the changed value but an object value.
When i again select from the dropdown view the selected value becomes correct.
The code for the Combobox is
ComboBox<AspectRatio> aspectRatio = new ComboBox<>();
aspectRatio.setCellFactory(new AspectRatioCellFactory());
aspectRatio.setButtonCell(new AspectRatioCell());
Cell Factory
class AspectRatioCellFactory implements Callback<ListView<AspectRatio>, ListCell<AspectRatio>> {
#Override
public ListCell<AspectRatio> call(ListView<AspectRatio> param) {
return new AspectRatioCell();
}
}
class AspectRatioCell extends ListCell<AspectRatio> {
public AspectRatioCell() {
super();
}
#Override
protected void updateItem(AspectRatio item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
setText(item.getAspectRatio());
setGraphic(null);
}
}
}
Updating class
private class RefreshButtonListener implements ButtonListener {
#Override
public void onClick() {
theModel.setObservableAspectRatios(theModel.getAllAspectRatio());
theView.clearCombo();
}
}
How can I disable any cell editable in parent row in treetableview? Please look the pictures and check the sample code. Shortly I want to disable row editable if row is expandable (root row or sub root row)
this picture is correct
but this is not correct
**Example code **
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeTableExample extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
#SuppressWarnings("unchecked")
public void start(Stage stage) {
HBox root = new HBox(createTable());
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Using a TreeTableView");
stage.show();
}
public TreeTableView createTable() {
TreeTableView<Person> treeTable = new TreeTableView<>();
treeTable.setEditable(true);
Callback<TreeTableColumn<Person, String>,
TreeTableCell<Person, String>> cellFactory
= (TreeTableColumn<Person, String> p) -> new EditingCell();
TreeTableColumn<Person, String> firstName = new TreeTableColumn<>("First Name");
firstName.setCellValueFactory(new TreeItemPropertyValueFactory<>("firstName"));
firstName.setCellFactory(cellFactory);
firstName.setOnEditCommit((TreeTableColumn.CellEditEvent<Person, String> event) -> {
if(event.getNewValue()!=null)
event.getRowValue().getValue().setFirstName(event.getNewValue());
});
TreeTableColumn<Person, String> lastName = new TreeTableColumn<>("Last Name");
lastName.setCellValueFactory(new TreeItemPropertyValueFactory<>("lastName"));
lastName.setCellFactory(cellFactory);
lastName.setOnEditCommit((TreeTableColumn.CellEditEvent<Person, String> event) -> {
if(event.getNewValue()!=null)
event.getRowValue().getValue().setLastName(event.getNewValue());
});
treeTable.getColumns().addAll(firstName, lastName);
TreeItem<Person> root = new TreeItem<>();
for (int i = 0; i < 5; i++) {
root.getChildren().add(new TreeItem<>(new Person()));
}
treeTable.setRoot(root);
return treeTable;
}
public class Person {
private SimpleStringProperty firstName;
private SimpleStringProperty lastName;
public Person(){
firstName = new SimpleStringProperty(this, "firstName");
lastName = new SimpleStringProperty(this, "lastName");
};
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
}
class EditingCell extends TreeTableCell<Person, String> {
private TextField textField;
public EditingCell() {
}
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.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(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
setEditable(false);
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.focusedProperty().addListener(
(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) -> {
if (!arg2) {
commitEdit(textField.getText());
}
});
}
private String getString() {
return getItem() == null ? "" : getItem();
}
}
}
just run it and double click on the root item
make-individual-cell-editable-in-javafx-tableview I checked the solution works for tableview but for treetaleview does not work.
It seems that TreeTableCell does not properly check its editable property before deciding whether or not to call startEdit(). I think that's a bug. You can work around it by checking that yourself in your startEdit() method:
#Override
public void startEdit() {
if (isEditable() && !isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
}
and now in your updateItem() method, you can check the current tree item from the row, and update editable as required:
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
TreeItem<Person> treeItem = getTreeTableRow().getTreeItem();
setEditable(treeItem != null && treeItem.isLeaf());
if (empty) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
setEditable(false);
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
Actually I disagree with the reasoning in the other answer: there is nothing wrong with core TreeTableCell (it does check for its editability before actually starting an edit) - instead the logic in the custom cell implementation is broken. Particularly, the part of updateItem that sets the editable property:
} else if (isEditing()) {
if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
setEditable(false);
Besides being incomplete in not resetting the editable back to true anywhere (remember: cells are re-used), we allow super to first start editing and only after it started, it's disabled.
This logic error is fixed (in the other answer, copied here for convenience) by unconditionally setting the editability in updateItem:
super.updateItem(item, empty);
TreeItem<Person> treeItem = getTreeTableRow().getTreeItem();
setEditable(treeItem != null && treeItem.isLeaf());
The other usage error (as already noted) was not fully checking cell state before actually configuring the editor. The suggested fix - check cell's editable - isn't quite complete because table/column editability might be disabled as well. To take that into account, I would tend to let super do its job and only configure the editor if editability actually changed, like
super.startEdit();
// super changed state into editing
if (isEditing()) {
// create and install the textField
}
Here's a simple class, which I'm holding in a javafx ListView
class MyItem {
var active: Boolean = true
def toString = "Test"
}
I've made a custom listcell for MyItem
class MyCell extends ListCell[MyItem] {
override def updateItem(item: MyItem, empty: Boolean): Unit = {
super.updateItem(item, empty)
if(item != null) {
setText(item.toString)
if(item.active) setGraphic(g1) else setGraphic(g2)
}
}
}
I've set this on a listview, but I would like the graphic to change along with the active var. If this were swing, I'd just call repaint when I change it, but this isn't an option.
Is there some way of notifying javafx of the change? Should I be structuring things differently?
Try making var active as BooleanProperty and add a ChangeListener on active. If this is on Java,
Modify model
class MyItem {
private BooleanProperty active;
public MyItem() {
active = new SimpleBooleanProperty();
}
public BooleanProperty activeProperty() {
return active;
}
public void setActive(boolean active) {
this.active.set(active);
}
public boolean isActive() {
return active.get();
}
}
Modify update method:
super.updateItem(item, empty);
if (item != null) {
setText(item.toString());
item.activeProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) setGraphic(g1);
else setGraphic(g2);
});
}
I have two buttons(edit + delete) in one column.
ButtonCell functionButtonCell = new ButtonCell() {
#Override
public void render(final Context context, final SafeHtml data, final SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<button type='button' class='gwt-Button' style = 'width:60px;margin:1px;'>Edit</button>");
sb.appendHtmlConstant("<br/>");
sb.appendHtmlConstant("<button type='button' class='gwt-Button' style = 'width:60px;margin:1px;'>Delete</button>");
}
};
functionColumn = new Column<AdminModel, String>(functionButtonCell) {
public String getValue(final AdminModel object) {
return object.getSeq().toString();
}
};
Bind event for this column in Presenter as
.........
view.getFunctionColumn().setFieldUpdater(new FieldUpdater<AdminModel, String>() {
public void update(final int index, final AdminModel object, final String value) {
Window.alert(index + "-" + value);
}
});
After clicked on edit button , alert-box has appeared , but not on delete button. When I clicked on delete button , nothing has appeared. What would be the problem ?
Addition: How can I decide which button was clicked by user (edit or delete) from my presenter ?
I would really appreciate any of your suggestions because I am troubled on it for a long times. Thanks!
ButtonCell filters events on the first child element only: https://gwt.googlesource.com/gwt/+/2.6.1/user/src/com/google/gwt/cell/client/ButtonCell.java This is why you don't get an event when clicking the second button (note: the goal of that code is to make sure you clicked on the button, and not on blank space around the button; see https://gwt.googlesource.com/gwt/+/a0dc88c8be7408be9554f746eb1ec93798183a28)
The easiest way to implement a two-button cell is to use a CompositeCell; it requires that child cells are rendered into sibling elements though (uses <span>s by default, example below overrides the rendering to use <div>s so your buttons stack each on its own line).
new CompositeCell<AdminModel>(Arrays.asList(
// First button
new HasCell<AdminModel, String>() {
#Override public Cell<String> getCell() { return new ButtonCell(); }
#Override public FieldUpdated<AdminModel, String> getFieldUpdater() {
return new FieldUpdater<AdminModel, String>() {
#Override public void update(int index, AdminModel object, String value) {
Window.alert("Edit " + object.getId());
}
};
}
#Override public String getValue(AdminModel o) {
return "Edit";
}
},
// Second button
new HasCell<AdminModel, String>() {
#Override public Cell<String> getCell() { return new ButtonCell(); }
#Override public FieldUpdated<AdminModel, String> getFieldUpdater() {
return new FieldUpdater<AdminModel, String>() {
#Override public void update(int index, AdminModel object, String value) {
Window.alert("Delete " + object.getId());
}
};
}
#Override public String getValue(AdminModel o) {
return "Delete";
}
}) {
#Override protected <X> void render(Cell.Context context, AdminModel value, SafeHtmlBuilder sb, HasCell<String,X> hasCell) {
// use a <div> instead of the default <span>
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<div>");
cell.render(context, hasCell.getValue(value), sb);
sb.appendHtmlConstant("</div>");
}
};
(note: in your case, because the button's text doesn't depend on the row object, maybe you should rather use an ActionCell; it would better fit "semantically" with what you're doing, but otherwise it's almost the same; with an ActionCell, you'd use HasCell<AdminModel, AdminModel>, ActionCell<AdminModel>, getFieldUpdater would return null, and thegetValueof theHasCellwould just return theAdminModel` argument as-is).
Otherwise, implement your Cell (or AbstractCell) entirely by yourself.
Ideally, a column should have only one type of cell be it ImageCell, ButtonCell etc. Because all this ImageCell and ButtonCell does not provide any in-built events. The events are handled by FieldUpdater itself which does not have differentiators to identify that which ButtonCell is clicked. Ideally on click of that column, the field-updater will be called.
You should rather create your own composite widget which extends HasCell. This composite widget will have two different buttons and those in built methods are called on click of respective button.
public void onModuleLoad() {
CellTable<Person> table = new CellTable<Person>();
List<HasCell<Person, ?>> cells = new LinkedList<HasCell<Person, ?>>();
cells.add(new ActionHasCell("Edit", new Delegate<Person>() {
#Override
public void execute(Person object) {
// EDIT CODE
}
}));
cells.add(new ActionHasCell("Delete", new Delegate<Person>() {
#Override
public void execute(Person object) {
// DELETE CODE
}
}));
CompositeCell<Person> cell = new CompositeCell<Person>(cells);
table.addColumn(new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getName()
}
}, "Name");
// ADD Cells for Age and Address
table.addColumn(new Column<Person, Person>(cell) {
#Override
public Person getValue(Person object) {
return object;
}
}, "Actions");
}
private class ActionHasCell implements HasCell<Person, Person> {
private ActionCell<Person> cell;
public ActionHasCell(String text, Delegate<Person> delegate) {
cell = new ActionCell<Person>(text, delegate);
}
#Override
public Cell<Person> getCell() {
return cell;
}
#Override
public FieldUpdater<Person, Person> getFieldUpdater() {
return null;
}
#Override
public Person getValue(Person object) {
return object;
}
}
Also, see the link below.
[GWT CellTable-Need to have two buttons in last single cell of each row
I have DataGrid where one on of the columns contains images. I used this code to generate the column.
Column<Job, String> expandHideColumn = new Column<Job, String>(
imageCell) {
#Override
public String getValue(Job object) {
return null;
}
#Override
public void render(Context context, Job Object, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<img src='images/expand.jpeg' style='cursor: pointer' />");
}
}
What I want is on clicking the image it has to change. For this I added a click handler on the ImageCell like this
ImageCell imageCell = new ImageCell() {
#Override
public Set<String> getConsumedEvents() {
Set<String> events = new HashSet<String>();
events.add("click");
return events;
}
};
In the onBrowserEvent method I wrote this
#Override
public void onBrowserEvent(Context context, Element element,
Job job, NativeEvent event) {
if (element.getFirstChildElement().isOrHasChild(
Element.as(event.getEventTarget()))) {
if (element.getFirstChildElement().getPropertyString("src")
.matches("(.*)expand.jpeg")) {
element.getFirstChildElement().setPropertyString("src",
"images/collapse.jpeg");
} else {
element.getFirstChildElement().setPropertyString("src",
"images/expand.jpeg");
}
}
}
I don't think this is a good approach to change images on click event. Is there a better solution?
You can use a column value for know the state of the column :
Column<Job, Boolean> expandHideColumn = new Column<Job, Boolean>(new ImageExpandCollapseCell()) {
#Override
public Boolean getValue(Job object) {
return object.isExpand(); //The object know the expand state ?
}
}
expandHideColumn.setValueUpdater(new FieldUpdater<Job, Boolean>() {
void update(int index, Job object, Boolean value) {
object.setExpand(value);
}
});
The ImageExpandCollapseCell look like this :
public class ImageExpandCollapseCell extends AbstractCell<Boolean> {
final String EXPAND = "images/expand.jpeg";
final String COLLAPSE = "images/collapse.jpeg";
interface Template extends SafeHtmlTemplates {
#Template("<div style=\"float:right\"><img src=\"" + url + "\"></div>")
SafeHtml img(String url);
}
private static Template template;
/**
* Construct a new ImageCell.
*/
public ImageCell() {
super("click"); //Replace your getConsumedEvents()
if (template == null) {
template = GWT.create(Template.class);
}
}
#Override
public void render(Context context, Boolean value, SafeHtmlBuilder sb) {
if (value != null) {
sb.append(template.img(UriUtils.fromSafeConstant(value ? EXPAND : COLLAPSE)));
}
}
#Override
public void onBrowserEvent(Context context, Element element,
Boolean value, NativeEvent event, ValueUpdater<Boolean> valueUpdater) {
valueUpdate.update(!value);
}
}
I improve the proposed version of user905374
It's not a good idea to instantiate new value in the render method.
The column render method call the Cell render method, you musn't replace it !
With the FieldUpdater, you can change the state of the image : expand or collapse and update the cell display (it will be rendered again).