I'm trying to build a simple java application using Spring Boot and Vaadin.
I need to add a table on UI like this: https://www.screencast.com/t/1c4xkr4IE
It could be extended by periods.
Looks like Vaadin Grid element perfectly fits my requirements, but it adds my rows as columns. Is it possible to reverse grid or maybe there is another way to build needed table?
UPDATE
Here are my code:
#SpringComponent
#UIScope
public class MyDataEditor extends VerticalLayout {
private final MyDataRepository repository;
private MyData myData;
TextField month = new TextField("Period");
TextField numberOfWorkers = new TextField(" Number of workers");
TextField numberOfNewcomers = new TextField("Number of newcomers");
TextField numberOfDismissals = new TextField("Number of dismissals");
Button save = new Button("Save");
Button cancel = new Button("Cancel");
Button delete = new Button("Delete");
CssLayout actions = new CssLayout(save, cancel, delete);
Binder<MyData> binder = new Binder<>(MyData.class);
#Autowired
public MyDataEditor(MyDataRepository repository) {
this.repository = repository;
addComponents(month, numberOfWorkers, numberOfNewcomers, numberOfDismissals, actions);
binder.bindInstanceFields(this);
setSpacing(true);
actions.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
save.setStyleName(ValoTheme.BUTTON_PRIMARY);
save.setClickShortcut(ShortcutAction.KeyCode.ENTER);
save.addClickListener(e -> repository.save(myData));
delete.addClickListener(e -> repository.delete(myData));
cancel.addClickListener(e -> editInputData(myData));
setVisible(false);
}
public interface ChangeHandler {
void onChange();
}
public final void editMyData(MyData c) {
if (c == null) {
setVisible(false);
return;
}
final boolean persisted = c.getMonth() != null;
if (persisted) {
myData = repository.findOne(c.getMonth());
} else {
myData = c;
}
cancel.setVisible(persisted);
binder.setBean(myData);
save.focus();
periodId.selectAll();
}
public void setChangeHandler(ChangeHandler h) {
save.addClickListener(e -> h.onChange());
delete.addClickListener(e -> h.onChange());
}
}
#SpringUI
#Theme("valo")
public class VaadinUI extends UI {
private final MyDataRepository repo;
private final MyDataEditor editor;
final Grid<MyData> grid;
private final Button addNewBtn;
#Autowired
public VaadinUI(MyDataRepository repo, MyDataEditor editor) {
this.repo = repo;
this.editor = editor;
this.grid = new Grid<>(MyData.class);
this.addNewBtn = new Button("Add new month");
}
#Override
protected void init(VaadinRequest request) {
grid.setHeight(300, Unit.PIXELS);
grid.setColumns("month", "numberOfWorkers", "numberOfNewcomers", "numberOfDismissals");
grid.asSingleSelect().addValueChangeListener(e -> {
editor.editMyData(e.getValue());
});
addNewBtn.addClickListener(e -> editor.editMyData(new MyData()));
editor.setChangeHandler(() -> {
editor.setVisible(false);
grid.setItems(repo.findAll());
});
}
}
So what I mean by this question is that I set
grid.setColumns("month", "numberOfWorkers", "numberOfNewcomers", "numberOfDismissals");
and do not find out method like setRows, so my table looks like: https://www.screencast.com/t/ndDY6tXp, but should be like on first picture.
I do believe there is no way to solve it elegantly without CSS or extending the client grid component.
What you could do though is add your data using
List<MyData> data = repo.findAll();
for(int i = 0; i < data.size(); i++)
grid.addColumn(i)
//String[] months = data.map(x -> x.month).collect(Collectors.toArray)
//String[] nrWork = data.map(x -> x.nrWork).collect(Collectors.toArray)
grid.addRow(months)
grid.addRow(nrWork)
I believe the Vaadin grid (or table) component was designed having the table concept as a starting point. Hence you'd have a unified structure defined by the columns and display any number of same-type data elements, 1 per row. And as far as I know, up to 8.0.4, you can't rotate the structure.
Furthermore, from the user experience perspective, if you have multiple time periods, it'll be easier to scroll them vertically (with the mouse wheel) than horizontally, so I'd suggest discussing the possibility of displaying them just as you started, with the "month", "numberOfWorkers", "numberOfNewcomers" and "numberOfDismissals" columns, and supplying rows of MyData. This also makes it easier to sort, filter, add or edit selected items, whereas for the workaround below, you'd have to do something extra.
If for some reason that's not acceptable at all, you should be able to fake the feature you want with a bit of work (see below sample), but performance and usability wise, there's no guarantees... after all, this is not what it's been designed for.
Code
package com.example.grid;
import com.vaadin.data.ValueProvider;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Notification;
import com.vaadin.ui.VerticalLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
public class HorizontalGrid extends VerticalLayout {
private static final String ROW_CAPTION = "row-caption";
public HorizontalGrid() {
// basic grid setup without column header
Grid<HorizontalDisplayAdapter> grid = new Grid<>();
grid.setSizeFull();
grid.setSelectionMode(Grid.SelectionMode.NONE);
grid.removeHeaderRow(0);
// load some data from the DB or someplace else
List<PeriodSummary> periods = loadPeriods();
// add row headers
grid.addColumn(HorizontalDisplayAdapter::getCaption).setId(ROW_CAPTION).setWidth(150);
// add a column for each period
for (int i = 0; i < periods.size(); i++) {
// save the column index so we ca figure out what to edit later
grid.addColumn(new AdapterValueProvider(i)).setId(String.valueOf(i)).setWidth(60);
}
// wrap the data in "horizontal display adapters"
grid.setItems(
new HorizontalDisplayAdapter("Period", periods, PeriodSummary::getPeriod),
new HorizontalDisplayAdapter("Workers", periods, PeriodSummary::getWorkers),
new HorizontalDisplayAdapter("Newcomers", periods, PeriodSummary::getNewcomers),
new HorizontalDisplayAdapter("Dismissals", periods, PeriodSummary::getDismissals)
);
// retrieve the correct period summary to edit, based on the column that was clicked (unless it's the header)
grid.addItemClickListener(event -> {
if (!ROW_CAPTION.equals(event.getColumn().getId())) {
Integer columnIndex = Integer.valueOf(event.getColumn().getId());
Notification.show("Editing " + event.getItem().getSummary(columnIndex), Notification.Type.ERROR_MESSAGE);
}
});
// freeze first column for scrolling purposes
grid.setFrozenColumnCount(1);
addComponent(grid);
setSizeFull();
}
// generate some dummy data to simulate loading from the DB
private List<PeriodSummary> loadPeriods() {
Random random = new Random();
ArrayList<PeriodSummary> periodSummaries = new ArrayList<>();
for (int i = 0; i < 100; i++) {
periodSummaries.add(new PeriodSummary(i, random.nextInt(100), random.nextInt(100), random.nextInt(100)));
}
return periodSummaries;
}
// adapter to display data in a "horizontal format"
public class HorizontalDisplayAdapter {
// row caption
private final String caption;
// periods for each column
private final List<PeriodSummary> periods;
// used for brevity, a class hierarchy is probably more elegant
private Function<PeriodSummary, Integer> valueExtractor;
public HorizontalDisplayAdapter(String caption, List<PeriodSummary> periods, Function<PeriodSummary, Integer> valueExtractor) {
this.caption = caption;
this.periods = periods;
this.valueExtractor = valueExtractor;
}
public String getCaption() {
return caption;
}
public PeriodSummary getSummary(int columnIndex) {
return periods.get(columnIndex);
}
// extract the data for a certain column
public Integer getValue(int columnIndex) {
return valueExtractor.apply(periods.get(columnIndex));
}
}
// basic bean
public class PeriodSummary {
int period;
int workers;
int newcomers;
int dismissals;
public PeriodSummary(int period, int workers, int newcomers, int dismissals) {
this.period = period;
this.workers = workers;
this.newcomers = newcomers;
this.dismissals = dismissals;
}
public int getPeriod() {
return period;
}
public void setPeriod(int period) {
this.period = period;
}
public int getWorkers() {
return workers;
}
public void setWorkers(int workers) {
this.workers = workers;
}
public int getNewcomers() {
return newcomers;
}
public void setNewcomers(int newcomers) {
this.newcomers = newcomers;
}
public int getDismissals() {
return dismissals;
}
public void setDismissals(int dismissals) {
this.dismissals = dismissals;
}
#Override
public String toString() {
return "PeriodSummary{" +
"period=" + period +
", workers=" + workers +
", newcomers=" + newcomers +
", dismissals=" + dismissals +
'}';
}
}
// value provider for the horizontal display adapters
private class AdapterValueProvider implements ValueProvider<HorizontalDisplayAdapter, Integer> {
// column index is used to retrieve data from the correct summary
private int columnIndex;
public AdapterValueProvider(int columnIndex) {
this.columnIndex = columnIndex;
}
#Override
public Integer apply(HorizontalDisplayAdapter horizontalDisplayAdapter) {
return horizontalDisplayAdapter.getValue(columnIndex);
}
}
}
Result
Related
I have a UI bug in a legacy code in our Java project. We display a table, with three columns (HumanReadable, name and value) in a window. In that window, users can click on each cell and update the values. Before that, user clicks the "add" button to add a new row (three new cells). Each cell has a default value, until the user decides to update the value. Now, when the users decides to update the value of the cell, he clicks on the cell and types in the value. The bug is that, once done editing, it keeps the default value in the UI. In the backend, the value has changed (if you click the cell again, it will go into editing mode and show you the value).
I uploaded a short GIF that shows the issue and can be found here.
In that GIF you can see that I updated the default value of the first column to be test. Then I click some other place (to exit the edit mode) and it showed the default value instead of test in the first column.
The method that creates the table:
private void createTable(final Composite parent) {
final Table varTable = new Table(parent, SWT.MULTI);
varTable.setHeaderVisible(true);
varTable.setLinesVisible(true);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(varTable);
varTableViewer = new TableViewer(varTable);
final DataBindingContext bindingContext = new DataBindingContext();
final TableViewerColumn col1 = GuiUtils.createTableColumn(varTableViewer, "Human Readable");
col1.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, dataProperty));
col1.getColumn().setWidth(120);
final TableViewerColumn col2 = GuiUtils.createTableColumn(varTableViewer, "Name");
col2.getColumn().setWidth(120);
col2.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, nameProperty));
final TableViewerColumn col3 = GuiUtils.createTableColumn(varTableViewer, "Value");
col3.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, valueProperty));
KeyBoardNavigationSupport.createSupport(varTableViewer);
input = new WritableList(globalVars, FlowVar.class);
ViewerSupport.bind(varTableViewer, input, BeanProperties.values(new String[] { dataProperty, nameProperty, valueProperty }));
}
The StringEditingSupport class:
public class StringEditingSupport extends ObservableValueEditingSupport {
private class CellEditorPrintValidatorErrors extends TextCellEditor {
public CellEditorPrintValidatorErrors(Composite control) {
super(control);
}
#Override
protected void focusLost(){
if(this.getErrorMessage() != null) {
MessageDialog.openError(this.getControl().getShell(), "Invalid input", this.getErrorMessage());
}
}
}
private final CellEditor cellEditor;
String propertyName;
public StringEditingSupport(final ColumnViewer viewer, final DataBindingContext dbc, final String propertyName) {
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.propertyName = propertyName;
}
public StringEditingSupport(final ColumnViewer viewer, final DataBindingContext dbc, final String propertyName, final ICellEditorValidator validator) {
super(viewer, dbc);
cellEditor = new CellEditorPrintValidatorErrors((Composite) viewer.getControl());
cellEditor.setValidator(validator);
this.propertyName = propertyName;
}
#Override
protected IObservableValue doCreateCellEditorObservable(final CellEditor cellEditor) {
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(final Object element, final ViewerCell cell) {
return BeansObservables.observeValue(element, propertyName);
}
#Override
protected CellEditor getCellEditor(final Object element) {
return cellEditor;
}
public String getErrorMessage(){
return cellEditor.getErrorMessage();
}
}
I believe it has something to do with the StringEditingSupport class. This class allows to edit the value in each cell of table. But I couldn't figure out a way to "update" the value shown in the GUI. As I understand input (of type WritableList) contains all the information. Here is the add button listener method:
private class AddButtonSelectionListener extends SelectionAdapter {
#Override
public void widgetSelected(final SelectionEvent e) {
String name = nameProperty;
String meaning = dataProperty;
final List<String> names = new ArrayList<String>();
final List<String> meanings = new ArrayList<String>();
for (final Object var : input) {
names.add(((FlowVar) var).getName());
meanings.add(((FlowVar) var).getData());
}
int index = 0;
while (names.contains(name)) {
name = nameProperty + ++index;
}
index = 0;
while (meanings.contains(meaning)) {
meaning = dataProperty + ++index;
}
input.add(new FlowVar(name, valueProperty, meaning));
}
}
So, as I understand, I need to somehow bind the input to the UI (the content of each cell). I did try many attempts like trying to set a listener to the whole table (varTableViewer.addSelectionChangedListener) but none of them worked. Is it possible to suggest a way to solve this kind of issue?
If anything is missing, please let me know and I'll add it.
At first my question is how to let a newly added row flash in JavaFx, then I went through a lot of questions related to this topic (such as javafx: table row flashing). Most of them are using setRowFactory and override the updateItem method by adding a Timeline animation which change the state of pseudoClass of the row.
Below is my code, I am trying to building a FlashControl which can be reused.
public class TableFlashControl<T> {
private PseudoClass flashHighlight = PseudoClass.getPseudoClass("flash-highlight");
private List<T> newAdded = new ArrayList<>();
private boolean isFilterApplied = false;
private boolean isSorted = false;
public void setIsFilterApplied(boolean isFilterApplied) {
this.isFilterApplied = isFilterApplied;
}
public void add(TableView<T> table){
ListChangeListener<T> change = c -> {
while (c.next()) {
if (c.wasAdded()) {
List<? extends T> added = c.getAddedSubList();
T lastAdded = added.get(0);
if (!isFilterApplied) {
newAdded.add(lastAdded);
}
}
}
};
table.getItems().addListener(change);
table.setRowFactory(param -> new TableRow<T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
return;
}
if (newAdded.contains(item)) {
if (isSorted) {
new Thread(()->{
Timeline flasher = new Timeline(
new KeyFrame(Duration.seconds(0.4), e -> pseudoClassStateChanged(flashHighlight, true)),
new KeyFrame(Duration.seconds(0.8), e -> pseudoClassStateChanged(flashHighlight, false))
);
flasher.setCycleCount(2);
flasher.play();
}).start();
if (item == newAdded.get(0)) {
newAdded.clear();
isSorted = false;
}
}else{
if(item == newAdded.get(0)){
isSorted = true;
}
}
}
}
});
}
}
Here ListChangeListener is associated with table.getItems() which helps me to record the newly inserted row.
It is possible to add multiple rows within one operation which means newAdded.size() can be larger than 1. What's more, rows are inserted from the top of the tableView(because I sort it with the Number.)
In tableView, not all rows are visible and updateItem methods only update those visible rows. My problem comes when these two situations happen(see below).
The first scenario
In first scenario, only 4 rows are visible, if user inserts 5 rows within one time, I cannot record the last row update(the updateItem won't be called for the new_row_5). Thereby, I cannot clear newAdded list (by doing newAdded.clear())
The second scenario
In the second scenario, only 4 rows are visible again. However, there are invisible rows both at top and bottom of the visible rows. If user inserts 2 rows, one will be visible and the other will be invisible. In my case, new_row_2 will flash while new_row_1 remains invisible. If user scrolls up the tableView when new_row_2 is flashing, he will see new_row_2 is flashing while new_row_1 is not which is really weird.
I also want to know if there is any way to find the number of visible rows.
I am still new to JavaFx and I don't know if this method is good or not. I hope someone can help me fix my problems. Thanks a lot!
Your approach doesn't seem like a clean way to do this. The animation depends on the TableRow the item is positioned in and does not seem to support multiple animations happening at the same time. Furthermore it relies on the equals method of the item class not being overridden and on the user not adding a item multiple times to the TableView. Also you potentially create a large number of Timelines (not necessary to start them from a seperate thread btw, since Timeline.play() does not block).
It's better to make the animation depend on the indices. Also keeping track of the TableRows created allows you to access existing cells, should they be be assigned a index that needs to be animated. Also you could handle the animations using a single AnimationTimer by storing the data in a suitable data structure.
Also it would IMHO be most convenient to use the rowFactory class to implement this logic.
The following example makes the rows flash whether they are on-screen or not.
public class FlashTableRowFactory<T> implements Callback<TableView<T>, TableRow<T>> {
private final static PseudoClass FLASH_HIGHLIGHT = PseudoClass.getPseudoClass("flash-highlight");
public FlashTableRowFactory(TableView<T> tableView) {
tableView.getItems().addListener((ListChangeListener.Change<? extends T> c) -> {
while (c.next()) {
if (c.wasPermutated()) {
int from = c.getFrom();
int to = c.getTo();
permutationUpdate(scheduledTasks, c, from, to);
permutationUpdate(unscheduledTasks, c, from, to);
}
if (c.wasReplaced()) {
addRange(c.getFrom(), c.getTo());
} else if (c.wasRemoved()) {
int from = c.getFrom();
int removed = c.getRemovedSize();
removeRange(scheduledTasks, from, from + removed);
removeRange(unscheduledTasks, from, from + removed);
modifyIndices(unscheduledTasks, from, -removed);
modifyIndices(scheduledTasks, from, -removed);
} else if (c.wasAdded()) {
int from = c.getFrom();
int to = c.getTo();
modifyIndices(unscheduledTasks, from, to - from);
modifyIndices(scheduledTasks, from, to - from);
addRange(from, to);
}
}
// remove all flashTasks that are older than the youngest for a
// given index
Set<Integer> indices = new HashSet<>();
removeDuplicates(unscheduledTasks, indices);
removeDuplicates(scheduledTasks, indices);
flashingIndices.clear();
updateFlash(lastUpdate);
refreshFlash();
if (!unscheduledTasks.isEmpty()) {
flasher.start();
}
});
this.tableView = tableView;
}
private static void removeDuplicates(List<FlashTask> list, Set<Integer> found) {
for (Iterator<FlashTask> iterator = list.iterator(); iterator.hasNext();) {
FlashTask next = iterator.next();
if (!found.add(next.index)) {
iterator.remove();
}
}
}
private static void modifyIndices(List<FlashTask> list, int minModify, int by) {
for (FlashTask task : list) {
if (task.index >= minModify) {
task.index += by;
}
}
}
private void addRange(int index, int to) {
for (; index < to; index++) {
unscheduledTasks.add(new FlashTask(index));
}
}
private static void removeRange(List<FlashTask> list, int from, int to) {
for (Iterator<FlashTask> iterator = list.iterator(); iterator.hasNext();) {
FlashTask next = iterator.next();
if (next.index >= from && next.index < to) {
iterator.remove();
}
}
}
private static void permutationUpdate(List<FlashTask> list, ListChangeListener.Change<?> c, int from, int to) {
for (FlashTask task : list) {
if (task.index < to && task.index >= from) {
task.index = c.getPermutation(task.index);
}
}
}
// set of item indices that should flash
private final Set<Integer> flashingIndices = new HashSet<>();
// references to every row created by this factory
private final List<SoftReference<TableRow<T>>> rows = new LinkedList<>();
// tasks waiting to be scheduled
private final List<FlashTask> unscheduledTasks = new LinkedList<>();
// tasks currently being animated
private final List<FlashTask> scheduledTasks = new LinkedList<>();
private static class FlashTask {
int index;
long schedulingTime;
public FlashTask(int index) {
this.index = index;
}
}
private final TableView<T> tableView;
private long lastUpdate;
/**
* Updates flashingIndices set
* #param now the current timestamp
* #return true if the set has been modified, otherwise false.
*/
private boolean updateFlash(long now) {
boolean modified = false;
for (Iterator<FlashTask> iterator = scheduledTasks.iterator(); iterator.hasNext();) {
FlashTask next = iterator.next();
// running time in seconds
double runningTime = (now - next.schedulingTime) / (1000d * 1000d * 1000d);
// slows down the animation for demonstration
final double animationSpeed = 0.1;
if (runningTime < 0.4 / animationSpeed) {
// no need to handle tasks that run for less than 0.4 seconds
break;
} else if (runningTime > 1.6 / animationSpeed) {
// end of task reached
iterator.remove();
modified |= flashingIndices.remove(next.index);
} else if (runningTime > 0.8 / animationSpeed && runningTime < 1.2 / animationSpeed) {
// second "inactive" interval during animation
modified |= flashingIndices.remove(next.index);
} else {
// activate otherwise
modified |= flashingIndices.add(next.index);
}
}
return modified;
}
private final AnimationTimer flasher = new AnimationTimer() {
#Override
public void handle(long now) {
lastUpdate = now;
// activate waiting flash tasks
for (FlashTask task : unscheduledTasks) {
task.schedulingTime = now;
}
scheduledTasks.addAll(unscheduledTasks);
unscheduledTasks.clear();
if (updateFlash(now)) {
refreshFlash();
if (scheduledTasks.isEmpty()) {
// stop, if there are no more tasks
stop();
}
}
}
};
/**
* Sets the pseudoclasses of rows based on flashingIndices set
*/
private void refreshFlash() {
for (Iterator<SoftReference<TableRow<T>>> iterator = rows.iterator(); iterator.hasNext();) {
SoftReference<TableRow<T>> next = iterator.next();
TableRow<T> row = next.get();
if (row == null) {
// remove references claimed by garbage collection
iterator.remove();
} else {
row.pseudoClassStateChanged(FLASH_HIGHLIGHT, flashingIndices.contains(row.getIndex()));
}
}
}
#Override
public TableRow<T> call(TableView<T> param) {
if (tableView != param) {
throw new IllegalArgumentException("This factory can only be used with the table passed to the constructor");
}
return new FlashRow();
}
private class FlashRow extends TableRow<T> {
{
rows.add(new SoftReference<>(this));
}
#Override
public void updateIndex(int i) {
super.updateIndex(i);
// update pseudoclass based on flashingIndices set
pseudoClassStateChanged(FLASH_HIGHLIGHT, flashingIndices.contains(i));
}
}
}
Greetings dear Stackoverflowians,
A couple of months ago I was dealing with a ILazyTreeContentProvider, and finally fixed it as per Eclipse RCP - ILazyTreeContentProvider implementation is unexpectedly eager
But I am facing the exact same problem with a ILazyContentProvider, and despite having followed similar steps as with the tree, I am at a loss.
In this table I am adding around 1000 elements per second in the table, and triggering a refresh via setItemCount() on the viewer every 100 ms.
The window size is smaller than 100 rows, and hence the updateElement() method should not start from the first index every time I call setItemCount() on the viewer.
Unfortunately, though, it does. It updates from 0 till the last index, each time.
Here's the code:
package manyelementscontentprovider;
import java.util.List;
import java.util.Vector;
import org.eclipse.jface.viewers.ILazyContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class LargeDataSetTable {
private class MyContentProvider implements ILazyContentProvider {
private TableViewer viewer;
public List<MyEntity> elements;
private int lastIndex=0;
public MyContentProvider(TableViewer viewer) {
this.viewer = viewer;
}
public void dispose() {
}
#SuppressWarnings("unchecked")
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
this.elements = (List<MyEntity>) newInput;
}
#Override
public void updateElement(int index) {
System.out.println(index);
if (!viewer.isBusy())
viewer.replace(elements.get(index), index);
}
}
public static class MyEntity {
public int counter;
public MyEntity(int counter) {
this.counter = counter;
}
public String toString() {
return "Item " + this.counter;
}
}
List<MyEntity> model;
private int counter;
private Display display;
private TableViewer v;
public LargeDataSetTable(Shell shell, Display display) {
model = createModel();
this.display=display;
v= new TableViewer(shell, SWT.VIRTUAL);
v.setLabelProvider(new LabelProvider());
v.setContentProvider(new MyContentProvider(v));
v.setInput(null);
v.setUseHashlookup(true);
counter = 0;
v.setInput(model);
v.setItemCount(model.size());
v.getTable().setLinesVisible(true);
}
private void startSomeShit() {
final Runnable gooeiUpdate = new Runnable() {
#Override
public void run() {
long timeA = System.currentTimeMillis();
v.setItemCount(counter);
v.setSelection( new StructuredSelection( model.get(counter-1) ), true );
v.setSelection(null);
long timeB = System.currentTimeMillis();
System.out.println("Paint lasted:"+(timeB-timeA));
}
};
Runnable addThingsToModel = new Runnable() {
public void run() {
long currentTime=System.currentTimeMillis();
long howManyGotIn =0;
while (counter<4000000) {
for (int i = 0; i< 10; i++){
final MyEntity m = new MyEntity(counter);
model.add(m);
counter++;
}
if (System.currentTimeMillis()-currentTime>100) {
howManyGotIn=counter - howManyGotIn;
display.syncExec(gooeiUpdate);
currentTime=System.currentTimeMillis();
System.out.println("How many got in = "+howManyGotIn);
howManyGotIn=counter;
}
try {
Thread.sleep(0,25);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread th = new Thread(addThingsToModel);
th.start();
}
private List<MyEntity> createModel() {
List<MyEntity> list = new Vector<MyEntity>(4000000);
return list;
}
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
LargeDataSetTable viewerCica = new LargeDataSetTable(shell,display);
shell.open();
viewerCica.startSomeShit();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
Any sort of suggestions, opinions and options are very appreciated. You guys rock!
The javadoc for
TableViewer.setSelection(ISelection selection, boolean reveal)
states the following:
Sets a new selection for this viewer and optionally makes it visible. The TableViewer implementation of this method is inefficient for the ILazyContentProvider as lookup is done by indices rather than elements and may require population of the entire table in worse case.
Use Table#setSelection(int[] indices) and Table#showSelection() if you wish to set selection more efficiently when using a ILazyContentProvider.
Therefore, you could write something like this:
v.getTable().setSelection(counter - 1);
v.getTable().showSelection();
Using this approach, the paint operation takes an average time of 10ms.
Here is some code snippet from the AbstractTableViewer#virtualSetSelectionToWidget(List list, boolean reveal), which is called, when you use v.setSelection(new StructuredSelection(model.get(counter - 1)), true);:
if (getContentProvider() instanceof ILazyContentProvider) {
ILazyContentProvider provider = (ILazyContentProvider) getContentProvider();
// Now go through it again until all is done or we are no longer
// virtual
// This may create all items so it is not a good
// idea in general.
// Use #setSelection (int [] indices,boolean reveal) instead
for (int i = 0; virtualElements.size() > 0 && i < doGetItemCount(); i++) {
provider.updateElement(i);
Item item = doGetItem(i);
if (virtualElements.contains(item.getData())) {
indices[count++] = i;
virtualElements.remove(item.getData());
if (firstItem == null) {
firstItem = item;
}
}
}
}
as you can see it always iterates over all elements (confessing, that It might not be the best idea), as per Eclipse 3.x. Tree viewer has different implementation (which is actually understandable, that there you actually have kind of visibility levels and in table you don't have those).
I think, that refreshing of elements in general could be handled without dependency on content provider, so that only visible elements are refreshed (at least on demand).
CompositeCell let us customize the content of a table cell's in GWT using Java. We can put almost any other group of widget within the table's cell and layout them as we want. Problem is that if we used the html tags to define the layout of the CompositeCell as yet another table (see CompositeCell anonymous class implementation bellow) we loose the event handling for the components of the cell :(.
Running the following code, when we click in the buttons of the cell will realize the popup in response of the event handling IF WE COMMENT the CompositeCell anonymous implementation.
I've been debugging CompositeCell.onBrowserEvent(Context, Element, C, NativeEvent, ValueUpdater) because i think that the definition of the cell layout using HTML table tags breaks the event chain within GWT widgets hierarchy but haven't been successful so far.
Remark: both commented and uncommented versions of the code realize the same GUI layout. This example just intent to show that we loose event handling when customizing cell's content.
public class ActionCellTest implements EntryPoint {
private static final String SERVER_ERROR = "An error occurred while " + "attempting to contact the server. Please check your network "
+ "connection and try again.";
private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class);
public void onModuleLoad() {
CellTable<Person> table = new CellTable<ActionCellTest.Person>();
final List<HasCell<Person, ?>> cells = new LinkedList<HasCell<Person, ?>>();
cells.add(new HasCellImpl("first name", new ActionCell.Delegate<Person>() {
#Override
public void execute(Person object) {
Window.alert(object.getFirstName());
}
}));
cells.add(new HasCellImpl("last name", new ActionCell.Delegate<ActionCellTest.Person>() {
#Override
public void execute(Person object) {
Window.alert(object.getLastName());
}
}));
CompositeCell<Person> cell = new CompositeCell<Person>(cells) {
#Override
public void render(Context context, Person value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<table><tbody><tr>");
for (HasCell<Person, ?> hasCell : cells) {
render(context, value, sb, hasCell);
}
sb.appendHtmlConstant("</tr></tbody></table>");
}
#Override
protected <X> void render(Context context, Person value, SafeHtmlBuilder sb, HasCell<Person, X> hasCell) {
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<td>");
cell.render(context, hasCell.getValue(value), sb);
sb.appendHtmlConstant("</td>");
}
#Override
protected Element getContainerElement(Element parent) {
return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
}
};
table.addColumn(new TextColumn<ActionCellTest.Person>() {
#Override
public String getValue(ActionCellTest.Person object) {
return object.getFirstName() + " " + object.getLastName();
}
}, "name");
table.addColumn(new Column<Person, Person>(cell) {
#Override
public Person getValue(ActionCellTest.Person object) {
return object;
}
}, "composite");
LinkedList<Person> data = new LinkedList<ActionCellTest.Person>();
data.add(new Person("Amy", "Reed"));
data.add(new Person("Tim", "Gardner"));
table.setRowData(data);
RootPanel.get().add(table);
}
private class HasCellImpl implements HasCell<Person, Person> {
private ActionCell<Person> fCell;
public HasCellImpl(String text, Delegate<Person> delegate) {
fCell = new ActionCell<Person>(text, delegate);
}
#Override
public Cell<Person> getCell() {
return fCell;
}
#Override
public FieldUpdater<Person, Person> getFieldUpdater() {
return null;
}
#Override
public Person getValue(Person object) {
return object;
}
}
private class Person {
private String fFirstName;
private String fLastName;
public Person(String first, String last) {
fFirstName = first;
fLastName = last;
}
public String getFirstName() {
return fFirstName;
}
public String getLastName() {
return fLastName;
}
}
}
This is a known issue which will be fixed in the upcoming GWT 2.5 (a matter of weeks): http://code.google.com/p/google-web-toolkit/issues/detail?id=5714
(in the mean time, you can run off trunk or try backporting the change)
I'm a newbie in java and I have a small problem. I want to access a variable in one class from another. I have three classes and I want to be able to access a variable in the main class to enable me read the array.
The error I am getting is
java.lang.SecurityException: MIDlet not constructed by createMIDlet
Please see the example below. Please bear in mind they're all in the same package.
package tungPackage;
import com.sun.lwuit.*;
import com.sun.lwuit.animations.CommonTransitions;
import com.sun.lwuit.events.ActionEvent;
import com.sun.lwuit.events.ActionListener;
import javax.microedition.midlet.MIDlet;
public class TungMidlet extends MIDlet implements ActionListener {
private Command back = new Command("Back");
private Command ok = new Command("Ok");
public ActionListener commandlistListener = new ActionListener() {
public void actionPerformed(ActionEvent cmd) {
// check which command cliked
if (cmd.getCommand() == back) {
// go back to previous form
mainForm.show();
} else if (cmd.getCommand() == ok) {
// go forward
}
}
};
private List list;
private Form mainForm;
private Label promptLabel;
private housesClass houseClassObject = new housesClass();
public int counter; //this is the variable I want to access in a class called calculate class object.
private int sumAmmt;
public TungMidlet tungMidletObject;
public calculateClass calculateClassObject;
public TungMidlet() {
Display.init(this);
}
private ActionListener applistListener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if(list.getSelectedIndex()==0){
counter++;
if (counter>5)
{
//check sum price.
sumAmmt = calculateClassObject.calculateSum();
Dialog x = new Dialog("info");
Label label = new Label("Maximum reached.");
Label label2 = new Label("Sum ammt = "+sumAmmt);
x.addComponent(label);
x.addComponent(label2);
x.addCommand(ok);
x.show();
}
else
{
//calculate the price
String info = houseClassObject.randomHouse();
Dialog x = new Dialog("info");
Label label = new Label(info);
x.addComponent(label);
x.addCommand(ok);
x.show();
}
}
}
};
public void startApp() {
//calculateClassObject = new calculateClass();
//sumAmmt = calculateClassObject.calculate(sumAmmt);
mainForm = new Form("Investment Categories");
promptLabel = new Label("choose category");
list = new List();
list.addItem("House");
list.addItem("Cars");
list.addItem("Schools");
list.addItem("Schools");
list.addItem("Supermarkets");
list.addItem("Stocks");
list.addItem("Land");
list.addActionListener(applistListener);
mainForm.addComponent(promptLabel);
mainForm.addComponent(list);
mainForm.addCommand(back);
mainForm.addCommandListener(commandlistListener);
mainForm.setTransitionInAnimator(CommonTransitions.createSlide(CommonTransitions.SLIDE_HORIZONTAL, true, 1000));
mainForm.show();
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
public void actionPerformed(ActionEvent ae) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
The class I want to access the "counter" variable using is shown below.
package tungPackage;
import java.util.Random;
public class housesClass {
public Random generator = new Random();
public String[] houseArray = new String[5];
public housesClass housesClassObject;
public calculateClass calcobj;// = new calculateClass();
public housesClass()
{
}
public String randomHouse() {
housesClassObject = new housesClass();
houseArray[0] = "Bungalow - 20,000,000 Shillings";
houseArray[1] = "Microhouse - 10,000,000 Shillings";
houseArray[2] = "Flat - 200,000,000 shillings";
houseArray[3] = "Garage apartment - 7,000,000 shillings";
houseArray[4] = "Studio apartment - 13,000,000 shillings";
int rnd = generator.nextInt(houseArray.length);
housesClassObject.housePrices(rnd);///noma
String house = houseArray[rnd];
return house;
}
void housePrices(int houseNumber) {
calcobj = new calculateClass();
TungMidlet tungmidobj = new TungMidlet();
int counter = tungmidobj.counter;
int[] housePriceArray = new int[5];
housePriceArray[0] = 20000000;
housePriceArray[1] = 10000000;
housePriceArray[2] = 200000000;
housePriceArray[3] = 7000000;
housePriceArray[4] = 13000000;
int price = housePriceArray[houseNumber];
calcobj.storePrice(counter,price);
}
}
The other supporting class is shown below.
package tungPackage;
public class calculateClass {
int[] storeArray = new int[5];
public calculateClass()
{
}
public void storePrice(int counter, int number2)
{
storeArray[counter] = number2;
}
public int calculateSum()
{
int sum =0;
for(int i=1; i<6; i++){
sum= sum+storeArray[i];
}
return sum;
}
}
Are you getting an error? It looks like your access code should work.
I can't seem to find anywhere that you actually initialise counter though, so maybe your problem is that you need to put counter = 0; somewhere in your code.
Java is also object oriented so you should avoid accessing like the above and make some 'getter and setter' methods:
public int getCounter() {
return counter;
}
and then call int counter = tungmidobj.getCounter();
remove TungMidlet constructor. If there was something useful to do there, you could also declare it protected - but this is not the case with your code snippet, see below.
Wherever you try to invoke that constructor directly, remove code that does this and find another way to do what you need. If needed, study code examples provided in LWUIT Tutorial - Introduction for how typical things are done in LWUIT.
put statement Display.init() in the beginning of the startApp method,
just like it is done in LWUIT Tutorial - Hello, LWUIT! example code
The reason why you are getting SecurityException is because you invoke TungMidlet constructor directly. Don't do that.
MIDP API documentation for MIDlet constructor states:
Throws:
SecurityException - unless the application management software is creating the MIDlet.
one way is
TungMidlet tungMidlet=new TungMidlet();
System.out.println(tungMidlet.counter);
but know encapsulation
second way is
you can make counter private variable and provide setter and getters.
private int counter;
public void setCounter(int counter){
this.counter=counter;
}
public int getCounter(){
return counter;
}
second way is preferred way as it achieves encapsulation