Adding rows with sorting in CellTable - java

I want to sort rows in CellTable when adding new.
To markup UI I use UIBinder:
<g:HTMLPanel>
<c:CellTable pageSize='100' ui:field='myTable'/>
<c:SimplePager ui:field='myPager' location='CENTER'/>
</g:HTMLPanel>
In the widget I created a table and pagination:
#UiField(provided=true) CellTable<myDTO> myTable;
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
myPager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
myTable = new CellTable<myDTO>();
Then I installed a selection model:
final NoSelectionModel<myDTO> selectionModel = new NoSelectionModel<myDTO>();
selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
public void onSelectionChange(SelectionChangeEvent event) {
clickedObject = selectionModel.getLastSelectedObject();
}
});
myTable.setPageSize(50);
myTable.setSelectionModel(selectionModel);
And added a few columns:
Column<myDTO, String> column1 = new Column<myDTO, String>(new TextCell()) {
#Override
public String getValue(myDTO data) {
return data.getSomeData1();
}
};
Column<myDTO, String> column2 = new Column<myDTO, String>(new TextCell()) {
#Override
public String getValue(myDTO data) {
return data.getSomeData2();
}
};
...
Column<myDTO, String> columnN = new Column<myDTO, String>(new TextCell()) {
#Override
public String getValue(myDTO data) {
return data.getSomeDataN();
}
};
myTable.addColumn(column1, "name of column1");
myTable.addColumn(column2, "name of column2");
...
myTable.addColumn(columnN, "name of columnN");
Next, I create AsyncDataProvider:
AsyncDataProvider<myDTO> provider = new AsyncDataProvider<myDTO>() {
#Override
// is called when the table requests a new range of data
protected void onRangeChanged(HasData<myDTO> display) {
final int start = display.getVisibleRange().getStart();
final int lenght = display.getVisibleRange().getLength();
myService.findAll(new AsyncCallback<List<myDTO>>() {
public void onFailure(Throwable caught) {
// exception handling here
}
public void onSuccess(List<myDTO> data) {
updateRowCount(data.size(), true);
updateRowData(0, data);
}
});
}
};
provider.addDataDisplay(myTable);
If I use this approach, then new rows are added to the end of the table.
I need to automatically sort rows when added.
How can I do it?

Create a sort handler right after creating your provider:
ListHandler<myDTO> sortHandler = new ListHandler<myDTO>(provider.getList());
myTable.addColumnSortHandler(sortHandler);
Then for each column that you want to sort by, set a comparator and add the column to the sort list, e.g.:
sortHandler.setComparator(column1, new Comparator<myDTO>() {
public int compare(myDTO dto1, myDTO dto2) {
// This is an example, how you compare them depends on the context
return dto1.getSomeData1().compareTo(dto2.getSomeData1());
}
});
myTable.getColumnSortList().push(column1);
You can call the push() method multiple times to sort by multiple columns. You can also call it twice for the same column to invert its sorting order (ascending/descending).

Related

Error with Paginated TableView sort for whole list

