In GXT (from Sencha) I am attempting to use a ProgressMessageBox to show the user that work is being done, but I am not seeing the message box until after all of the work is complete. Here is the code:
final ProgressMessageBox messageBox = new ProgressMessageBox("Task Description",
"Executing Task...");
messageBox.setProgressText("Calculating...");
messageBox.setPredefinedButtons();
messageBox.show();
for (int i = 0; i < 5; ++i) {
for (long l = 0l; l < 10000000000l; ++l) {
if (l == 12345l) {
MyUtil.info(60, "" + System.currentTimeMillis() / 1000);
}
}
messageBox.updateProgress((double)(i + 1) / 5, "{0}% Complete");
}
This code is in the SelectionHandler of a menu item. Obviously I am not actually just looping a few billion times in the "real" code, but when I perform the work that I want to execute I get the same result ... the message box is shown after all the work has been completed. I see the "info" messages (MyUtil#info is simply a wrapper around the GXT Info capability, which causes an info message to be displayed for the specified number of seconds) ... and on my machine, running the code shown, each message has a value that is about seven seconds greater than the previous message (but they all show up at the same time as the message box).
Is there something that I need to do after calling ProgressMessageBox#updateProgress to force the screen or message box to refresh?
I was able to get this to work by putting the task into a Scheduler#execute method. The example in the Sencha Explorer Demo uses a Timer, but since I don't know how long the execution will take I chose to use the Scheduler approach. Here is the relevant final code:
...
menuItem.addSelectionHandler(new SelectionHandler<Item>() {
#Override
public void onSelection(final SelectionEvent<Item> selectionEvent) {
final ProgressMessageBox messageBox = new ProgressMessageBox("Size All Columns", //
"Resizing Columns...");
messageBox.setProgressText("Calculating...");
// messageBox.setPredefinedButtons();
messageBox.show();
resizeNextColumn(messageBox,
_selectionModel instanceof CheckBoxSelectionModel ? 1 : 0,
_grid.getColumnModel().getColumnCount() - 1);
}
});
...
private void resizeNextColumn(final ProgressMessageBox messageBox,
final int columnIndex,
final int lastColumnIndex) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
resizeColumnToFit(columnIndex);
if (columnIndex == lastColumnIndex) {
messageBox.hide();
_grid.getView().refresh(true);
return;
}
messageBox.updateProgress((double)columnIndex / (lastColumnIndex + 1),
"{0}% Complete");
resizeNextColumn(messageBox, columnIndex + 1, lastColumnIndex);
}
});
}
One thing that didn't appear to work is the messageBox.setPredefinedButtons() call ... when I use this the progress bar in the message box doesn't update at all. I really didn't want an "OK" button on the dialog, but for now it'll have to do. I'm using 3.1.0 of GXT.
Related
I use this package https://pub.dev/packages/stop_watch_timer in my app to keep track of the music that is playing. However if I want to change the song by changing the time on the stopwatch it says that I have to reset the timer first which I have already done. If I press the button for the second time it works. This is the code:
final StopWatchTimer _stopWatchTimer = StopWatchTimer(
mode: StopWatchMode.countUp,
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
);
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
_stopWatchTimer.setPresetSecondTime(new_time); // this is where I set new time
}
I don't know how to get around this. I have already created an issue on the creators GitHub but no response. So there's somebody who can help me here
As you mentioned in the github issue, it looks like the root cause of your issue is that the reset action takes place asynchronously, and so hasn't gone through yet by the time you try to set the time.
One way to get around this is to define your own async function which resets the stopwatch, then waits for the action to complete before returning:
Future<void> _resetTimer() {
final completer = Completer<void>();
// Create a listener that will trigger the completer when
// it detects a reset event.
void listener(StopWatchExecute event) {
if (event == StopWatchExecute.reset) {
completer.complete();
}
}
// Add the listener to the timer's execution stream, saving
// the sub for cancellation
final sub = _stopWatchTimer.execute.listen(listener);
// Send the 'reset' action
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
// Cancel the sub after the future is fulfilled.
return completer.future.whenComplete(sub.cancel);
}
Usage:
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_resetTimer().then(() {
_stopWatchTimer.setPresetSecondTime(new_time);
});
}
Or (with async/await):
void change_timer_value(int song_index) async {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
await _resetTimer();
_stopWatchTimer.setPresetSecondTime(new_time);
}
I have a PageView, I want scroll pageView programmatically, so I have two choices:
use animateToPage
use jumpToPage
now, I need smooth transition effect, so I have to use first api. But, I have a problem: animateToPage will load middle pages which don't need to be shown at this time when I scroll from the first page to the last page.(jumpToPage don't have this problem, but I need animation).
How to avoid it?
We can achieve that by
Swap lastPage Widget to next position of current page
Animate to next page
Jump to real lastPage index
Refresh swapped Index to its previous value
In this example, I used fixed PageView children count, which is 8.
Demo
Comparison
Combine to 8th page Button
as CopsOnRoad suggested, this button will trigger Scroll animation to last page (in this case 8th page). Firstly, we
jumpToPage(6), and then animateToPage(7, ..).
This method works, but adversely, user will notice sudden change of current page to 7th page.
Flash Jump to 8th page Button
Unlike like first method, this button will avoid displaying 7th page unnecessarily
Syntax Explanation
this is the main function
void flashToEight() async {
int pageCurrent = pageController.page.round();
int pageTarget = 7;
if (pageCurrent == pageTarget){
return;
}
swapChildren(pageCurrent, pageTarget); // Step # 1
await quickJump(pageCurrent, pageTarget); // Step # 2 and # 3
WidgetsBinding.instance.addPostFrameCallback(refreshChildren); // Step # 4
}
detailed look
// Step # 1
void swapChildren(int pageCurrent, int pageTarget) {
List<Widget> newVisiblePageViews = [];
newVisiblePageViews.addAll(pageViews);
if (pageTarget > pageCurrent) {
newVisiblePageViews[pageCurrent + 1] = visiblePageViews[pageTarget];
} else if (pageTarget < pageCurrent) {
newVisiblePageViews[pageCurrent - 1] = visiblePageViews[pageTarget];
}
setState(() {
visiblePageViews = newVisiblePageViews;
});
}
// Step # 2 and # 3
Future quickJump(int pageCurrent, int pageTarget) async {
int quickJumpTarget;
if (pageTarget > pageCurrent) {
quickJumpTarget = pageCurrent + 1;
} else if (pageTarget < pageCurrent) {
quickJumpTarget = pageCurrent - 1;
}
await pageController.animateToPage(
quickJumpTarget,
curve: Curves.easeIn,
duration: Duration(seconds: 1),
);
pageController.jumpToPage(pageTarget);
}
// Step # 4
List<Widget> createPageContents() {
return <Widget>[
PageContent(1),
PageContent(2),
PageContent(3),
PageContent(4),
PageContent(5),
PageContent(6),
PageContent(7),
PageContent(8),
];
}
void refreshChildren(Duration duration) {
setState(() {
visiblePageViews = createPageContents();
});
}
Full Working-Example Repository
You may look into full source code and build locally. Github
I have a table which uses infinite scroll to load more results and append them, when the user reaches the bottom of the page.
At the moment I have the following code:
var currentPage = 0;
var tableContent = Rx.Observable.empty();
function getHTTPDataPageObservable(pageNumber) {
return Rx.Observable.fromPromise($http(...));
}
function init() {
reset();
}
function reset() {
currentPage = 0;
tableContent = Rx.Observable.empty();
appendNextPage();
}
function appendNextPage() {
if(currentPage == 0) {
tableContent = getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; });
} else {
tableContent = tableContent.combineLatest(
getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; }),
function(o1, o2) {
return o1.concat(o2);
}
)
}
}
There's one major problem:
Everytime appendNextPage is called, I get a completely new Observable which then triggers all prior HTTP calls again and again.
A minor problem is, that this code is ugly and it looks like it's too much for such a simple use case.
Questions:
How to solve this problem in a nice way?
Is is possible to combine those Observables in a different way, without triggering the whole stack again and again?
You didn't include it but I'll assume that you have some way of detecting when the user reaches the bottom of the page. An event that you can use to trigger new loads. For the sake of this answer I'll say that you have defined it somewhere as:
const nextPage = fromEvent(page, 'nextpage');
What you really want to be doing is trying to map this to a stream of one directional flow rather than sort of using the stream as a mutable object. Thus:
const pageStream = nextPage.pipe(
//Always trigger the first page to load
startWith(0),
//Load these pages asynchronously, but keep them in order
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
//One option of how to join the pages together
scan((pages, p) => ([...pages, p]), [])
)
;
If you need reset functionality I would suggest that you also consider wrapping that whole stream to trigger the reset.
resetPages.pipe(
// Used for the "first" reset when the page first loads
startWith(0),
//Anytime there is a reset, restart the internal stream.
switchMapTo(
nextPage.pipe(
startWith(0),
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
scan((pages, p) => ([...pages, p]), [])
)
).subscribe(x => /*Render page content*/);
As you can see, by refactoring to nest the logic into streams we can remove the global state that was floating around before
You can use Subject and separate the problem you are solving into 2 observables. One is for scrolling events , and the other is for retrieving data. For example:
let scrollingSubject = new Rx.Subject();
let dataSubject = new Rx.Subject();
//store the data that has been received back from server to check if a page has been
// received previously
let dataList = [];
scrollingSubject.subscribe(function(page) {
dataSubject.onNext({
pageNumber: page,
pageData: [page + 10] // the data from the server
});
});
dataSubject.subscribe(function(data) {
console.log('Received data for page ' + data.pageNumber);
dataList.push(data);
});
//scroll to page 1
scrollingSubject.onNext(1);
//scroll to page 2
scrollingSubject.onNext(2);
//scroll to page 3
scrollingSubject.onNext(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
I have a working ProgressMonitorDialog, but I want to make sure that I am setting it up correctly.
First the Code:
Method to create Dialog
public void startProgressBar() {
try {
new ProgressMonitorDialog(getShell()).run(true, true,
new ProgressBarThread());
}
catch (InvocationTargetException e) {
MessageDialog.openError(getShell(), "Error", e.getMessage());
}
catch (InterruptedException e) {
MessageDialog.openInformation(getShell(), "Cancelled", e.getMessage());
}
}
Class File
class ProgressBarThread implements IRunnableWithProgress {
private static final int TOTAL_TIME = 1000;
public ProgressBarThread() {
}
public void run(IProgressMonitor monitor) throws InvocationTargetException,InterruptedException {
monitor.beginTask("Creating PDF File(s): Please wait.....", IProgressMonitor.UNKNOWN);
for (int total = 0; total < TOTAL_TIME ; total++) {
Thread.sleep(total);
monitor.worked(total);
if (total == TOTAL_TIME / 2) monitor.subTask("Please be patient... Operation should finish soon.");
}
monitor.done();
}
}
Method that calls the ProgressBar and runs a Pdf file creation Operation
private void startSavePdfOperation() {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
startProgressBar();
}
});
saveOp = new AplotSaveOperation(appReg.getString("aplot.message.SAVETOPDF"), "PDF", session);
saveOp.addOperationListener(new MyOperationListener(this) {
public void endOperationImpl() {
java.io.File zipFile = null;
try {
AplotSaveResultsParser.SaveResult saveResults = saveOp.getSaveResults();
if (saveResults != null) {
ETC..... ETC......
Questions:
Being the ProgressMonitorDialog is a GUI, it needs to be executed in a
Display.getDefault().asyncExec?
If the ProgressMonitorDialog is running in a separate thread, how does it know to close when the operation is finsihed?
Is there any relationship between the progressbar and the operation?
I am correct in assuming that the for loop in the ProgressBarThread class is basically the timer that keeps the monitor open?
Is there a way to increase the speed of the ProgressMonitorDialog's indicator, also can you remove the cancel button?
This is what I am thinking is happening currently.
I am starting the progress bar just before I start the PDF Operation Listener
See startSavePdfOperation() Above
The progress bar is running as unknown, but using a for loop to keep the progress bar dialog open, while the operation is running on a thread in the background.
See Class ProgressBarThread above
When the PDF operation completes the listener operation class closes the base GUI dialog.
public void endOperation() {
try {
endOperationImpl();
}
finally {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
w.getShell().setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_ARROW));
w.recursiveSetEnabled(getShell(), true);
w.getShell().setEnabled(!getShell().getEnabled());
w.close();
}
});
}
}
I am not sure what is happening to the ProgressBarThread monitor?
Is this Possible?
When the PDF Operation starts, the ProgressMonitorDialog opens and starts the indicator. OK with keeping it unknown.
When the PDF Operation completes, the monitor closes, then the base Dialog
I am just wanting to open progress bar dialog that will inform the user that their request is working in the background.
As stated the above code works, but I am afraid by letting the closing of Base GUI, destroy my Progress Thread and Monitor is not good practice.
First of all, in your ProgressBarThread#run() you should use monitor.worked(1). You don't need to set the total worked but increment it by the amount of work done, since the last time it was called.
Q1. Yes it needs to be executed in the display thread
Q2. Normally the work that needs to be done is actually performed in the runnable that is passed to the progress monitor dialog so that you can accurately report the amount of progress made. So your operation (if it is a synchronous call) should be called from within ProgressBarThread#run() so that you call monitor.worked(1) only when one file processing is complete.
Q3. What kind of operation are you running, perhaps it already supports showing progress bar, and you just need to invoke the right API. Is it an IUndoableOperation?
Q4. As I said this approach is problematic because you can never accurately report the progress and close the dialog only when the operation is completed. But if this is the only choice you have, then you can just save the monitor reference somewhere so that it is accessible to the other thread. Once monitor.done() is called, your ProgressBarThread#run() should return, the dialog will close.
Q5. You can remove the cancel button by passing the correct parameter to ProgressMonitorDialog#run(..):
new ProgressMonitorDialog(getShell()).run(true, false, new ProgressBarThread());
For the rest of the questions I can better answer if I know what kind of operation (what API) you are using.
I am using tagcloud_0.4.jar for using Tag Cloud in GWT. Now, when I click on any tag inside the tag cloud, then how can I handle that event or how can I do RPC call when user select any tag from cloud?
Thanks.
Here is an example to catch a ClickEvent on the cloud. But this is a workaround as the library may change. As the developper do not offer to manage events on the cloud neither on tags, this is perhaps for a good reason...
So use this code at your own risk. Pray for no change of the widget's structure DOM. I suggest you to ask the developper some enhancements on its widgets, like managing such events.
final TagCloud cloud = new TagCloud();
cloud.addWord(new WordTag("AAA"));
cloud.addWord(new WordTag("AAA"));
cloud.addWord(new WordTag("BBB"));
cloud.addWord(new WordTag("CCC"));
cloud.addWord(new WordTag("CCC"));
cloud.addWord(new WordTag("CCC"));
cloud.addDomHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// Prevent the click from the tag to be executed
event.preventDefault();
Element e;
// For each tag present in the cloud, look for the one that is triggered
for (int i = 0; i < cloud.getTags().size(); ++i) {
// Gets the element in the cloud, this really is the unsafe part of the code
// if the developer change the dom of its widget
e = DOM.getChild(DOM.getChild(cloud.getElement(), 0), i);
// Is the current element targeted by the event?
if (event.getClientX() >= e.getOffsetLeft() && event.getClientY() >= e.getOffsetTop()
&& event.getClientX() <= e.getOffsetLeft() + e.getOffsetWidth()
&& event.getClientY() <= e.getOffsetTop() + e.getOffsetHeight()) {
// Gets the abstract tag
Tag t = cloud.getTags().get(i);
// Gets tag descendant
if (t instanceof WordTag) {
// Do what you want with your WordTag, maybe an RPC call
} else if (t instanceof ImageTag) {
// Do what you want with your ImageTag, maybe an RPC call
}
break;
}
}
}
}, ClickEvent.getType());