I've got a project written in JavaFX and I'm trying to get a refresh on a tableview without result.
I've googled around and tried some examples I've found but it still doesn't work.
I populate a tableview with information each row in this table can have new comments added to by double click on the row. The a new Tabpane is opened and the new comment can be added there. On close of this tabpane I'd like the one I clicked from to be refreshed.
I must be doing something wrong. I just don't know what.
In my StoreController
private void populateTableView(List<Store> stores) {
ObservableList<Store> data = FXCollections.observableArrayList(stores);
storeNumberColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("id"));
storePhoneColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("phoneNbr"));
chainColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("chainId"));
commentColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Store, ImageView>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Store, ImageView> p) {
Integer numberOfComments = p.getValue().getCommentsCount();
ReadOnlyObjectWrapper wrapper = null;
if (numberOfComments == 0) {
wrapper = null;
} else if (numberOfComments == 1) {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_SINGLE_FLAG_SOURCE));
} else {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_DOUBLE_FLAG_SOURCE));
}
return wrapper;
}
});
storeTable.setItems(data);
sortTable(storeTable, missedColumn);
}
#FXML
public void handleTableAction(MouseEvent event) {
if (event.getClickCount() == 2) {
showNewCommentStage();
}
}
private void showNewCommentStage() {
initCommentController();
Store store
= storeTable.getSelectionModel().selectedItemProperty().getValue();
commentController.showNewStage(commentPane, store);
}
It seems like the call-function doesn't get called when the commentpane is closed.
CommentController
public void showNewStage(Pane pane, Store store) {
this.store = store;
initStage(pane);
windowHandler = new WindowHandler(stage);
effectHandler.playEffect(pane);
constructCommentHeaders();
List<Comment> comments;
comments = commentService.listByStoreId(store.getId());
populateCommentTable(comments);
}
Like I said I've tried a lot of the solutions found here on Stackoverflow but with no results. The Tableview doesn't refresh. The Stores and the Comments are in different database tables if that's important
Can someone point me in the right direction?
Thanks!
****EDIT****
The Store.class
public class Store extends CommentEntity {
private String id;
private String chainId;
private String phoneNbr;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
public String getPhoneNbr() {
return phoneNbr;
}
public void setPhoneNbr(String phoneNbr) {
this.phoneNbr = phoneNbr;
}
#Override
public String toString() {
return "Store{" + "id=" + id + ", chainId=" + chainId + '}';
}
#Override
public String getCommentIdentifier() {
return id;
}
}
The CommentEntity.Class
public abstract class CommentEntity {
private int commentsCount;
public int getCommentsCount() {
return commentsCount;
}
public void setCommentsCount(int commentsCount) {
this.commentsCount = commentsCount;
}
public abstract String getCommentIdentifier();
}
Thank you for input, I hadn't even reflected over the ImageView / String.
Two issues:
First, you need to distinguish between the data the cells in your column are displaying, and the cells that actually display those data. The cellValueFactory determines the data that are displayed. The PropertyValueFactory is a cellValueFactory implementation that references a JavaFX Property, so when you call
storeNumberColumn.setCellValueFactory(new PropertyValueFactory<Store, String>("id"));
it effectively tells the cells in the storeNumberColumn to call the idProperty() method on the Store object in the current row to get the data for the cell. (If no such method exists, it will try to use getId() as a backup plan.)
By default, you get a cellFactory that displays text resulting from calling toString() on the data generated by the cellValueFactory. In the case where your data are simply Strings, this is usually what you need. In other cases, you often need to provide a cellFactory of your own to get the correct way to display the data.
In your case, the data for the commentColumn are simply the number of comments. You are going to display that by choosing an image based on that numeric value.
So you should have
TableColumn<Store, Number> commentColumn = new TableColumn<>("Comments");
For the cellValueFactory, you can just use
commentColumn.setCellValueFactory(new PropertyValueFactory<>("commentsCount"));
Then you need a cellFactory that displays the appropriate ImageView:
commentColumn.setCellFactory(new Callback<TableColumn<Store, Number>, new TableCell<Store, Number>>() {
#Override
public TableCell<Store, Number>() {
private ImageView imageView = new ImageView();
#Override
public void updateItem(Number numberOfComments, boolean empty) {
super.updateItem(count, empty) ;
if (empty) {
setGraphic(null);
} else {
if (numberOfComments.intValue() == 0) {
setGraphic(null);
} else if (numberOfComments.intValue() == 1) {
imageView.setImage(new Image(COMMENT_SINGLE_FLAG_SOURCE));
setGraphic(imageView);
} else {
imageView.setImage(new Image(COMMENT_DOUBLE_FLAG_SOURCE));
setGraphic(imageView);
}
}
}
}
});
The second issue is actually about the update. A TableView keeps its contents "live" by observing JavaFX properties that are provided by the cellValueFactory as ObservableValues. If the value might change while the table is displayed, you must provide an actual property that can be observed: using a ReadOnlyObjectWrapper is no good (because it's read only, so it's wrapped value will not change). The PropertyValueFactory will also return a ReadOnlyObjectWrapper if you do not have JavaFX property accessor methods (i.e. if it is only using getXXX() methods to access the data). So your model class must provide JavaFX Properties.
You can make an immediate fix to this by updating CommentEntity to use an IntegerProperty:
public abstract class CommentEntity {
private final IntegerProperty commentsCount = new SimpleIntegerProperty();
public final int getCommentsCount() {
return commentsCountProperty().get();
}
public final void setCommentsCount(int commentsCount) {
commentsCountProperty().set(commentsCount);
}
public IntegerProperty commensCountProperty() {
return commentsCount ;
}
public abstract String getCommentIdentifier();
}
I would also strongly recommend updating the Store class to use JavaFX Properties in a similar manner.
Related
cmbSablonSecim = new ComboBox<>();
cmbSablonSecim.setItems(EnumSablonSecim.values());
My combo box --> TUMU,GRAFIK,DAGILIM
I want fill my combobox with Enum->islemAdi
Combobox --> Tümü,Grafik,Dağılım (Enum->islemAdi)
public enum EnumSablonSecim {
TUMU(0, "Tümü"),
GRAFIK(1, "Grafik"),
DAGILIM(2, "Dağılım")
;
private final Integer islemKodu;
private final String islemAdi;
private EnumSablonSecim(Integer islemKodu, String islemAdi) {
this.islemKodu = islemKodu;
this.islemAdi = islemAdi;
}
public Integer getIslemKodu() {
return islemKodu;
}
public String getIslemAdi() {
ResourceBundle messages = I18n.getInstance(this.getClass());
if (messages.containsKey(islemAdi)) {
return messages.getString(islemAdi);
} else {
return islemAdi;
}
}
public static EnumSablonSecim get(Integer islemKodu) {
for (EnumSablonSecim enumSablonSecim : EnumSablonSecim.values()) {
if (enumSablonSecim.islemKodu == islemKodu) {
return enumSablonSecim;
}
}
return null;
}
}
My combobox must return (islemAdi).is it possible or not? Thank you...
ComboBox::setItemLabelGenerator
Are you asking if you can show the islemAdi field as the label in the combo box?
You can specify code to generate a label used for displaying each item in your enum. Call ComboBox::setItemLabelGenerator. Pass a method reference for your getter. Vaadin then calls this method as needed to display each item.
cmbSablonSecim.setItemLabelGenerator(EnumSablonSecim::getIslemAdi);
See Showing a List of Data with Data Providers in the manual.
Here's the situation: I have an ObservableSet (because the ID field for my data must be unique) that has a listener. That listener updates an ObservableList. That ObservableList is, in turn, listened to by a TableView. (According to the comments, this is all necessary because ObservableSet can't be used to back a TableView in JavaFX.)
What we're finding, however, is that doing multiple add operations to the Set don't trigger the refresh of the TableView.
This works fine for:
Initial list population
Duplicate set entries (the listener isn't triggered, that's the intended behavior)
However, editing the value, the TableView will only trigger if there's a single add statement (in the example below, commenting out markStructure.add(new MarkStructureItem(2, 15)); makes it work fine, but then you only have a single value). If there's more than one, the TableView doesn't refresh.
This can be worked around by adding a manual refresh at the end of the Listener class (tblTable.refresh()). This is actually how it's functioned up until now, but development now requires the removal of that.
Any idea what's going on? Why is the listener on the TableView not getting triggered for subsequent adds? The data IS being put into the Set and List, that much has been determined; it's just not triggering a refresh.
Code example (not production, but a POC I put together to test and get answers):
public class TestController implements Initializable {
ObservableSet<MarkStructureItem> markStructure = FXCollections.observableSet();
ObservableList<MarkStructureItem> listMarkStructure = FXCollections.observableArrayList();
#FXML
private Pane root;
#FXML
private TableView<MarkStructureItem> tblTable;
#FXML
private TableColumn<MarkStructureItem, Integer> col1;
#FXML
private TableColumn<MarkStructureItem, Integer> col2;
#FXML
private Button btnButton;
private void resetAll() {
markStructure.clear();
markStructure.add(new MarkStructureItem(1, 25));
markStructure.add(new MarkStructureItem(2, 15));
}
#FXML
private void handleButtonAction(ActionEvent event) throws IOException {
Object source = event.getSource();
Button btnSource = (Button) source;
Stage stage = (Stage) root.getScene().getWindow();
switch (btnSource.getId()) {
case "btnButton": {
resetAll();
break;
}
}
}
class MarksUpdater<MarkStructureItem extends configurationeditor.MarkStructureItem> implements SetChangeListener {
#Override
public void onChanged(Change change) {
if (change.wasRemoved()) {
listMarkStructure.remove(change.getElementRemoved());
} else if (change.wasAdded()) {
MarkStructureItem newMarks = (MarkStructureItem) change.getElementAdded();
listMarkStructure.add(newMarks);
}
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
markStructure.addListener(new MarksUpdater<MarkStructureItem>());
col1.setCellValueFactory(
new PropertyValueFactory<MarkStructureItem, Integer>("id")
);
col2.setCellValueFactory(
new PropertyValueFactory<MarkStructureItem, Integer>("marks")
);
col2.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
col2.setOnEditCommit(
new EventHandler<CellEditEvent<MarkStructureItem, Integer>>() {
#Override
public void handle(CellEditEvent<MarkStructureItem, Integer> t) {
((MarkStructureItem) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setMarks(t.getNewValue());
}
}
);
tblTable.setItems(listMarkStructure);
resetAll();
}
}
markStructure class:
public class MarkStructureItem {
final SimpleIntegerProperty marks;
final SimpleIntegerProperty id;
#Override
public int hashCode() {
return this.getId().hashCode();
}
#Override
public boolean equals(Object obj) {
if (obj.getClass() == this.getClass()) {
MarkStructureItem thisObj = (MarkStructureItem) obj;
return thisObj.getId() == this.getId();
}
return false;
}
public MarkStructureItem(Integer finishPosition, Integer marks) {
this.marks = new SimpleIntegerProperty(marks);
this.id = new SimpleIntegerProperty(finishPosition);
}
public Integer getId() {
return id.get();
}
public void setMarks(Integer value) {
marks.set(value);
}
public Integer getMarks() {
return marks.get();
}
public void setId(Integer value) {
id.set(value);
}
}
Issue 1:
Your equals implementation is not null-safe and you're comparing the Integers using reference equality (==) instead of equals. This will probably work as long as you're inside the range of cached values and use autoboxing, but cease to work outside that range (this range is only guarantied to be -128 to 127, see Integer.valueOf(int)).
Since you're using the Object in a Set, I recommend making the id unmodifiable anyways. Otherwise you need to remove the value from the set before modifying it and adding it back to the Set (the hash code changes), which would mess up the order in the list unless you temporarily prevent the SetChangeListener from updating the list.
Also using a primitive type will prevent issues with ==.
Issue 2:
For any changes in the marks property to be visible in the TableView, you need to provide a marksProperty() method returning the property itself. This is necessary for the TableView to listen to changes of the property.
public class MarkStructureItem {
final SimpleIntegerProperty marks;
final int id;
#Override
public int hashCode() {
return id;
}
#Override
public boolean equals(Object obj) {
return obj != null
&& obj.getClass() == this.getClass()
&& this.id == ((MarkStructureItem) obj).id; // primitive type can be compared using ==
// && this.getId().equals(((MarkStructureItem) obj).getId()); // alternative for non-primitive types
}
public MarkStructureItem(int finishPosition, Integer marks) {
this.marks = new SimpleIntegerProperty(marks);
this.id = finishPosition;
}
public int getId() {
return id;
}
public IntegerProperty marksProperty() {
return marks;
}
How do you refresh the data in your tableView on button Press using FXML?
I have the following file structure and I want to refresh the data in this table when a button is pressed. Would anyone know a solution?
public class MyTable {
private final SimpleStringProperty ID = new SimpleStringProperty("");
private final SimpleStringProperty ParticipantID = new SimpleStringProperty("");
public Positions() {
this("", "")
}
public Positions(String ID, String ParticipantID) {
setMemberID(ID);
setParticipantID(ParticipantID);
}
public String getParticipantID() {
return ParticipantID.get();
}
public void setParticipantID(String pID) {
ParticipantID.set(ParticipantID);
}
public String getID() {
return ID.get();
}
public void ID(String cID) {
ID.set(ID);
}
}
I initialise this table on the tablecontroller file for this. Now on button press I would like the tableview which is an FXML file update itself. How do I do this?
Thanks but the solution to this was to have one global ObservableList<> data which you then modify on a button press action event. What I was trying to do was create another observable list which does not work.
If you want to update only on button press and not as data is modified that is the default way ?
The simplest way to do would be to create a bean to which all data is updated and the make the button synchronize it with the bean that represents a row in your table.
public class TableBean
{
MyTable child;
String Id;
String ParticipantId;
public void Sync()
{
child.Id(Id);
child.setParticipantID(ParticipantId);
}
}
It is important to note that your methods
violate JavaFX convetion , this probably brakes things. An example of 3 methods used for every propery in JavaFX.
private final IntegerProperty ratio = new SimpleIntegerProperty();
public int getRatio() {
return ratio.get();
}
public void setRatio(int value) {
ratio.set(value);
}
public IntegerProperty ratioProperty() {
return ratio;
}
Trying to make my CellTable Colum sortable but I'm not getting it to work. I'm having an MVP application which gets data from a rest service. To show the data within the table works fine but to sort is doesn't work.
public class LicenseUsageUserViewImpl<T> extends Composite implements LicenseUsageUserView<T> {
#UiTemplate("LicenseUsageUserView.ui.xml")
interface LicenseDataViewUiBinder extends UiBinder<ScrollPanel,LicenseUsageUserViewImpl> {}
private static LicenseDataViewUiBinder uiBinder = GWT.create(LicenseDataViewUiBinder.class);
#UiField
CellTable<GWTLicenseUser> licenseUserCellTable;
List<GWTLicenseUser> licenseUsers;
ListDataProvider<GWTLicenseUser> dataProvider;
public List<GWTLicenseUser> getLicenseUsers() {
return licenseUsers;
}
public void setLicenseUsers(List<GWTLicenseUser> licenseUsers) {
this.licenseUsers = licenseUsers;
}
#UiField Label header;
ListHandler<GWTLicenseUser> sortHandler;
public LicenseUsageUserViewImpl() {
initWidget(uiBinder.createAndBindUi(this));
initCellTable();
}
#Override
public void setLicenseUsersTable(List<GWTLicenseUser> tmpLicenseUsers) {
if (tmpLicenseUsers.isEmpty()) {
licenseUserCellTable.setVisible(false);
} else {
setLicenseUsers(tmpLicenseUsers);
licenseUserCellTable.setWidth("100%");
licenseUserCellTable.setVisible(true);
licenseUserCellTable.setPageSize(getLicenseUsers().size());
licenseUserCellTable.setRowCount(getLicenseUsers().size(), false);
licenseUserCellTable.setRowData(0, getLicenseUsers());
licenseUserCellTable.setVisibleRange(new Range(0, licenseUserCellTable.getRowCount()));
sortHandler.setList(getLicenseUsers());
dataProvider.getList().clear();
dataProvider.getList().addAll(getLicenseUsers());
}
}
#Override
public void initCellTable() {
sortHandler = new ListHandler<GWTLicenseUser>(getLicenseUsers());
licenseUserCellTable.addColumnSortHandler(sortHandler);
licenseUserCellTable.setWidth("100%");
licenseUserCellTable.setVisible(true);
licenseUserCellTable.setVisibleRange(new Range(0, licenseUserCellTable.getRowCount()));
// Create a data provider.
dataProvider = new ListDataProvider<GWTLicenseUser>();
// Connect the table to the data provider.
dataProvider.addDataDisplay(licenseUserCellTable);
licenseUserCellTable.setWidth("100%");
licenseUserCellTable.setAutoHeaderRefreshDisabled(true);
licenseUserCellTable.setAutoFooterRefreshDisabled(true);
// userID
TextColumn<GWTLicenseUser> userIdColumn = new TextColumn<GWTLicenseUser>() {
#Override
public String getValue(GWTLicenseUser object) {
if (object != null ){
return object.getUserId();
} else {
return "NULL";
}
}
};
userIdColumn.setSortable(true);
sortHandler.setComparator(userIdColumn, new Comparator<GWTLicenseUser>() {
#Override
public int compare(GWTLicenseUser o1, GWTLicenseUser o2) {
return o1.getUserId().compareTo(o2.getUserId());
}
});
licenseUserCellTable.addColumn(userIdColumn, "User ID");
// more column entries
licenseUserCellTable.getColumnSortList().push(userIdColumn);
licenseUserCellTable.getColumnSortList().push(countColumn);
licenseUserCellTable.addColumnSortHandler(sortHandler);
}
}
setLicenseUsersTable is called from my activity with the response list of my users. When I start my application and make a rest call my data is provide and put into my list also shown within the CellTable but its not sortable, but I have this sort icon before my colum name. I figured I post the whole code because I think its know easier to see what I'm trying to do.
Thanks for any help.
Remove this line:
sortHandler.setList(getLicenseUsers());
You already passed a List into the SortHandler constructor in
sortHandler = new ListHandler<GWTLicenseUser>(getLicenseUsers());
Also, instead of
setLicenseUsers(tmpLicenseUsers);
you may need to use
licenseUsers.addAll(tmpLicenseUsers);
I hope one of them fixes the problem.
I have an object, Supply, that can either be an ElecSupply or GasSupply (see related question).
Regardless of which subclass is being edited, they all have a list of BillingPeriods.
I now need to instantiate N number of BillingPeriodEditors based on the contents of that list, and am pretty baffled as to how I should do it.
I am using GWTP. Here is the code of the SupplyEditor I have just got working:
public class SupplyEditor extends Composite implements ValueAwareEditor<Supply>
{
private static SupplyEditorUiBinder uiBinder = GWT.create(SupplyEditorUiBinder.class);
interface SupplyEditorUiBinder extends UiBinder<Widget, SupplyEditor>
{
}
#Ignore
final ElecSupplyEditor elecSupplyEditor = new ElecSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor> elecSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor>(
elecSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof ElecSupply);
if(!(value instanceof ElecSupply))
{
showGasFields();
}
else
{
showElecFields();
}
}
};
#Ignore
final GasSupplyEditor gasSupplyEditor = new GasSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor> gasSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor>(
gasSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof GasSupply);
if(!(value instanceof GasSupply))
{
showElecFields();
}
else
{
showGasFields();
}
}
};
#UiField
Panel elecPanel, gasPanel, unitSection;
public SupplyEditor()
{
initWidget(uiBinder.createAndBindUi(this));
gasPanel.add(gasSupplyEditor);
elecPanel.add(elecSupplyEditor);
}
// functions to show and hide depending on which type...
#Override
public void setValue(Supply value)
{
if(value instanceof ElecSupply)
{
showElecFields();
}
else if(value instanceof GasSupply)
{
showGasFields();
}
else
{
showNeither();
}
}
}
Now, as the list of BillingPeriods is a part of any Supply, I presume the logic for this should be in the SupplyEditor.
I got some really good help on the thread How to access PresenterWidget fields when added dynamically, but that was before I had implemented the Editor Framework at all, so I think the logic is in the wrong places.
Any help greatly appreciated. I can post more code (Presenter and View) but I didn't want to make it too hard to read and all they do is get the Supply from the datastore and call edit() on the View.
I have had a look at some examples of ListEditor but I don't really get it!
You need a ListEditor
It depends of how you want to present them in your actual view, but the same idea apply:
public class BillingPeriodListEditor implements isEditor<ListEditor<BillingPeriod,BillingPeriodEditor>>, HasRequestContext{
private class BillingPeriodEditorSource extends EditorSource<BillingPeriodEditor>{
#Override
public EmailsItemEditor create(final int index) {
// called each time u add or retrive new object on the list
// of the #ManyToOne or #ManyToMany
}
#Override
public void dispose(EmailsItemEditor subEditor) {
// called each time you remove the object from the list
}
#Override
public void setIndex(EmailsItemEditor editor, int index) {
// i would suggest track the index of the subeditor.
}
}
private ListEditor<BillingPeriod, BillingPeriodEditor> listEditor = ListEditor.of(new BillingPeriodEditorSource ());
// on add new one ...
// apply or request factory
// you must implement the HasRequestContext to
// call the create.(Proxy.class)
public void createNewBillingPeriod(){
// create a new one then add to the list
listEditor.getList().add(...)
}
}
public class BillingPeriodEditor implements Editor<BillingPeriod>{
// edit you BillingPeriod object
}
Then in you actual editor edit as is in the path Example getBillingPeriods();
BillingPeriodListEditor billingPeriods = new BillingPeriodListEditor ();
// latter on the clickhandler
billingPeriods.createNewBillingPeriod()
You are done now.