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);
}
}
});
}
Related
I wanted to add a column if the line wasn't empty. In this one I wanted to put buttons with different ImageView to be able to go to an add or view page.
I'm trying to change the icon of a button according to the content of my row in a TableView (here if dateR (string) is null)
I manage to move it to such and such a page depending on the dateR value, but I can't change the imageView...
Thanks
TableColumn<ModelTab4, Void> visualiserAjouter = new TableColumn<ModelTab4, Void>("Visualiser/Ajouter");
Callback<TableColumn<ModelTab4, Void>, TableCell<ModelTab4, Void>> cellFactory1 = (final TableColumn<ModelTab4, Void> param) -> {
final TableCell<ModelTab4, Void> cell = new TableCell<ModelTab4, Void>() {
private String dateR;
private final Button btn1 = new Button();
{
btn1.setOnAction(click -> {
try {
ModelTab4 analyse = getTableView().getItems().get(getIndex());
dateR = analyse.getDateR();
setDate(dateR);
idAnalyse = analyse.getNum1();
if (dateR!=null){
mainApp.goConsultResult(idPerso, idPatient, idAnalyse);
}
else {
mainApp.goAjoutResult(idPerso, idPatient, idAnalyse);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void setDate(String date) {
this.dateR = date;
}
public String getDate() {
return dateR;
}
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
}
else {
if (getDate()!=null){
setGraphic(btn1);
ImageView icone1 = new ImageView("consult.png");
icone1.setFitHeight(20);
icone1.setFitWidth(20);
btn1.setGraphic(icone1);
setStyle("-fx-alignment : CENTER;");
}
else if (getDate()==null){
setGraphic(btn1);
ImageView icone2 = new ImageView("ajout.png");
icone2.setFitHeight(20);
icone2.setFitWidth(20);
btn1.setGraphic(icone2);
setStyle("-fx-alignment : CENTER;");
}
}
}
};
return cell;
};
Assuming your ModelTab4 class includes the following:
public class ModelTab4 {
private final StringProperty dateR = new SimpleStringProperty() ;
public StringProperty dateRProperty() {
return dateR ;
}
public final String getDateR() {
return dateRProperty().get();
}
public final void setDateR(String dateR) {
dateRProperty().set(dateR);
}
// other properties ...
}
you should make the value in the visualiserAjouter column the dateR property:
TableColumn<ModelTab4, String> visualiserAjouter = new TableColumn<>("Visualiser/Ajouter");
visualiserAjouter.setCellValueFactory(cellData -> cellData.getValue().dateRProperty());
Now the item in the cell implementation is the dateR property. The issue with the code the way you had it, is that the only way the property changes is after the button is pressed, and there is no notification back to the cell that the value has changed. So in updateItem() you're checking the value of dateR before is has had a chance to change. With the setup above, you can now do:
final TableCell<ModelTab4, Void> cell = new TableCell<ModelTab4, Void>() {
private final Button btn1 = new Button();
{
btn1.setOnAction(click -> {
try {
ModelTab4 analyse = getTableView().getItems().get(getIndex());
idAnalyse = analyse.getNum1();
if (getItem()!=null){
mainApp.goConsultResult(idPerso, idPatient, idAnalyse);
}
else {
mainApp.goAjoutResult(idPerso, idPatient, idAnalyse);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void updateItem(String dateR, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
}
else {
if (dateR!=null){
setGraphic(btn1);
ImageView icone1 = new ImageView("consult.png");
icone1.setFitHeight(20);
icone1.setFitWidth(20);
btn1.setGraphic(icone1);
setStyle("-fx-alignment : CENTER;");
}
else if (dateR==null){
setGraphic(btn1);
ImageView icone2 = new ImageView("ajout.png");
icone2.setFitHeight(20);
icone2.setFitWidth(20);
btn1.setGraphic(icone2);
setStyle("-fx-alignment : CENTER;");
}
}
}
};
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.
I am using a JavaFX tableview that has 5 columns (Data Type of Column): Task Id (int), Task Name (String), Task Description (String), Status (String) and Remark (String). Now, I am loading the data from a database that has the same columns and what I'm trying to do is when the user clicks on the task status column in the software the column cell changes to a Combobox (String) but the list of statuses that are displayed in the Combobox should be different depending on the selected item that the user is trying to edit (aka the status of the task)
I have tried creating a new cellFactory in the OnEdit of the column and I have also tried to override the update item method and using a boolean variable I would set whether to set the graphic to a Combobox or not
myStatusColumn.setOnEditStart(new EventHandler<TableColumn.CellEditEvent<Task, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Task, String> event) {
try {
dataBeingEdited = true;
Task task = myTasksTable.getSelectionModel().getSelectedItem();
statuses = Login.dbHandler.getStatuses(task.getTaskTypeID());
myStatusColumn.setCellFactory(new Callback<TableColumn<Task, String>, TableCell<Task, String>>() {
#Override
public TableCell<Task, String> call(TableColumn<Task, String> param) {
return new TableCell<Task,String>(){
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(empty)
setGraphic(null);
else
{
if(dataBeingEdited)
{
ComboBox<String> comboBox = new ComboBox<>(statuses);
setGraphic(comboBox);
}
}
}
};
}
});
} catch (SQLException e) {
e.printStackTrace();
}
}
});
I expect the output to be a Combobox when I double click on the status column but I am not getting a Combobox to appear and when I get
so far I have not found a way to directly use the ComboBoxTableCell class but what I did is redesigned my own using the CellFactory. What I did was before returning the Cell created in the CellFactory I set an onMouseClickedListner that will check for a double click and when the user double clicks I would get the selected item from the table and set the graphic to be a Combobox with values that depend on the row selected and the column clicked
and then I set an onKeyPressedListener that will change the item selected and then refresh the table and update the database
myStatusColumn.setCellFactory(new Callback<TableColumn<Task, String>, TableCell<Task, String>>() {
#Override
public TableCell<Task, String> call(TableColumn<Task, String> param) {
ComboBox<String> box = new ComboBox<>();
TableCell<Task, String> cell = new TableCell<Task, String>() {
#Override
protected void updateItem(String item, boolean empty) {
if (empty)
setGraphic(null);
else {
setEditable(false);
setText(item);
}
}
};
cell.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton().equals(MouseButton.PRIMARY))
{
Task task = myTasksTable.getSelectionModel().getSelectedItem();
if(task!=null)
box.setItems(FXCollections.observableArrayList(task.getStatuses()));
cell.setEditable(true);
}
if(event.getClickCount()==2 && cell.isEditable() ) {
box.getSelectionModel().select(0);
cell.setText(null);
cell.setGraphic(box);
}
}
});
cell.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode().equals(KeyCode.ENTER))
{
try {
TaskLog taskLog = (TaskLog) myTasksTable.getSelectionModel().getSelectedItem();
if(taskLog != null) {
taskLog.setStatues(box.getSelectionModel().getSelectedItem());
taskLog.setStatuesID(Login.dbHandler.getStatusID(taskLog.getStatues()));
System.out.println(taskLog.getStatues());
Login.dbHandler.addNewTaskLog(taskLog);
cell.setEditable(false);
myTasksTable.refresh();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
});
return cell;
}
});
I have a CheckListView which is displaying a list of custom objects. I am trying to implement a select all/ select none option however the list View is not changing. The checkboxes only change if clicked
pendingUsers_listView.setCellFactory(new Callback<ListView<User>, javafx.scene.control.ListCell<User>>() {
#Override
public ListCell<User> call(ListView<User> listView) {
return new PendingUserCell();
}
});
Custom Cell:
public class PendingUserCell extends CheckBoxListCell<User> {
PendingUserCell() {
this.setSelectedStateCallback(new Callback<User, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(User param) {
return new SimpleBooleanProperty(param.isApproved());
}
});
}
#Override
public void updateItem(User u, boolean empty) {
super.updateItem(u, empty);
if (empty) {
this.setText(null);
} else {
this.setText(u.getFullName() + " ( " + u.getEmail() + " )");
}
}
}
These are the functions responsible for the action
public void handleSelectAll() {
this.pendingUsers_listView.getCheckModel().checkAll();
this.pendingUsers_listView.refresh();
}
public void handleSelectNone() {
this.pendingUsers_listView.getCheckModel().clearChecks();
this.pendingUsers_listView.refresh();
}
here is the solution:
I have a Model class, that holds the information about the items in the ListView, i put there a name, that is displayed in the lisview, and a boolean property that holds the checked/unchecked value like this:
public class Model {
private StringProperty name;
private BooleanProperty selected;
public Model(String name, Boolean selected) {
this.name = new SimpleStringProperty(name);
this.selected = new SimpleBooleanProperty(selected);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public boolean isSelected() {
return selected.get();
}
public BooleanProperty selectedProperty() {
return selected;
}
}
Then I made a custom StringConverter to convert Model to String for displaying the text in listView like this:
public class ModelConverter extends StringConverter<Model> {
#Override public String toString(Model object) {
return object.getName();
}
#Override public Model fromString(String string) {
return null;
}
}
Then in the controller I added some items to the listView and i put as first element Select All, and a listener for it. So if you check it all items became checked, if you uncheck it you all became unchecked. Here is the code:
public class TestController implements Initializable {
#FXML
private ObservableList<Model> data = FXCollections.observableArrayList();
#FXML
private ListView<Model> listView;
#Override
public void initialize(URL location, ResourceBundle resources) {
listView.setItems(data);
listView.setCellFactory(factory -> new CustomListCell(Model::selectedProperty, new ModelConverter()));
data.addAll(new Model("Select All", false), new Model("Apple", false), new Model("Orange", false),
new Model("Peach", false));
data.get(0).selectedProperty().addListener(
(observable, oldValue, newValue) -> data.forEach(item -> item.selectedProperty().set(newValue)));
}
}
So I made as simple as it can be, by using one item from the listView, but you can use many ways to select all select none, for example defining two separate checkboxes outside of listView, or a togglebutton or something else that you want, the important is to add a listener to the respective component that you want to use it for select all select none, then everything became easy I think.
I have created a cellList :
I want to add a clickhandler when user clicks on button "Send"
Please Help. FieldUpdater should work if user clicks on "Send" button.
Here is the code :
final String imageHtml =AbstractImagePrototype.create(images.contact()).getHTML();
// first make a list of HasCell type - MyClass is the type of object being displayed in the CellList (could be String for simple labels)
List<HasCell<contactinfo, ?>> hasCells = new ArrayList<HasCell<contactinfo, ?>>();
hasCells.add(new HasCell<contactinfo, String>()
{
public ButtonCell cell = new ButtonCell();
public Cell<String> getCell()
{
return cell;
}
#Override
public String getValue(contactinfo object)
{
return "Send";
}
#Override
public FieldUpdater<contactinfo, String> getFieldUpdater() {
FieldUpdater< contactinfo, String > updater= new FieldUpdater<contactinfo, String>() {
#Override
public void update(int index, contactinfo object, String value) {
Window.alert("You clicked "+object.getName());
}
};
return updater;
}
}
);
// now construct the actual composite cell using the list (hasCells)
Cell<contactinfo> myClassCell = new CompositeCell<contactinfo>(hasCells)
{
#Override
public void render(Context context, contactinfo value, SafeHtmlBuilder sb)
{
sb.appendHtmlConstant("<table><tbody><tr>");
super.render(context, value, sb);
sb.appendHtmlConstant("</tr></tbody></table>");
}
#Override
protected Element getContainerElement(Element parent)
{
// Return the first TR element in the table.
return parent.getFirstChildElement().getFirstChildElement();
}
#Override
protected <X> void render(Context context, contactinfo contactinfo, SafeHtmlBuilder sb, HasCell<contactinfo, X> hasCell)
{
this renders each of the cells inside the composite cell in a new table cell
// Value can be null, so do a null check..
if (contactinfo == null) {
return;
}
sb.appendHtmlConstant("<table>");
// Add the contact image.
sb.appendHtmlConstant("<tr><td rowspan='3'>");
sb.appendHtmlConstant(imageHtml);
sb.appendHtmlConstant("</td>");
// Add the name and address.
sb.appendHtmlConstant("<td style='font-size:95%;'>");
if(contactinfo.getName()!=null)
sb.appendEscaped(contactinfo.getName());
sb.appendHtmlConstant("</td></tr><tr><td>");
if(contactinfo.getAddress()!=null)
sb.appendEscaped(contactinfo.getRemarks());
sb.appendHtmlConstant("</td>");
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<td>");
cell.render(context, hasCell.getValue(contactinfo), sb);
sb.appendHtmlConstant("</td></tr></table>");
}
};
// then make the actual cellList, passing the composite cell
cellList =new CellList<contactinfo>(myClassCell,KEY_PROVIDER);
// Add a selection model so we m select cells.
singleselectionModel = new SingleSelectionModel<contactinfo>(
KEY_PROVIDER);
cellList.setSelectionModel(singleselectionModel);
singleselectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
#Override
public void onSelectionChange(SelectionChangeEvent event) {
}
});
Also, I do not see in code any piece that handles event. Did you read through http://www.gwtproject.org/doc/latest/DevGuideUiCustomCells.html#cell-onBrowserEvent
Have you tried the code sample provided by GWT - http://gwt.googleusercontent.com/samples/Showcase/Showcase.html#!CwCellSampler . Browse the "Source Code" !!!
If you have not read already then you should start here # DevGuideUiCustomCells