I m trying to implement a Paginated TableView that allows sorting by all items in JavaFX. I implemented the paginated tableview from here: https://stackoverflow.com/a/25424208/12181863. provided by jewelsea and tim buthe.
I was thinking that because the table view is only accessing a sublist of items, i wanted to extend the sorting from the table columns to the full list based on what i understand on the section about sorting on the Java Docs: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TableView.html#setItems-javafx.collections.ObservableList-
// bind the sortedList comparator to the TableView comparator
//i m guessing it extends the sorting from the table to the actual list?
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
and then refresh the tableview for the same sublist indexes (which should now be sorted since the whole list is sorted).
Basically, I want to use the table column comparator to sort the full list, and then "refresh" the tableview using the new sorted list. Is this feasible? Or is there a simpler way to go about this?
I also referred to other reference material such as : https://incepttechnologies.blogspot.com/p/javafx-tableview-with-pagination-and.html but i found it hard to understand since everything was all over the place with vague explanation.
A quick extract of the core components in my TouchDisplayEmulatorController class
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Pagination;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.List;
public class TouchDisplayEmulatorController extends Application {
public TableView sensorsTable;
public List<Sensor> sensors;
public int rowsPerPage = 14;
public GridPane grids = new GridPane();
public long timenow;
public void start(final Stage stage) throws Exception {
grids = new GridPane();
setGridPane();
Scene scene = new Scene(grids, 1024, 768);
stage.setScene(scene);
stage.setTitle("Table pager");
stage.show();
}
//public static void main(String[] args) throws Exception {
// launch(args);
//}
public void setGridPane(){
processSensors();
sensorsGrid();
}
public void sensorsGrid(){
buildTable();
int numOfPages = 1;
if (sensors.size() % rowsPerPage == 0) {
numOfPages = sensors.size() / rowsPerPage;
} else if (sensors.size() > rowsPerPage) {
numOfPages = sensors.size() / rowsPerPage + 1;
}
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(this::createPage);
pagination.setMaxPageIndicatorCount(numOfPages);
grids.add(pagination, 0, 0);
}
private Node createPage(int pageIndex) {
int fromIndex = pageIndex * rowsPerPage;
int toIndex = Math.min(fromIndex + rowsPerPage, sensors.size());
sensorsTable.setItems(FXCollections.observableArrayList(sensors.subList(fromIndex, toIndex)));
return new BorderPane(sensorsTable);
}
public void processSensors(){
sensors = new ArrayList<>();
// long timenow = OffsetDateTime.now(ZoneOffset.UTC).toInstant().toEpochMilli()/1000;
// StringTokenizer hildetoken = new StringTokenizer(msg);
for (int i=0; i<20; i++) {
sensors.add(new Sensor(String.valueOf(i), "rid-"+i, "sid-"+i, "0", "0", "no condition"));
}
}
public void buildTable() {
sensorsTable = new TableView();
TableColumn<Sensor, String> userid = new TableColumn<>("userid");
userid.setCellValueFactory(param -> param.getValue().userid);
userid.setPrefWidth(100);
TableColumn<Sensor, String> resourceid = new TableColumn<>("resourceid");
resourceid.setCellValueFactory(param -> param.getValue().resourceid);
resourceid.setPrefWidth(100);
TableColumn<Sensor, String> column1 = new TableColumn<>("sid");
column1.setCellValueFactory(param -> param.getValue().sid);
column1.setPrefWidth(100);
TableColumn<Sensor, String> column2 = new TableColumn<>("timestamp");
column2.setCellValueFactory(param -> param.getValue().timestamp);
column2.setPrefWidth(100);
TableColumn<Sensor, String> column3 = new TableColumn<>("reading");
column3.setCellValueFactory(param -> param.getValue().reading);
column3.setPrefWidth(100);
TableColumn<Sensor, String> column4 = new TableColumn<>("last contacted");
column4.setCellFactory(new Callback<TableColumn<Sensor, String>, TableCell<Sensor, String>>() {
#Override
public TableCell<Sensor, String> call(TableColumn<Sensor, String> sensorStringTableColumn) {
return new TableCell<Sensor, String>() {
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
this.setTextFill(Color.WHITE);
if (item.contains("#")) {
this.setTextFill(Color.BLUEVIOLET);
} else if (item.equals("> 8 hour ago")) {
this.setStyle("-fx-background-color: red;");
} else if (item.equals("< 8 hour ago")) {
this.setStyle("-fx-background-color: orange;");
//this.setTextFill(Color.ORANGE);
} else if (item.equals("< 4 hour ago")) {
this.setStyle("-fx-background-color: yellow;");
this.setTextFill(Color.BLACK);
} else if (item.equals("< 1 hour ago")) {
this.setStyle("-fx-background-color: green;");
//this.setTextFill(Color.GREEN);
}
setText(item);
}
}
};
}
});
column4.setCellValueFactory(param -> param.getValue().condition);
column4.setPrefWidth(100);
sensorsTable.getColumns().addAll(userid, resourceid, column1, column2, column3, column4);
}
}
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public SimpleStringProperty condition;
public Sensor(String userid, String resourceid, String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty(userid);
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
//we can use empty string or condition 3 here
}
public Sensor(String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty("-1");
this.resourceid = new SimpleStringProperty("-1");
this.sid = new SimpleStringProperty(sid);
this.timestamp= new SimpleStringProperty(timestamp);
this.reading= new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
}
public String getUserid() { return this.userid.toString(); }
public String getResourceid() { return this.resourceid.toString(); }
public String getSid() { return this.sid.toString(); }
public String getTimestamp() { return this.timestamp.toString(); }
public String getReading() { return this.reading.toString(); }
public String getCondition() { return this.condition.toString(); }
public String toString() { return "userid: "+getUserid()+" resourceid: "+getResourceid()+" sid: "+getSid()+
"\ntimestamp: "+getTimestamp()+" reading: "+getReading()+" condition: "+getCondition();}
}
separate class:
public class tester {
public static void main(String[] args) {
Application.launch(TouchDisplayEmulatorController.class, args);
}
}
Pagination of a TableView is not directly supported, so we have to do it ourselves. Note that the solutions referenced in the question pre-date the (re-introduction of Sorted-/FilteredList)
Nowadays, the basic approach is to use a FilteredList that contains only the rows which are on the current page. This filteredList must be the value of the table's itemsProperty. To also allow sorting, we need to wrap the original data into a SortedList and bind its comparator to the comparator provided by the table. Combining all:
items = observableArrayList(... //my data);
sortedList = new SortedList(items);
filteredList = new FilteredList(sortedList);
table.setItems(filteredList);
sortedList.comparatorProperty().bind(table.comparatorProperty());
Looks good, doesn't it? Unfortunately, nothing happens when clicking onto a column header. The reason:
the collaborator that's responsible for the sort is the sortPolicy
the default policy checks whether the table's items is a sorted list: if so (and its comparator is bound to the table's), sorting is left to that list, otherwise it falls back to FXCollections.sort(items, ...)
collections.sort fails to do anything because a filtered list is unmodifiable
In pseudo code:
if (items instanceof SortedList) {
return sortedList.getComparator().isBoundTo(table.getComparator());
}
try {
FXCollections.sort(items);
// sorting succeeded
return true;
} catch (Exception ex) {
// sorting failed
return false;
}
The way out is to implement a custom sort policy: instead of only checking the table's items for being a sortedList, it walks up the chain of transformationList (if available) sources until it finds a sorted (or not):
ObservableList<?> lookup = items;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
items = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
// ... same as original policy
Now we have the sorting (of the complete list) ready - next question is what should happen to the paged view after sorting. Options:
keep the page constant and updated the filter
keep any of the current items visible and update the page
Both require to trigger the update when the sort state of the list changes, which to implement depends on UX guidelines.
A runnable example:
public class TableWithPaginationSO extends Application {
public static <T> Callback<TableView<T>, Boolean> createSortPolicy(TableView<T> table) {
// c&p of DEFAULT_SORT_POLICY except adding search up a chain
// of transformation lists until we find a sortedList
return new Callback<TableView<T>, Boolean>() {
#Override
public Boolean call(TableView<T> table) {
try {
ObservableList<?> itemsList = table.getItems();
// walk up the source lists to find the first sorted
ObservableList<?> lookup = itemsList;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
itemsList = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
if (itemsList instanceof SortedList) {
SortedList<?> sortedList = (SortedList<?>) itemsList;
boolean comparatorsBound = sortedList.comparatorProperty()
.isEqualTo(table.comparatorProperty()).get();
return comparatorsBound;
} else {
if (itemsList == null || itemsList.isEmpty()) {
// sorting is not supported on null or empty lists
return true;
}
Comparator comparator = table.getComparator();
if (comparator == null) {
return true;
}
// otherwise we attempt to do a manual sort, and if successful
// we return true
FXCollections.sort(itemsList, comparator);
return true;
}
} catch (UnsupportedOperationException e) {
return false;
}
};
};
}
private Parent createContent() {
initData();
// wrap sorted list around data
sorted = new SortedList<>(data);
// wrap filtered list around sorted
filtered = new FilteredList<>(sorted);
// use filtered as table's items
table = new TableView<>(filtered);
addColumns();
page = new BorderPane(table);
// install custom sort policy
table.setSortPolicy(createSortPolicy(table));
// bind sorted comparator to table's
sorted.comparatorProperty().bind(table.comparatorProperty());
pagination = new Pagination(rowsPerPage, 0);
pagination.setPageCount(sorted.size() / rowsPerPage);;
pagination.setPageFactory(this::createPage);
sorted.addListener((ListChangeListener<Locale>) c -> {
// update page after changes to list
updatePage(true);
});
return pagination;
}
private Node createPage(int pageIndex) {
updatePredicate(pageIndex);
return page;
}
/**
* Update the filter to show the current page.
*/
private void updatePredicate(int pageIndex) {
int first = rowsPerPage * pageIndex;
int last = Math.min(first + rowsPerPage, sorted.size());
Predicate<Locale> predicate = loc -> {
int index = sorted.indexOf(loc);
return index >= first && index < last;
};
filtered.setPredicate(predicate);
// keep reference to first on page
firstOnPage = filtered.get(0);
}
/**
* Update the page after changes to the list.
*/
private void updatePage(boolean keepItemVisible) {
if (keepItemVisible) {
int sortedIndex = sorted.indexOf(firstOnPage);
int pageIndex = sortedIndex >= 0 ? sortedIndex / rowsPerPage : 0;
pagination.setCurrentPageIndex(pageIndex);
} else {
updatePredicate(pagination.getCurrentPageIndex());
}
}
private void addColumns() {
TableColumn<Locale, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
TableColumn<Locale, String> country = new TableColumn<>("Country");
country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
table.getColumns().addAll(name, country);
}
private void initData() {
Locale[] availableLocales = Locale.getAvailableLocales();
data = observableArrayList(
Arrays.stream(availableLocales)
.filter(e -> e.getDisplayName().length() > 0)
.limit(120)
.collect(toList())
);
}
private TableView<Locale> table;
private Pagination pagination;
private BorderPane page;
private ObservableList<Locale> data;
private FilteredList<Locale> filtered;
private SortedList<Locale> sorted;
private Locale firstOnPage;
private int rowsPerPage = 15;
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
First off, it's not clear that Pagination is the correct control for this, although the UI for page selection is really nice. Your problems might stem from the fact that you're putting the TableView into a new BorderPane each time, or it might come from the fact that you're using TableView.setItems().
An approach that works is to use FilteredList to handle the pagination, and just keep the TableView as a static element in the layout, not in the dynamic graphic area of the Pagination. To satisfy the need to have the Pagination do something, a Text with the page number was created.
A new property was added to Sensor - ordinalNumber. This is used to control the filter for the paging. The filter will dynamically change to select only those Sensor's with an ordinalNumber in a particular range. The range is controlled by the Pagination's currentPageIndexProperty. There's a listener on that property that regenerates the FilteredList's predicate property each time the page is changed.
That handles the page changes, but what about sorting the whole list? First, the FilteredList is wrapped in a SortedList, and it's the SortedList that's set into the TableView. The SortedList's Comparator is bound to the TableView's Comparator.
But the SortedList only sees the Sensors included under the current filter. So a listener was added to the TableView's comparatorProperty. The action for this listener Streams the underlying ObservableList, sorts it using the new Comparator, and resets each Sensor's ordinalNumber according to the new sort order.
Finally, in order to have the FilteredList re-evaluate the ObservableList, these ordinalNumber changes need to trigger a ListChange event. So an extractor was added to the ObservableList based on the ordinalNumber.
The result works pretty well, except for the goofy page numbering Text sliding onto the screen with each page change.
The entire code was cleaned up for readability and unused stuff was stripped out to keep the example minimal.
Here's the Sensor class:
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public IntegerProperty ordinalNumber = new SimpleIntegerProperty(0);
public Sensor(int userid, String resourceid, String sid, String timestamp, String reading, String condition) {
this.userid = new SimpleStringProperty(Integer.toString(userid));
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.ordinalNumber.set(userid);
}
}
Here's the layout code:
public class PaginationController extends Application {
public TableView<Sensor> sensorsTable = new TableView<>();
public ObservableList<Sensor> sensorObservableList = FXCollections.observableArrayList(sensor -> new Observable[]{sensor.ordinalNumber});
public FilteredList<Sensor> sensorFilteredList = new FilteredList<>(sensorObservableList);
public SortedList<Sensor> sensorSortedList = new SortedList<>(sensorFilteredList);
public IntegerProperty currentPage = new SimpleIntegerProperty(0);
public int rowsPerPage = 14;
public void start(final Stage stage) throws Exception {
processSensors();
stage.setScene(new Scene(buildScene(), 1024, 768));
stage.setTitle("Table pager");
stage.show();
}
public Region buildScene() {
buildTable();
int numOfPages = calculateNumOfPages();
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(pageIndex -> {
Text text = new Text("This is page " + (pageIndex + 1));
return text;
});
pagination.setMaxPageIndicatorCount(numOfPages);
currentPage.bind(pagination.currentPageIndexProperty());
sensorFilteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> createPageFilter(pagination.getCurrentPageIndex()), pagination.currentPageIndexProperty()));
return new VBox(sensorsTable, pagination);
}
#NotNull
private Predicate<Sensor> createPageFilter(int currentPage) {
int lowerLimit = (currentPage) * rowsPerPage;
int upperLimit = (currentPage + 1) * rowsPerPage;
return sensor -> (sensor.ordinalNumber.get() >= lowerLimit) &&
(sensor.ordinalNumber.get() < upperLimit);
}
private int calculateNumOfPages() {
int numOfPages = 1;
if (sensorObservableList.size() % rowsPerPage == 0) {
numOfPages = sensorObservableList.size() / rowsPerPage;
} else if (sensorObservableList.size() > rowsPerPage) {
numOfPages = sensorObservableList.size() / rowsPerPage + 1;
}
return numOfPages;
}
public void processSensors() {
Random random = new Random();
for (int i = 0; i < 60; i++) {
sensorObservableList.add(new Sensor(i, "rid-" + i, "sid-" + i, Integer.toString(random.nextInt(100)), "0", "no condition"));
}
}
public void buildTable() {
addStringColumn("userid", param1 -> param1.getValue().userid);
addStringColumn("resourceid", param1 -> param1.getValue().resourceid);
addStringColumn("sid", param1 -> param1.getValue().sid);
addStringColumn("timestamp", param1 -> param1.getValue().timestamp);
addStringColumn("reading", param1 -> param1.getValue().reading);
TableColumn<Sensor, Number> ordinalCol = new TableColumn<>("ordinal");
ordinalCol.setCellValueFactory(param -> param.getValue().ordinalNumber);
ordinalCol.setPrefWidth(100);
sensorsTable.getColumns().add(ordinalCol);
sensorsTable.setItems(sensorSortedList);
sensorSortedList.comparatorProperty().bind(sensorsTable.comparatorProperty());
sensorSortedList.comparatorProperty().addListener(x -> renumberRecords());
}
private void renumberRecords() {
AtomicInteger counter = new AtomicInteger(0);
Comparator<Sensor> newValue = sensorsTable.getComparator();
if (newValue != null) {
sensorObservableList.stream().sorted(newValue).forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
} else {
sensorObservableList.forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
}
}
#NotNull
private void addStringColumn(String columnTitle, Callback<TableColumn.CellDataFeatures<Sensor, String>, ObservableValue<String>> callback) {
TableColumn<Sensor, String> column = new TableColumn<>(columnTitle);
column.setCellValueFactory(callback);
column.setPrefWidth(100);
sensorsTable.getColumns().add(column);
}
}
For demonstration purposes, the timestamp field in the Sensor was initialized to a random number so that it would give an obvious change when that column was sorted. Also, the ordinalNumber field was added to the table so that it could be easily verified that they had been re-evaluated when a new sort column was chosen.

