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;
}
}
I want to sort celltable column, have written so far this, but it doesn't seem to be working, i did it all like in gwt showcase with celltable column sorting:
ListHandler<M> sort;
public View() {
getM(0,m);
createM();
}
void createM() {
Column<M, String> firstColumn = new Column<M, String>(
new TextCell()) {
#Override
public String getValue(M object) {
return object.getName();
}
};
table.addColumn(firstColumn,"Name");
firstColumn.setSortable(true);
sort.setComparator(firstColumn, new Comparator<M>() {
#Override
public int compare(M m1, M m2) {
return m1.getName().compareTo(m2.getName());
}
});
void getM(int dataID, M m) {
final ListDataProvider<M> listProvider = new ListDataProvider<M>();
listProvider.addDataDisplay(table);
listProvider.refresh();
final List<M> mList = listProvider.getList();
sort = new ListHandler<M>(mList);
AsyncCallback<List<M>> callback = new AsyncCallback<List<M>>() {
#Override
public void onFailure(Throwable caught) {
// TODO Auto-generated method stub
}
#Override
public void onSuccess(final List<M> result) {
for (final M m : result) {
mList.add(m);
}
table.addColumnSortHandler(sort);
}
};
rpcService.getDataSource(dataID, m, callback);
any suggestion, what is wrong here? How can i solve this?
i moved my code from getM() into createM() like:
void createM() {
final ListDataProvider<M> listProvider = new ListDataProvider<M>();
listProvider.addDataDisplay(table);
listProvider.refresh();
final List<M> mList = listProvider.getList();
sort = new ListHandler<M>(mList);
Column<M, String> firstColumn = new Column<M, String>(
new TextCell()) {
#Override
public String getValue(M object) {
return object.getName();
}
};
table.addColumn(firstColumn,"Name");
firstColumn.setSortable(true);
sort.setComparator(firstColumn, new Comparator<M>() {
#Override
public int compare(M m1, M m2) {
return m1.getName().compareTo(m2.getName());
}
});
and it sort the column, but if i choose the next data to show in column it will be added to the existing items in column, can anyone point me why it is so?
The first example is not working because you re-initialize your sort object:
sort = new ListHandler<M>(mList);
This removes all settings from sort.
In the second example, you need to call
mList.clear();
before you add new items to this list. Also, move
table.addColumnSortHandler(sort);
from the callback to your create method. There is no need to call it every time you add data.
So i have here a Cellbutton :
Column<dateiles, String> column_2 = new Column<dateiles, String>(new ButtonCell()) {
#Override
public String getValue(dateiles object) {
int s = object.comments.size();
String ss = String.valueOf(s);
return ss;
}
};
cellTable.addColumn(column_2, "Comments");
cellTable.setColumnWidth(column_2, "100px");
i want to add to my buttons for each cell column a Tooltip but how is it possible
this.addCellPreviewHandler(new Handler<Tabletype>() {
#Override
public void onCellPreview(final CellPreviewEvent<Tabletype> event) {
int columnID = event.getColumn();
}
});
}
Just replace tabletype with the object you definded when instantiating the table ( The T of CellTable<T> ) Then with the columnID you can detect the right column.
I have a ValueAwareEditor that contains a couple of sub editors:
Essentially, an OfferDto is composed of a TariffDto and a Commission. The Commission can be one of 4 sub-types, but there is only ever one. Usually this list of possible commissions inside the TariffDto will only contain one element, but it can sometimes contain two.
public class OfferDto
{
private TariffDto tariff;
// selected from the list in the tariff
private Commission commission;
}
public class TariffDto extends EntityDto
{
// omitted for brevity...
protected List<Commission> commissions = new ArrayList<Commission>();
}
When commissions contains more than one item, I want to display a dropdown with the two optiions, and add allow the user to choose between them, each time resetting the commission in the OfferDto and the CommissionEditor.
The problem is that, when call commission.setValue() for the second time, the editor does not change. What should I be doing here?
public class OfferEditor extends Composite implements ValueAwareEditor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Ignore
#UiField
HTMLPanel panel;
#UiField
CommissionEditor commission;
#Override
public void setValue(final OfferDto value)
{
panel.clear();
List<Commission> commissions = value.getTariff().getCommissions();
if(commissions.size() == 1)
{
value.setCommission(commissions.get(0));
}
else
{
// multiple commissions
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdown.setValue(value.getCommission());
dropdown.setAcceptableValues(commissions);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
// this works, but the CommissionEditor that was first rendered remains
value.setCommission(selected);
}
});
panel.add(dropdown);
}
}
}
Currently, I am rendering the list of commissions in a ValueListBox, then when the value changes I am pushing that value to the OfferDto. The Commission seems to get set right, but the subEditor does not change.
Any help greatly appreciated.
EDIT:
CommissionEditor shows the relevant sub-editor depending on the type.
public class CommissionEditor extends Composite implements Editor<Commission>
{
private static CommissionEditorUiBinder uiBinder = GWT.create(CommissionEditorUiBinder.class);
interface CommissionEditorUiBinder extends UiBinder<Widget, CommissionEditor>
{
}
#UiField
Panel subEditorPanel;
public CommissionEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Ignore
final UnitRateCommissionEditor unitRateCommissionEditor = new UnitRateCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor> unitRateCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor>(
unitRateCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof UnitRateCommission)
{
setValue(value, value instanceof UnitRateCommission);
System.out.println("UnitRateCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(unitRateCommissionEditor);
}
}
};
#Ignore
final StandingChargeCommissionEditor standingChargeCommissionEditor = new StandingChargeCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor> standingChargeCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor>(
standingChargeCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof StandingChargeCommission)
{
setValue(value, value instanceof StandingChargeCommission);
System.out.println("StandingChargeCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(standingChargeCommissionEditor);
}
}
};
#Ignore
final PerMwhCommissionEditor perMwhCommissionEditor = new PerMwhCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor> perMwhCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor>(
perMwhCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof PerMwhCommission)
{
setValue(value, value instanceof PerMwhCommission);
System.out.println("PerMwhCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(perMwhCommissionEditor);
}
}
};
}
Possible Solution:
I changed OfferEditor as so:
public class OfferEditor extends Composite implements Editor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Path("tariff.commissions")
#UiField
CommissionsEditor commission;
}
New editor CommissionsEditor is a CompositeEditor. It needs to take List tariff.commissions and set the chosen Commission into offer.commission:
public class CommissionsEditor extends Composite implements CompositeEditor<List<Commission>, Commission, CommissionEditor>
{
private static CommissionsEditorUiBinder uiBinder = GWT.create(CommissionsEditorUiBinder.class);
interface CommissionsEditorUiBinder extends UiBinder<Widget, CommissionsEditor>
{
}
private EditorChain<Commission, CommissionEditor> chain;
#UiField
FlowPanel dropdownPanel, subEditorPanel;
#Ignore
CommissionEditor subEditor;
public CommissionsEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public void setValue(List<Commission> valueList)
{
// clear both panels
dropdownPanel.clear();
subEditorPanel.clear();
if(valueList.size() == 1)
{
// set the commission to the first in the list
Commission selected = valueList.get(0);
subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
else if(valueList.size() > 1)
{
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdownPanel.add(dropdown);
dropdown.setValue(valueList.get(0));
dropdown.setAcceptableValues(valueList);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
subEditorPanel.clear();
CommissionEditor subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
});
}
}
#Override
public void flush()
{
}
#Override
public void onPropertyChange(String... paths)
{
// TODO Auto-generated method stub
}
#Override
public void setDelegate(EditorDelegate<List<Commission>> delegate)
{
// TODO Auto-generated method stub
}
#Override
public CommissionEditor createEditorForTraversal()
{
return new CommissionEditor();
}
#Override
public String getPathElement(CommissionEditor subEditor)
{
return null;
}
#Override
public void setEditorChain(EditorChain<Commission, CommissionEditor> chain)
{
this.chain = chain;
}
}
When the CommissionsEditor renders the dropdown and onValueChange() is called, the new editor gets created, but the value for the commission never seems to get set.
For some reason the selected subEditor's value is not pushed into offer.setCommission(). I thought chain.attach() would perform this for me?
I have a Cell Table of which the last column is a checkbox. I would like to add a Check-All-Box as a footer that when clicked selects every checkbox for every row. Somehow it won't work here is what I got so far:
Column<Object, Boolean> select = new Column<Object, Boolean>(new CheckboxCell()) {
#Override
public Boolean getValue(Object object) {
return msm.isSelected(object);
}
};
select.setFieldUpdater(new FieldUpdater<Object, Boolean>() {
public void update(int index, Object object, Boolean value) {
msm.setSelected(object, value);
}
});
final Header selectAllHeader = new Header(new CheckboxCell()) {
#Override
public Boolean getValue(){
return msm.getSelectedSet().size() == getRowCount();
}
};
selectAllHeader.setUpdater(new ValueUpdater<Boolean>() {
#Override
public void update(Boolean value) {
for (Object o : getVisibleItems) {
msm.setSelected(o, value);
}
}
});
//works
addColumn(select, selectAllHeader);
//does not work
//addColumn(select, HEADER, selectAllHeader);
I used a solution proposed here:
http://code.google.com/p/google-web-toolkit/issues/detail?id=7014
It works nicely. You can use it in your footer.