how to let tableRow flash even if it is not visible? (JavaFx) - java

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));
}
}
}

Related

JavaFX ObservableList with extractor update event not firing when it should

A little background:
Before I had a problem where I had ObservableList with extractor, but the properties that extractor was listening to were updated from NOT JavaFX thread. The solution I came up with was to create "UI equivalent" classes that would copy values of original class, just update their properties on JavaFX thread. For example, if we have a Person:
public class Person {
private final BooleanProperty adult;
public Person(boolean adult) {
this.adult = new SimpleBooleanProperty(adult);
// Randomize "adult" value every 5 seconds
ListViewExtractorTest.scheduledExecutorService.scheduleAtFixedRate(() -> {
this.adult.set(Math.random() > 0.5);
System.out.println("Updating adult: " + this);
}, 5, 5, TimeUnit.SECONDS);
// Getters setters
}
UI equivalent class would be:
public class PersonUI {
private final Person person;
private final BooleanProperty adult;
public PersonUI(Person person) {
this.person = person;
adult = new SimpleBooleanProperty(person.isAdult());
person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
Platform.runLater(() -> this.adult.set(newValue));
});
}
// Getters and setters
}
I did the same with ObservableList - created a method that would add a ListChangeListener to source list and whenever source list gets updated it would update destination list with UI equivalent classes on JavaFX thread:
public static <T, R> ObservableList<R> bindListContentPlatformRunLater(ObservableList<T> srcList, Function<T, R> function, ObservableList<R> dstList) {
for (T item : srcList) {
dstList.add(function.apply(item));
}
// Maybe should wrap the whole while loop in Platform.runLater()
// Less runnables, but big changes might hang up the UI.
srcList.addListener((ListChangeListener<? super T>) change -> {
while (change.next()) {
int from = change.getFrom();
int to = change.getTo();
if (change.wasPermutated()) {
Platform.runLater(() -> dstList.subList(from, to).clear());
List<? extends T> addItems = change.getList().subList(from, to);
for (int i = 0; i < addItems.size(); i++) {
final int index = i;
T addItem = addItems.get(i);
Platform.runLater(() -> dstList.add(from + index, function.apply(addItem)));
}
} else {
if (change.wasRemoved()) {
int removedSize = change.getRemovedSize();
Platform.runLater(() -> dstList.subList(from, from + removedSize).clear());
}
if (change.wasAdded()) {
List<? extends T> addedSubList = change.getAddedSubList();
for (int i = 0; i < addedSubList.size(); i++) {
final int index = i;
T item = addedSubList.get(i);
Platform.runLater(() -> dstList.add(from + index, function.apply(item)));
}
}
}
}
});
return dstList;
}
so now if I have ObservableList<Person> that is updated and has its item properties updated NOT on JavaFX thread, I can get a list that I can display in JavaFX easily:
ObservableList<PersonUI> secondList = FXCollections.observableArrayList(personUI -> new Observable[]{personUI.adultProperty()});
UIClassUtil.bindListContentPlatformRunLater(originalList, PersonUI::new, secondList);
so now the problem:
My expectation is that when secondList is displayed in ListView<PersonUI> it would update whenever Person#adultProperty values changes, but the expected behavior lasts only for a few seconds, after that the ListView stops updating and that happens because secondList "update" event stops firing. My guess is that PersonUI#adultProperty gets garbage collected after few seconds because it's not used anywhere else except in extractor??
To reproduce the issue I'm randomly changing the value of Person#adultProperty every few seconds. Full code without Person and PersonUI classes:
public final class ListViewExtractorTest extends Application {
private static final ObservableList<Person> originalList = FXCollections.observableArrayList();
private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
static {
for ( int a = 0; a < 5; a++ ) {
originalList.add(new Person(Math.random() > 0.5));
}
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<PersonUI> secondList = FXCollections.observableArrayList(personUI -> new Observable[]{personUI.adultProperty()});
UIClassUtil.bindListContentPlatformRunLater(originalList, PersonUI::new, secondList);
secondList.addListener((ListChangeListener<? super PersonUI>) change -> {
while (change.next()) {
if (change.wasUpdated()) {
System.out.println("List updated!");
}
}
});
ListView<PersonUI> listViewPerson = new ListView<>(secondList);
ListView<PersonUI> listViewForceRefresh = new ListView<>(secondList);
stage.setScene(new Scene(new HBox(5, listViewPerson, listViewForceRefresh)));
stage.setTitle("Hello");
stage.show();
scheduledExecutorService.scheduleAtFixedRate(() -> {
Platform.runLater(listViewForceRefresh::refresh);
}, 0, 100, TimeUnit.MILLISECONDS);
}
public static void main(String[] args) {
launch();
}
}
So I have two ListView: one on the left that should get updated whenever Person#adultProperty value changes and one on the right that I force refresh every 100ms. At first the lists are synced, but after a few seconds only the right list displays the correct values and left list gets stuck on same values and "List updated" of ListChangeListener gets printed only for the same first few seconds. So that means "update" event of the list is fired only for the same first few seconds.
However as soon as I start working with PersonUI#adultProperty(print it in console every few seconds using another thread) it starts behaving in expected behavior, so my guess is that it gets garbage collected if not used.
Any idea how could I make these properties of UI classes work as long as UI class itself is alive or somehow else achieve the expected behavior?
A simple fix to prevent property from being garbage collected too early was to update listener in PersonUI constructor from:
person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
Platform.runLater(() -> this.adult.set(newValue));
});
to:
person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
this.adult.get()
Platform.runLater(() -> this.adult.set(newValue));
});