How to sort values as numbers in NatTable?

My problem is, that data values are sorted as strings, although I used DefaultDoubleDisplayConverter. I registered converter to cell labels, not to column header label. My code:
public class NatTableFactory {
private NatTable createTable(Composite parent, List<TableLine> tLines, String[][] propertyNames,
PropertyToLabels[] propToLabels, TableParams params, TextMatcherEditor<TableLine>editor, boolean openableParts) {
BodyLayerStack bodyLayerStack =
new BodyLayerStack(
tLines,
tLines.get(0).getLength(),
params.getColumnIndicesForRowHeaders());
...
SortHeaderLayer<TableLine> sortHeaderLayer =
new SortHeaderLayer<TableLine>(
columnHeaderLayer,
new GlazedListsSortModel<TableLine>(
bodyLayerStack.getSortedList(),
getSortingColumnPropAccessor(propertyNames[0]),
configRegistry,
columnHeaderDataLayer));
...
composite.addConfiguration(NatTableLayerConfigurations.getCompoositeLayerConfiguration());
NatTable natTable = new NatTable(parent, composite, false);
if( params.getAutoFitColWidthIndices().size() > 0 )
registerAutoResizeColCmdHandler(natTable, composite, bodyLayerStack, params.getAutoFitColWidthIndices());
setNatTableContentTooltip(natTable);
natTable.setConfigRegistry(configRegistry);
natTable.addConfiguration(new SingleClickSortConfiguration());
natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
setNatTableContextMenu(natTable, openableParts);
natTable.addConfiguration(NatTableLayerConfigurations.getNatTableConfiguration());
//natTable.addConfiguration(NatTableLayerConfigurations.getCustomConvertConfiguration(bodyDataLayer));
natTable.configure();
...
NatTableContentProvider.addNatTableData(natTable, bodyLayerStack.getSelectionLayer(), bodyLayerStack.getBodyDataProvider());
return natTable;
}
}
then:
public class NatTableLayerConfigurations
{
...
public static AbstractRegistryConfiguration getNatTableConfiguration()
{
return new AbstractRegistryConfiguration()
{
#Override
public void configureRegistry(IConfigRegistry configRegistry)
{
...
Style cellAlignStyle = new Style();
cellAlignStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.RIGHT);
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellAlignStyle, DisplayMode.NORMAL, NatTableFactory.DataTypeNumberLabel);
//
configRegistry.registerConfigAttribute(
CellConfigAttributes.DISPLAY_CONVERTER,
new DefaultDoubleDisplayConverter(), DisplayMode.NORMAL,
NatTableFactory.DataTypeNumberLabel);
System.out.println("configRegistry.registerConfigAttribute CellConfigAttributes.DISPLAY_CONVERTER");
...
}
};
}
}
and:
public class BodyLayerStack extends AbstractLayerTransform
{
...
public BodyLayerStack(List<TableLine> values, int columnCount, Integer[] columnIndicesForRowHeaders)
{
EventList<TableLine> eventList = GlazedLists.eventList(values);
TransformedList<TableLine, TableLine> rowObjectsGlazedList = GlazedLists.threadSafeList(eventList);
sortedList = new SortedList<>(rowObjectsGlazedList, null);
// wrap the SortedList with the FilterList
filterList = new FilterList<>(sortedList);
bodyDataProvider = new ListDataProvider<TableLine>(filterList, getColumnAccessor(columnCount));
bodyDataLayer = new DataLayer(bodyDataProvider);
IConfigLabelAccumulator cellLabelAccumulator = new IConfigLabelAccumulator() {
#Override
public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
int columnIndex = bodyDataLayer.getColumnIndexByPosition(columnPosition);
int rowIndex = bodyDataLayer.getRowIndexByPosition(rowPosition);
if( isRowHeader(columnIndicesForRowHeaders, columnIndex) ) {
configLabels.addLabel(NatTableFactory.RowHeaderLabel);
} else {
configLabels.addLabel(filterList.get(rowIndex).getObjectTypeByColumn(columnIndex));
// NatTableLayerConfigurations.getNatTableConfiguration();
}
}
};
bodyDataLayer.setConfigLabelAccumulator(cellLabelAccumulator);
GlazedListsEventLayer<TableLine> glazedListsEventLayer = new GlazedListsEventLayer<>(bodyDataLayer, filterList);
...
}
}
I checked various examples, but I can not see, what do I miss. And how can I check, if data in cells were converted correctly? Thanks for some hint.
I suppose you are missing the configuration of the sort comparator.
This is explained in our documentation: https://www.eclipse.org/nattable/documentation.php?page=sorting
The following examples show the usage:
SortableGridExample
GroupByCustomTypesExample

