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.
Related
As you can see from the image below I want to select something from my table ( which changes whenever I press a button from the vertical box to the left i.e "Overview", "Orders" ... ) and delete the record from an array ( i.e. where the content comes from ).
The method I approached bellow works but it is not elegant since I have to create at most 8 if statements for each button id. Is there any way to delete the content dynamically. Is there any way for the JVM to figure out which record belongs to which array list?
TableController
#FXML
private TableView<Object> defaultTableView;
public void delete(){
if( MockServer.getServer().currentButton.equals("btnIngredients"))
MockServer.getServer().removeIngredient(defaultTableView.getSelectionModel().getSelectedItem());
else if ( MockServer.------.equals("btnOrders"))
MockServer.getServer().removeOrder(defaultTableView.getSelectionModel().getSelectedItem());
}
Controller
#FXML
private TableController tableController;
#FXML
public void deleteRecord(ActionEvent event){
tableController.delete();
}
MockServer
public class MockServer implements ServerInterface {
public Restaurant restaurant;
public ArrayList<Dish> dishes = new ArrayList<Dish>();
public ArrayList<Drone> drones = new ArrayList<Drone>();
public ArrayList<Ingredient> ingredients = new ArrayList<Ingredient>();
public ArrayList<Order> orders = new ArrayList<Order>();
public ArrayList<Staff> staff = new ArrayList<Staff>();
public MockServer(){}
public ArrayList<Ingredient> getIngredients() { return this.ingredients; }
public ArrayList<Order> getOrders() { return this.orders; }
public ArrayList<Staff> getStaff() { return this.staff; }
....
static public ServerInterface getServer(){
return server;
}
#Override
public void removeIngredient(Ingredient ingredient) {
int index = this.ingredients.indexOf(ingredient);
this.ingredients.remove(index);
this.notifyUpdate();
}
}
This pseudocode will need refactoring since I don't have all the code that you are using but I wish that you will get the general idea
Ok I believe that in the button click code you have to tell your mock server which list is currently used try adding this to the mock server
List currentList = null;
public void setCurrentList(String listName) { // you can use integer but the best is to use enum type setCurrentList(enum) this way you will get tapeSafety
switch(listName){
case "ingredients" : currentList = ingredients ; break;
//other cases
default : throw new Exception(" list not referred error with key value"+listName);
}
}
public void delete(Object o){
int index = this.currentList.indexOf(o);
this.currentList.remove(index);
}
now you can update you controller delete as bellow
public void delete(){
MockServer.getServer().delete(defaultTableView.getSelectionModel().getSelectedItem());
}
Why this should work?
you have to know that there is a good practice that says code to an interface, not an implementation
As you know List in java is an interface so when I assigned the ingredients object this interface will reference the same ArrayList as the object and it will take all it behaviours (how to search for an ingredient object etc...) this way when we will use the currentList on runtime after a button click we are sure that the currentList will be the same as the clicked list and pointing to the same list in the memory
Wish this simple and really resume explanation could help you
In one of our applications we use the lazy query container to browse a potentially very large dataset. This works great. However, it is possible to select an arbitrary number of rows when using a multi-select table.
In our case, this can lead to selecting up to 500.000 rows (Vaadin limit) and then crash the VM.
Is there a way to limit the number of selected rows?
Here is a sample that shows the problem:
public class UIImpl extends UI {
private int SIZE = 500000;
#Override
protected void init(VaadinRequest request) {
// add a large table
LazyQueryContainer lqc = new LazyQueryContainer(
new QueryFactory() {
public Query constructQuery(QueryDefinition qd) {
return new Query() {
#Override
public int size() {
return SIZE;
}
#Override
public void saveItems(List<Item> addedItems, List<Item> modifiedItems, List<Item> removedItems) { }
#Override
public List<Item> loadItems(int startIndex, int count) {
List<Item> r = new ArrayList<>(count);
for (int i = startIndex; i<startIndex+count;i++) {
PropertysetItem item = new PropertysetItem();
item.addItemProperty("name", new ObjectProperty(i));
r.add(item);
}
return r;
}
#Override
public boolean deleteAllItems() {
return false;
}
#Override
public Item constructItem() {
return null;
}
};
}
},
null,
20,
false
);
lqc.addContainerProperty("name", Integer.class, null);
Table table = new Table();
table.setContainerDataSource(lqc);
table.setMultiSelect(true);
table.setSelectable(true);
table.setImmediate(true);
table.setVisibleColumns("name");
table.setSizeFull();
table.addValueChangeListener(new Property.ValueChangeListener() {
public void valueChange(Property.ValueChangeEvent event) {
System.err.println(event.getProperty().getValue());
}
});
setContent(table);
}
}
If you want to limit the number of rows a user is able to select you can use something similar to the following code:
public class TableWithSelectionLimit extends Table {
private final int maxSelections= -1;
private String[] lastSelected;
public TableWithSelectionLimit(int maxSelections) {
this.maxSelections = maxSelections;
}
#Override
public void changeVariables(Object source, Map<String, Object> variables) {
String[] selected = (String[]) variables.get("selected");
if (selected != null && selected.length > maxSelections) {
if (lastSelected != null) {
variables.put("selected", lastSelected);
} else {
variables.remove("selected");
}
markAsDirty();
} else {
lastSelected = selected;
}
super.changeVariables(source, variables);
}
}
This is of course optimizable, but it gives you an idea on how you could do it.
Update
For handling also selections produced using "Shift"+Click one has to handle/update these selection ranges additionally inside the method mentioned above.
Those can be retrieved using variables.get("selectedRanges") that will return a String[] containing items like "8-10" whereas the
first number is: the start index of the selection range
second number is: the amount of items selected starting at this index
Using this information it should be possible to update those values as wished and put them back into the variables using variables.put("selectedRanges", updatedRanges).
Attention: do not forget to call markAsDirty() if the values are changed, as otherwise the changes won't be propagated to the client side.
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.
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.
After adding data into the database, there is no changes to the JComboBox unless I execute the program again. Do I need to call any specific methods to refresh the values? Thanks!
New Code:
class ComboModel extends AbstractListModel implements ComboBoxModel
{
// array? treeset?
public Object getElementAt(int index) {
}
public int getSize() {
}
public Object getSelectedItem() {
}
public void setSelectedItem(Object anItem) {
}
}
final JComboBox stockListComboBox = new JComboBox();
ComboModel model = new ComboModel();
stockListComboBox.setModel(model);
try
{
// Database Stuffs
}
catch(Exception e)
{
e.printStackTrace();
}
Not sure if I got second part of the question, however, regarding values added to DefaultComboBoxModel, you have to call a method to let the combobox know the model elements changed.
You can use fireContentsChanged method or fireIntervalAdded one.