How to update the model for a CheckBoxMultipleChoice?

The ClassChoice control inherits CheckBoxMultipleChoice. It is a common control used on multiple pages with selections persisted in the session. The available choices are obtained from the database. An "All" checkbox is added when there is more than one data item. On some pages a selection change causes the page to be refreshed with new data. On other page the selections should change without a refresh.
My problem is that I need to control the "All" checkbox when other checkboxes change and to change all of the checkboxes when the "All" checkbox changes.
I tried to call updateModel() to force a change but that did not work. How can I change the selections (the model parameter) without refreshing the page?
This edited code does not show page refreshing.
public class ClassChoice<T> extends CheckBoxMultipleChoice
{
private static final long serialVersionUID = 1L;
#SpringBean
private ClassService classService;
List<EntityClassModel> selection;
EntityClassModel ecmAll;
static List<EntityClassModel> availableClasses;
public ClassChoice(..)
{
super("classcheckboxes");
setSuffix(" "); // sets checkbox separator and ensures inline display
ecmAll = (EntityClassModel) modelFactory.getNewClassModel();
ecmAll.setClassname("All");
// List of all classes associated with user
availableClasses = classService.getListOfClasses(..);
setClassChoices();
add( new AjaxFormChoiceComponentUpdatingBehavior()
{
private static final long serialVersionUID = 1L;
#Override
protected void onUpdate(AjaxRequestTarget target)
{
List<Integer> previousIDs = UserSession.get().getSelectedClassIDs();
if ((previousIDs.size() > 0) && ((previousIDs.size() + 1) >= availableClasses.size()))
{
// Was previously Select All
if (selection.get(selection.size() - 1) == ecmAll)
{
// Select All still selected, remove it
selection.remove(selection.size() - 1);
}
else
{
// Remove all selections
selection.clear();
}
}
else if (selection.size() > 0)
{
// Was none or some selected
if (selection.get(selection.size() - 1) == ecmAll)
{
// Select All, select all available
selection.clear();
selection.addAll(availableClasses);
}
else if ((selection.size() + 1) >= availableClasses.size())
{
// Is now full, add Select All
selection.add(ecmAll);
}
// else change but no special handling required
}
// else none are currently selected
UserSession.get().setSelectedClasses(selection);
// Generate a list of selected class IDs, excluding All
List<Integer> selectedIDs = new ArrayList<Integer>();
int copysize = selection.size();
if ((copysize > 0) && (selection.get(copysize - 1) == ecmAll))
{
copysize--;
}
for (int index = 0; index < copysize; index++)
{
selectedIDs.add(selection.get(index).getId());
}
UserSession.get().setSelectedClassIDs(selectedIDs);
// Update the selections on the page
updateModel();
}
});
Initialize();
}
#SuppressWarnings("unchecked")
protected void Initialize()
{
// Grabs already selected classes from UserSession
List<Integer> selectedIDs = UserSession.get().getSelectedClassIDs();
selection = classService.getClassesByClassIDs(selectedIDs);
if (selectedIDs.size() > 1)
{
if ((selectedIDs.size() + 1) >= availableClasses.size())
{
selection.add(ecmAll);
}
}
setModel(Model.ofList(selection));
// Configure the data and display
setChoiceRenderer(new ChoiceRenderer<EntityClassModel>("classname", "id"));
setOutputMarkupId(true);
}
#SuppressWarnings("unchecked")
public void setClassChoices()
{
// Adds 'All' option when there is more than one class
if (availableClasses.size() > 1)
{
availableClasses.add(ecmAll);
}
setChoices(availableClasses);
}
public List<EntityClassModel> getSelection()
{
return selection;
}
}
You have to use the AjaxRequestTarget to update the HTML element on the browser side. By adding/removing elements to selection you change the model of ClassChoice at the server side. At the bottom of AjaxFormChoiceComponentUpdatingBehavior#onUpdate() you should do target.add(this) to tell Wicket to repaint this ClassChoice instance with its new selection/model.
Make sure you call setOutputMarkupId(true) in its constructor because otherwise you won't be able to update it with Ajax.