Redirecting output to table in Java Swing

Newbie here, I have a problem, redirecting my output to a Jtable. The data is coming from a different class that does the real work which is Scanner.java.
With this said, Scanner.java could print what i want on console but since I added gui which am still learning I have created a new class MainFrame.java and I want search or scan result form Scanner.java to be populated in my JTable but am finding it hard to get the login.
Scanner.java
public void getCompanyProfile(){
Document sourceCode;
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
List<String> allLinks = results();
Document sourceCode;
int counter = 1;
for (String link : allLinks){
System.out.println("Link #:" + counter + " " + link);
sourceCode = PageVisitor.getHtmlSource(link);
Elements profile = sourceCode.select("div.company a.cd");
for (Element links : profile) {
String linkHref = links.attr("href");
System.out.println(linkHref);
setUserData(linkHref);
}
counter++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private void setUserData(String url) throws IOException{
Extractor data = new Extractor();
// Scan each page alibaba initial result
data.setProfile(url);
this.companyName = data.getName();
this.country = data.getCountry();
HashSet<String> webites = data.getSellerUrls();
this.webAndEmail = new HashMap<String, HashSet<String>>();
HashSet<String> emails;
for (String webs: webites){
emails = data.emailExtractor(webs);
webAndEmail.put(webs, emails);
for (String anEmail : emails){
//This is the part i want to be displayed in my JTable Component.
System.out.println("Company=" +companyName + ", country=" + country + ", web="
+ webs + ", email=" + anEmail);
}
}
}
public String getProductName(){
return this.product;
}
public String getSource(){
return this.source;
}
public String getCompanyName(){
return this.companyName;
}
public String getCountry(){
return this.country;
}
public Map<String, HashSet<String>> getWebandEmail(){
return this.webAndEmail;
}
Finally, this is my MainFrame.java file below .
![JButton btnStart = new JButton("Start");
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
});
btnStart.setBounds(197, 166, 75, 29);
frame.getContentPane().add(btnStart);
//more statements like the above to establish all col. titles
String\[\] columnNames = {"Company Name", "Email", "Website", "Country", "Product", "Source"};
//Sample data to be printed
Object\[\]\[\] data =
{
{"Code Java Ltd", "bingo#codejava.net", "http://www.codejava.com", "Universe", "Polythecnic", "Ebay - B2B"},
};
DefaultTableModel model = new DefaultTableModel(data, columnNames) {
#Override
public boolean isCellEditable(int row, int column) {
//all cells false
return false;
}
};
resultTable = new JTable(model);
//resultTable.setBounds(37, 259, 553, 143);
resultTable.getColumnModel().getColumn(0).setPreferredWidth(150);
resultTable.getColumnModel().getColumn(1).setPreferredWidth(150);
resultTable.getColumnModel().getColumn(2).setPreferredWidth(150);
resultTable.getColumnModel().getColumn(3).setPreferredWidth(150);
resultTable.getColumnModel().getColumn(4).setPreferredWidth(100);
resultTable.getColumnModel().getColumn(5).setPreferredWidth(100);
resultTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
JScrollPane scrollPane = new JScrollPane( resultTable );
scrollPane.setBounds(37, 259, 806, 143);
frame.getContentPane().add( scrollPane );
//frame.add(resultTable);
JButton button = new JButton("Stop");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
});
button.setBounds(289, 166, 75, 29);
frame.getContentPane().add(button);][1]
This is what am trying to attain.
My other idea is to write the content to CSV from Scanner.java class file and read the file lated to populate the table. But like I said, am a beginner still don't think it would be that easy. So I kindly need someone to point me in the right direction.
Basically, you want to load your data from outside the Event Dispatching Thread, so as not to block the UI and make it "hang".
Next, you need some way for the Scanner to publish information it has generated, there are a number of ways you might do this, but the simplest might to use something like a Produce/Consumer Pattern.
With the Scanner acting as the producer, we need some way to inform the consumer that new content is available. Start with a simple interface...
public interface Consumer {
public void publish(String company, String country, String webLink, String email);
}
Note, I normally prefer to use objects (like a POJO), but I'm trying to keep it simple.
Next, we need to modify the Scannner to work with out Consumer...
public class Scanner {
public void getCompanyProfile(Consumer consumer) {
Document sourceCode;
List<String> allLinks = results();
Document sourceCode;
int counter = 1;
for (String link : allLinks) {
System.out.println("Link #:" + counter + " " + link);
sourceCode = PageVisitor.getHtmlSource(link);
Elements profile = sourceCode.select("div.company a.cd");
for (Element links : profile) {
String linkHref = links.attr("href");
System.out.println(linkHref);
setUserData(linkHref);
}
counter++;
}
}
private void setUserData(String url, Consumer consumer) throws IOException {
Extractor data = new Extractor();
// Scan each page alibaba initial result
data.setProfile(url);
this.companyName = data.getName();
this.country = data.getCountry();
HashSet<String> webites = data.getSellerUrls();
this.webAndEmail = new HashMap<String, HashSet<String>>();
HashSet<String> emails;
for (String webs : webites) {
emails = data.emailExtractor(webs);
webAndEmail.put(webs, emails);
for (String anEmail : emails) {
consumer.publish(companyName, country, webs, anEmail);
}
}
}
public String getProductName() {
return this.product;
}
public String getSource() {
return this.source;
}
public String getCompanyName() {
return this.companyName;
}
public String getCountry() {
return this.country;
}
public Map<String, HashSet<String>> getWebandEmail() {
return this.webAndEmail;
}
}
Now, we need some way to get the Scanner started and producing data, first we create the basic UI and then we start a SwingWorker, passing a reference of the TableModel to it, so it can add the new rows.
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new String[]{"Company Name", "Email", "Website", "Country", "Product", "Source"}, 0);
JTable table = new JTable(model);
// Initialise remainder of the UI...
ScannerWorker worker = new ScannerWorker(model);
worker.execute();
}
});
And the SwingWorker to hold it all together...
public class ScannerWorker extends SwingWorker<Object, String[]> implements Consumer {
private DefaultTableModel tableModel;
public ScannerWorker(DefaultTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
protected Object doInBackground() throws Exception {
Scanner scanner = new Scanner();
scanner.getCompanyProfile(this);
return null;
}
#Override
public void publish(String company, String country, String webLink, String email) {
publish(new String[]{company, email, webLink, country, "", ""});
}
#Override
protected void process(List<String[]> chunks) {
for (String[] rowData : chunks) {
tableModel.addRow(rowData);
}
}
}
Take a closer look at Worker Threads and SwingWorker and How to Use Tables for more details

In which container can I collect Strings to show any of them

I have a LinkedHashMap which fills with data from db with loop "for" string by string and when I try to show the first or the last String, the method can show me only the last String in log. But in application listViewContent is filled fully. So I don't understand why I can't see any string that I want. I need to collect all strings I get from db and compare them in future.
How can I collect all strings and what method should I call to show the string I want to see?Unfortunately I can only retrieve one (and the last instead of the first) string.
Here is my example code :
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FirstMethod();
}
public FirstMethod() {
SecondMethod newMethod = .. // getting data from the second method
}
public SecondMethod() {
public void onResponseReceived(String result) {
try {
...
if (posts != null) {
for (WallPostItem post : posts) { // this loop
//create new map for a post
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put(ATTRIBUTE_NAME_TEXT, post.text);
PictureItem postPicture = new PictureItem();
map.put(ATTRIBUTE_NAME_IMAGE, postPicture);
map.put(ATTRIBUTE_NAME_DATE, post.date);
sAdapter.notifyDataSetChanged();
};
};
...
List<Map.Entry<String, Object>> list = new ArrayList<Map.Entry<String, Object>>(GlobalMap.entrySet());
Map.Entry<String, Object> firstInsertedEntry = list.get(0);
Log.w("FirstEntryOfMap",""+firstInsertedEntry); // this log shows me the last string instead of the first
}
if (isRefresh) {
isRefresh = false;
lvSimple.setSelectionAfterHeaderView();
}
} catch (Exception e) {
Log.d("exceptions", "problem in get wall post task after post execute: " + e.toString());
}
}
You aren't putting your values into a List, you are putting them into a Map (that preserves key order). I would suggest you create a POJO class,
class MyAttribute {
final String postName;
final PictureItem postPicture;
final Date postDate;
public MyAttribute(String postName, PictureItem postPicture, Date postDate) {
this.postName = postName;
this.postPicture = postPicture;
this.postDate = postDate;
}
public String getPostName() {
return postName;
}
public Date getPostDate() {
return postDate;
}
public PictureItem getPostPicture() {
return postPicture;
}
}
Then you could create a
List<MyAttribute> myAttributes = new ArrayList<>();