Run Runnables concurrently in order

So here's my situation:
I got a few Threads that should do background work, ideally with a ThreadPool/ExecutorService and such
There are a lot of Runnables generated regularly that call one long method. They should be processed by the background workers.
The runnables have an order they should be executed in (approximately). The interesting thing is: that ordering is dynamic and might change at any time. So which runnable to take next should be decided as late as possible, directly before running it.
It should be possible to stop all currently working runnables. If this is not possible, they should be notified so that they discard their work once it's finished.
I don't really know how to approach this problem, and I'm not really familiar with multithreading and Java's APIs in that matter.
About the ordering
What I mean with approximately in order: if they get started in order, it will be good enough. Each Runnable does some work on a tile of a map. The idea is to sort the runnables in such a way, that tiles near the position where the used is looking at will be loaded first and then loading the surroundings. Note that therefore the order of execution might change at any time.
One solution is to put all the jobs that you want to process into a PriorityBlockingQueue. (This queue is automatically sorted either using the natural ordering of the queue items or by providing a Comparator). then the threads running within the ExecutorService should just take elements from the queue.
for example
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
public class PriorityQueueExample {
public static void main(String[] args) throws InterruptedException {
PriorityQueueExample priorityQueueExample = new PriorityQueueExample();
priorityQueueExample.doTheWork();
}
private void doTheWork() throws InterruptedException {
PriorityBlockingQueue<Customer> queue = new PriorityBlockingQueue<>(10, new CustomerComparator());
queue.add(new Customer("John", 5));
queue.add(new Customer("Maria", 2));
queue.add(new Customer("Ana", 1));
queue.add(new Customer("Pedro", 3));
while(queue.size() > 0){
System.out.println(queue.take());
}
}
}
class CustomerComparator implements Comparator<Customer> {
#Override
public int compare(Customer o1, Customer o2) {
return o1.getUrgency() - o2.getUrgency();
}
}
class Customer {
private String name;
private int urgency;
public Customer(String name, int urgency) {
this.name = name;
this.urgency = urgency;
}
public String getName() {
return name;
}
public int getUrgency() {
return urgency;
}
#Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", urgency=" + urgency +
'}';
}
}
1) Have your tiles implements Callable. You can have them return Callable too.
2) Determine which ones are position to be loaded first.
3) Pass them or their Callables into java.util.concurrent.ExecutorService.invokeAll.
4) Once invokeAll is returned get the next set of tiles adjacent to the previous ones and call java.util.concurrent.ExecutorService.invokeAll again.
5) Repeat step 4 if necessary.
you could also use a List to emulate a priority queue. For example:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ListEmulateQueueExample {
public static void main(String[] args) throws InterruptedException {
ListEmulateQueueExample priorityQueueExample = new ListEmulateQueueExample();
priorityQueueExample.doTheWork();
}
/**
* uses a list to emulate a queue.
*/
private void doTheWork() {
List<Customer> customerList = Collections.synchronizedList(new ArrayList<>());
Customer johnCustomer = new Customer("John", 5);
Customer mariaCustomer = new Customer("Maria", 3);
Customer anaCustomer = new Customer("Ana", 1);
customerList.add(johnCustomer);
customerList.add(mariaCustomer);
customerList.add(anaCustomer);
CustomerComparator customerComparator = new CustomerComparator();
synchronized (customerList){
customerList.sort(customerComparator);
}
System.out.println(customerList.remove(0)); // Ana
johnCustomer.setUrgency(1);
synchronized (customerList){
customerList.sort(customerComparator);
}
System.out.println(customerList.remove(0)); // John
}
}
So, I finally got a way around this problem. It's not that beautiful and kind of a hack, but it works as intended.
The idea is: if every Runnable is stateless and does only call one method, it does not need to know the tile it should work on on creation. Instead, it will ask for a needed tile once it's started.
public class WorldRendererGL {
protected Map<Vector2i, RenderedRegion> regions = new ConcurrentHashMap<>();
protected Queue<RegionLoader> running = new ConcurrentLinkedQueue<>();
protected Set<RenderedRegion> todo = ConcurrentHashMap.newKeySet();
protected ExecutorService executor;
/** Recalculate everything */
public void invalidateTextures() {
//Abort current calculations
running.forEach(f -> f.invalid.set(true));
running.clear();
todo.addAll(regions.values());
for (int i = 0; i < regions.size(); i++) {
RegionLoader loader = new RegionLoader();
running.add(loader);
executor.submit(loader);
}
}
protected class RegionLoader implements Runnable {
/** Set this to true to nullify all calculations*/
final AtomicBoolean invalid = new AtomicBoolean(false);
#Override
public void run() {
try {
if (invalid.get())
return;
RenderedRegion region = null;
region = nextRegion(); // Get the correct work at runtime
if (region == null)
return;
BufferedImage texture = renderer.renderRegion(new RegionFile(region.region.regionFile));
if (!invalid.get()) {
region.texture = texture;
update.notifyObservers();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
protected RenderedRegion nextRegion() {
Comparator<RenderedRegion> comp = (a, b) -> /*...*/);
RenderedRegion min = null;
for (Iterator<RenderedRegion> it = todo.iterator(); it.hasNext();) {
RenderedRegion r = it.next();
if (min == null || comp.compare(min, r) > 0)
min = r;
}
todo.remove(min);
return min;
}
}

How to reverse vaadin Grid?

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

How can i Analyse and Discover all possible paths in an activity network

Hey , i have been developing this kind of a "project management tool" for study it is supposed to calculate tasks durations and cost to determine a critical path for any given project like this for example :
en example of a project and tasks relations
**until now i calculated all the durations and starts and finishes but i'm stuck with how to discover all the possible paths in such a graph and how to traverse this structure from the initial node to the final node and determine witch path will take the longest duration and i have no idea how to continue so thanks in advance for helping me **
I implemented a Task Class as follow :
public class Task {
private String name;
private int duration;
private int earlyStart;
private int earlyFinish;
private int lateStart;
private int lateFinish;
private int totalFloat;
private HashSet<Task> predecessors;
private HashSet<Task> successors;
private String[] dependencies;
public Task(String taskName, int taskDuration, String[] dependencies) {
// Initialize Attributes
this.name = taskName;
this.duration = taskDuration;
this.dependencies = dependencies;
this.predecessors = new HashSet<Task>();
this.successors = new HashSet<Task>();
}
}
Ps : i didn't include the getters and setters
and i have also a class called project implemented as follow :
public class Project {
private HashSet<Task> tasks;
private HashSet<Task> initialTasks;
private HashSet<Task> finalTasks;
private int maxDuration;
public Project() {
this.tasks = new HashSet<Task>();
}
public void initialize(){
this.calculateTasksRelation();
this.calculateInitialTasks();
this.calculateInitialTasksEarlies();
this.forwardPass();
this.calculateFinalTasks();
this.calculateMaxDuration();
this.calculateFinalTasksLates();
this.backwardPass();
}
public void addTask(Task task) {
this.tasks.add(task);
}
public Task getTaskByName(String taskName) {
for (Task task : tasks) {
if(task.getName().equals(taskName)){
return task;
}
}
return null;
}
public HashSet<Task> getAllTasks() {
return tasks;
}
/**
* Private Methods internal Usage Only
* */
private void calculateTasksRelation() {
for (Task current : tasks) {
if ( current.getDependencies() != null ) {
for (String string : current.getDependencies() ) {
if (this.getTaskByName(string) != null) {
Task dependencie = this.getTaskByName(string);
current.addPredecessor(dependencie);
dependencie.addSuccessor(current);
}
}
}
}
}
// Return only the tasks that dosn't have predecessors
private void calculateInitialTasks(){
HashSet<Task> remaining = new HashSet<Task>(this.tasks);
// itertare over the remaining and remove all tasks
// that are successors = they have predecessor
for (Task current : tasks) {
for (Task successor : current.getSuccessors()) {
remaining.remove(successor);
}
}
this.initialTasks = new HashSet<>(remaining);
}
private void calculateInitialTasksEarlies() {
for (Task initialTask : this.initialTasks) {
initialTask.setEarlyStart(0);
initialTask.setEarlyFinish(initialTask.getEarlyStart() + initialTask.getDuration());
}
}
private void calculateMaxDuration() {
for (Task task : finalTasks) {
if(task.getEarlyFinish() > this.maxDuration) {
this.maxDuration = task.getEarlyFinish();
}
}
}
// Return only the tasks that dosn't have any successors
private void calculateFinalTasks() {
HashSet<Task> remaining = new HashSet<Task>(this.tasks);
// itertare over the remaining and remove all tasks
// that are predecessors = they have successor
for (Task current : tasks) {
for (Task predecessor : current.getPredecessors()) {
remaining.remove(predecessor);
}
}
this.finalTasks = new HashSet<>(remaining);
}
private void calculateFinalTasksLates() {
for (Task endTask : this.finalTasks) {
endTask.setLateFinish(this.maxDuration);
endTask.setLateStart(endTask.getLateFinish() - this.maxDuration);
}
}
private void forwardPass() {
// tasks whose early starts has been calculated
HashSet<Task> completed = new HashSet<Task>(initialTasks);
// tasks whose early starts has not been calculated yet
HashSet<Task> remaining = new HashSet<Task>(tasks);
remaining.removeAll(initialTasks);
// Backflow algorithm
// while there are tasks whose early start isn't calculated.
while (!remaining.isEmpty()) {
boolean progress = false;
for (Task currentTask : this.tasks) {
if(completed.containsAll(currentTask.getPredecessors())){
int temp = 0 ;
for ( Task dependencie : currentTask.getPredecessors() ) {
if( dependencie.getEarlyFinish() > temp ){
// update the temp variable
temp = dependencie.getEarlyFinish();
}
}
currentTask.setEarlyStart(temp);
currentTask.setEarlyFinish(currentTask.getEarlyStart() + currentTask.getDuration());
// set the task as completed and remove it from the remaining
completed.add(currentTask);
remaining.remove(currentTask);
// note that we are making a progress
progress = true;
}
}
// If we haven't made any progress then a cycle must exist in
// the graph and we wont be able to calculate the critical path
if (!progress)
throw new RuntimeException("Cyclic dependency, algorithm stopped!");
}
}
private void backwardPass() {
// tasks whose early starts has been calculated
HashSet<Task> completed = new HashSet<Task>(this.finalTasks);
// tasks whose early starts has not been calculated yet
HashSet<Task> remaining = new HashSet<Task>(tasks);
remaining.removeAll(finalTasks);
// Backflow algorithm
// while there are tasks whose early start isn't calculated.
while (!remaining.isEmpty()) {
boolean progress = false;
for (Task currentTask : this.tasks) {
if(completed.containsAll(currentTask.getSuccessors())){
int temp = this.maxDuration;
for ( Task successor : currentTask.getSuccessors() ) {
if( successor.getLateStart() < temp ){
// update the temp variable
temp = successor.getLateStart();
}
}
currentTask.setLateFinish(temp);
currentTask.setLateStart(currentTask.getLateFinish() - currentTask.getDuration());
// set the task as completed and remove it from the remaining
completed.add(currentTask);
remaining.remove(currentTask);
// note that we are making a progress
progress = true;
}
}
// If we haven't made any progress then a cycle must exist in
// the graph and we wont be able to calculate the critical path
if (!progress)
throw new RuntimeException("Cyclic dependency, algorithm stopped!");
}
}
}
Ps : Sorry for the long code :)
Finding the longest path from one source node to another one is an NP-hard problem in the general form, so there is no polynomial solution to it.
Contrary to it, finding the shortest path is kind of easy to solve using Dijkstra's or Bellman-Ford's algorithm.
I am not an expert, but I would really suggest to reconsider what you can and what you cannot do in the exercise.

Categories

Resources