ParseObject as a data to the table/chart

I'm new in coding and I have a problem to understand something. I follow the example form Parse.com Doc and wrote this.
public void getData() {
ParseQuery<ParseObject> query = ParseQuery.getQuery("ParseClass");
query.getInBackground("lxFzCTeOcl", new GetCallback<ParseObject>() {
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
String object = parseObject.getString("value");
int object_value = Integer.parseInt(obiect);
} else {
Log.d("score", "Error: " + e.getMessage());
}
}
});
}
I understand this like:
I send query to server
get obiect with "lxFzCTeOcl" id
if there is no exception I create String object which takes string
form "value" column.
convert String to int
My question is: How can I use object_value for example to make a chart or put it into a table?
Here we will add the array list to your code and start to store an object inside the array every time we call the getData method in your class.
private ArrayList<Integer> dataArray;
public void getData() {
ParseQuery<ParseObject> query = ParseQuery.getQuery("ParseClass");
query.getInBackground("lxFzCTeOcl", new GetCallback<ParseObject>() {
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
String object = parseObject.getString("value");
Integer objectValue = Integer.parseInt(obiect);
if(dataArray==null)
dataArray = new ArrayList<Integer>();
dataArray.add(objectValue);
} else {
Log.d("score", "Error: " + e.getMessage());
}
}
});
}
And here I'm just adding a simple example of how to create a simple pie chart using our array list (note that I used the lib AChartEngine http://www.achartengine.org/):
private static int[] COLORS = new int[] { Color.GREEN, Color.BLUE,Color.MAGENTA, Color.CYAN };
private GraphicalView createPieChart(ArrayList<Integer> data){
GraphicalView chartView;
CategorySeries series = new CategorySeries("PIE");
for (int i = 0; i < VALUES.length; i++) {
series.add(i, data.get(i));
SimpleSeriesRenderer renderer = new SimpleSeriesRenderer();
renderer.setColor(COLORS[(series.getItemCount() - 1) % COLORS.length]);
mRenderer.addSeriesRenderer(renderer);
}
chartView = ChartFactory.getPieChartView(this, series, new DefaultRenderer());
chartView.repaint();
return chartView;
}
Now you can add this GraphicalView to your view.
The returned object is much like a map, with key/value pairs. In your example, the key is "value", which makes it a little confusing, but it would be like this if you wanted all fields:
for (Field field : myInstance.getClass().getDeclaredFields()) {
String name = field.getName();
value = field.get(myInstance).toString();
map.put(name, value);
}

Categories