View not updating synchronously - java

Before I go any further, here is my ViewPart class:
public class Exp extends ViewPart {
public static final String ID = "edu.bitsgoa.views.Exp"; //$NON-NLS-1$
public static Text text;
public Exp() {
}
#Override
public void createPartControl(final Composite parent) {
ScrolledComposite sc = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
Composite composite = new Composite(sc, SWT.NONE);
sc.setContent(composite);
composite.setLayout(new GridLayout(2, false));
text = new Text(composite, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI | SWT.READ_ONLY);
text.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
Font newFont=new Font(text.getDisplay(),new FontData("Monospace",10,SWT.NATIVE));
text.setFont(newFont);
GridDataFactory.fillDefaults().grab(true, true).hint(400, 400).applyTo(text);
sc.setExpandHorizontal(true);
sc.setExpandVertical(true);
sc.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
createActions();
initializeToolBar();
initializeMenu();
}
private void createActions() {
// Create the actions
}
private void initializeToolBar() {
IToolBarManager toolbarManager = getViewSite().getActionBars()
.getToolBarManager();
}
private void initializeMenu() {
IMenuManager menuManager = getViewSite().getActionBars()
.getMenuManager();
}
public void setFocus() {
// Set the focus
}
}
I have create this class to act as a console, so that I can print data on it from my custom plugin. This is how I display data on it (this may be a very naive approach, but works to some extent):
public static void display(final String message){
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
Exp.text.append(message);
Exp.text.append("\n");
}
});
}
This method is in some different class. Whenever I have to print something on the console, I called this method as:
Preparation.display("Calculating parameters...);
calculateParameters();
Preparation.display("Done");
Now, the problem is that in the above three lines, the data is not printed on the console as:
Calculating parameters...
(wait for some time, calculate parameters)
Done
Instead, the parameters are calculated first, and then everything is printed at once, as:
(wait for some time, calculate parameters)
Calculating parameters...
Done
I want it to be done synchronously, as I call the display method. I am very new to SWT and Java graphics in general. Any idea where I might be going wrong?
Thanks!

A long running task in the UI thread will lock up the UI and nothing will update until it finishes. You must use a background thread.
Since you are blocking the SWT readAndDispatch loop even operations you have done before to long running code starts may not happen until the code ends since they rely on the dispatch loop.
You can just use a normal Thread for the background code or you can use an Eclipse Job. Jobs allow you to update a 'progress monitor' which reports the work's progress.
In the background thread you can use the asyncExec or syncExec methods of Display to run code in the UI thread.
For more details see Eclipse Jobs and Background Processing

Related

How to know if a Thread has finished its task in SWT environment?

I'm in trouble in getting result from some threads.
I explain the environment, I have a SWT shell with a button. The listener for this button calls a Runnable that inside its run() calls a method that instantiate N threads for performing some operations. The problem is: how can I display a message dialog or something on the screen, when all the computation is terminated? The code I have is something similar
public void widgetSelected(SelectionEvent event) {
Runnable t = new MyThread(params);
executor.execute(t);
}
And inside MyThread class I have
public void run(){
myMethod();
}
public void myMethod(){
for(int i =0; i<queue.length; i++){
Runnable thread = new AnotherThread();
executor.execute(thread);
}
executor.shutdown();
if(executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS){
//here I know all thread of kind AnotherThread have finished
}
}
So inside the widgetSelected method of the button listener, I want to put something that alerts the user that all threads called by the listener have successfully terminated.
Is there a way to know that? If I put the awaitTermination statement inside the Listener, the shell becomes not responsive and freezes on the screen.
Hope someone could help me. If something was not clear, please tell me.
Thanks all.
This is "straight forward" - but a bit of work: you "simply" have to enhance your Runnables to somehow signal their progress.
In other words: the ExecutorService interface doesn't offer any means to figure how many of scheduled "tasks" completed. So - if you need that information, you have to "bake" it into your Runnable.
One idea: create a Map<Runnable, Integer> upfront. The keys are your Runnable objects, and the value could represent some progress information. You start with all values at 0. And then you pass that map to each Runnable - and the Runnable simply updates its value at certain, defined points in time. Maybe ten steps, or maybe just 4. And as soon as all map values are at say 100, you know that you are done! That implies that your main thread simply loops and checks the map content every other second/minute/... and of course: this extra thread should not be the event dispatcher thread. You don't want to stall your UI while doing this.
( of course: you should use a ConcurrentHashMap to implement this ).
Long story short: this information is available already - the code in your Runnable knows what it is doing, right?! So you "only" have to make this information accessible to the outer world somehow. There are many options to do that; the above is just one way to get there.
I would recommend taking a look at using FutureCallbacks which are part of the Guava library.
What this will allow you to do is create a ListenableFuture for each task that you fire off. In your case, it sounds like this would be represented by a Runnable, but you can just as easily use a Callable. When these tasks are all fired off, you will end up with a list of these ListenableFuture objects, which can be "flattened" into a single ListenableFuture which represents the completion of ALL of the tasks. This is accomplished with the method Futures.allAsList(...):
final List<ListenableFuture<T>> futures = ...
final ListenableFuture<List<T>> combinedFuture = Future.allAsList(futures);
Now that you have a single ListenableFuture which represents the completion of all of your tasks, you can easily listen for its completion by adding a FutureCallback to be invoked upon completion:
Futures.addCallback(future, new FutureCallback<List<String>>() {
#Override
public void onFailure(final Throwable arg0) {
// ...
}
#Override
public void onSuccess(final List<String> arg0) {
// ...
}
}
Now, once these tasks are completed, we need to update the UI to notify users. To do so, we must be sure that the UI updates happen back on the SWT UI thread:
Display.getCurrent().asyncExec(new Runnable() {
#Override
public void run() {
// Update the UI
}
});
Note that this can easily be done within the onSuccess() method above so that the result of the tasks can be used.
Putting it all together, we can easily loop through a handful of ListeningExecutorService.submit(...) calls for background execution (so as not to block the UI thread - In my example below, you can freely type in the text box while the tasks are running in the background), grab all the ListenableFutures, and add a callback to be invoked upon completion, which will hop back to the UI thread to make the UI updates.
Full example:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
public class CallbackExample {
private final Display display;
private final Shell shell;
private final Text output;
private final ListeningExecutorService executor;
public CallbackExample() {
display = new Display();
shell = new Shell(display);
shell.setLayout(new FillLayout());
executor = MoreExecutors.listeningDecorator(Executors
.newFixedThreadPool(20));
final Composite baseComposite = new Composite(shell, SWT.NONE);
baseComposite
.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
baseComposite.setLayout(new GridLayout());
output = new Text(baseComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Button button = new Button(baseComposite, SWT.PUSH);
button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
button.setText("Start tasks");
button.addSelectionListener(new SelectionAdapter() {
#SuppressWarnings("synthetic-access")
#Override
public void widgetSelected(final SelectionEvent e) {
// Start tasks when the button is clicked
startTasks();
}
});
}
private void startTasks() {
// Create a List to hold the ListenableFutures for the tasks
final List<ListenableFuture<String>> futures = new ArrayList<ListenableFuture<String>>();
// Submit all the tasks for execution (in this case 100)
for (int i = 0; i < 100; i++) {
final ListenableFuture<String> future = executor
.submit(new Callable<String>() {
#Override
public String call() throws Exception {
// Do the work! Here we sleep to simulate a long task
Thread.sleep(2000);
final long currentMillis = System
.currentTimeMillis();
System.out.println("Task complete at "
+ currentMillis);
return "Task complete at " + currentMillis;
}
});
// Add the future for this task to the list
futures.add(future);
}
// Combine all of the futures into a single one that we can wait on
final ListenableFuture<List<String>> future = Futures
.allAsList(futures);
// Add the callback for execution upon completion of ALL tasks
Futures.addCallback(future, new FutureCallback<List<String>>() {
#Override
public void onFailure(final Throwable arg0) {
System.out.println("> FAILURE");
}
#SuppressWarnings("synthetic-access")
#Override
public void onSuccess(final List<String> arg0) {
System.out.println("> SUCCESS");
// Update the UI on the SWT UI thread
display.asyncExec(new Runnable() {
#Override
public void run() {
final StringBuilder sb = new StringBuilder();
for (final String s : arg0) {
sb.append(s + "\n");
}
final String resultString = sb.toString();
output.setText(resultString);
}
});
}
});
}
public void run() {
shell.setSize(200, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
executor.shutdownNow();
display.dispose();
}
public static void main(final String... args) {
new CallbackExample().run();
}
}

Eclipse Plugin: Opening dialogs alongside quickfix

I'm working on a plugin and am trying to add some additional information around the quickfix menu that is triggered by clicking on my custom markers.
I'm adding a method call in MarkerResolutionGenerator.getResolutions() to draw the new dialog window, but I'm having trouble getting it to agree with the quickfix dialog. I can get it to draw at the same time, but I can't control the position and it also draws an extra blank dialog in the background.
Any thoughts? Relevant code below. The first two methods are from my MarkerResolutionGenerator, and my custom class is below that. (I just copied it from an example, I'm more worried about getting it to behave before I work on the content.)
#Override
public IMarkerResolution[] getResolutions(IMarker marker)
{
IMarker problem = marker;
makeStuff();
...
}
private void makeStuff()
{
Display display = Activator.getDefault().getWorkbench().getDisplay();
Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor()
.getSite().getWorkbenchWindow().getShell();
Shell myShell = new Shell(shell, SWT.NO_TRIM);
MyDialog md = new MyDialog(myShell);
md.open();
}
public class MyDialog extends Dialog
{
public MyDialog(Shell parentShell)
{
super(parentShell);
setShellStyle(SWT.CLOSE | SWT.MODELESS | SWT.BORDER | SWT.TITLE);
setBlockOnOpen(false);
}
#Override
protected Control createDialogArea(Composite parent)
{
Composite container = (Composite) super.createDialogArea(parent);
Button button = new Button(container, SWT.PUSH);
button.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
button.setText("Press me");
button.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
System.out.println("Pressed");
}
});
return container;
}
...
}
Do you really need to create another Shell? Why not just use the one you already have as parent for your dialog? It might be the reason for the "extra blank dialog in the background".
You should be able to set the initial position overriding getInitialLocation in your custom dialog:
#Override
protected Point getInitialLocation(Point initialSize) {
return new Point(10, 10); // x and y coordinates of the initial position
}

Tabbing from field to field doesn't work (aka "tab order" in Java SWT)

I have a Java SWT desktop application with various pages etc. pp. It works perfectly well, except the tab order - not at all. When I am in "field 1" and press Tab on my keyboard, I want the application to jump to "field 2", so I don't have to click to the second field after I entered the information in the first one.
Generally there is the "setTabList", where you can set the right order for "tabbing through the form". My problem is I don't even need to set the order right, tabbing doesn't work at all.
When I am in a field of my form and press Tab, for a millisecond I can see the Windows "working circle" (or whatever it is called) and nothing happens. The cursor stays exactly at the same position. It won't jump to another field and I have no idea why.
Generally my code:
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.BORDER);
setControl(container);
fieldOne = new StyledText(container, SWT.NONE);
fieldOne.setBounds(whatever);
fieldOne.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent arg0) {
txtChanged();
}
});
fieldTwo = new StyledText(container, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
fieldOne.setBounds(whatever);
fieldOne.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent arg0) {
txtChanged();
}
});
Control[] controls = { fieldOne, fieldTwo };
container.setTabList(controls);
}
Even if I leave the Control[] and container.setTabList rows out, nothing happens.
Any ideas?
In a StyledText control Tab normally just moves the cursor to the next tab stop inside the control. Ctrl+Tab moves to the next control.
You can override this with a traverse listener:
fieldOne.addTraverseListener(new TraverseListener() {
#Override
public void keyTraversed(final TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
e.doit = true;
}
}
});
It is also possible that your key listener is interfering with normal operation. Use a ModifyListener if all you want to know is that the text has changed.

SWT Popup Composite unexpected disposal

So I've stolen this cool PopupComposite, and I am really satisfied with it.
There's just one issue. If it put a org.eclipse.swt.widgets.Text in it, I open the popup, focus the Text, and press ESC, then both the Text and the PopupComposite dispose themselves.
I really can't figure out where the dispose call is coming from. Is it a Shell issue? What Shell should I use with the popup?
SSCCE:
/**
*
* #author ggrec
*
*/
public class PopupCompositeTester
{
public static void main(final String[] args)
{
new PopupCompositeTester();
}
private PopupCompositeTester()
{
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
createContents(shell);
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if ( !display.readAndDispatch() )
display.sleep();
}
display.dispose();
}
private static void createContents(final Composite parent)
{
final Button button = new Button(parent, SWT.PUSH);
button.setText("Poke Me");
final PopupComposite popup = new PopupComposite(parent.getShell());
new Text(popup, SWT.NONE);
popup.pack();
button.addSelectionListener(new SelectionAdapter()
{
#Override public void widgetSelected(final SelectionEvent e)
{
popup.show( Display.getDefault().map(parent, null, button.getLocation()) );
}
});
}
}
The reason for this is because when you focus the text field and press Escape, the field sends a SWT.TRAVERSE_ESCAPE event to its parent shell. The shell (in your case not being a top-level shell) responds by calling Shell.close(). You can work around that by adding a traverse listener to your text field, which would cancel the event (code below).
new Text(popup, SWT.NONE).addTraverseListener(new TraverseListener() {
#Override
public void keyTraversed(TraverseEvent e) {
if(e.detail == SWT.TRAVERSE_ESCAPE) {
e.doit = false;
}
}
});
Keep in mind, this is a rather crude solution to your specific issue. I would not recommend using this for anything other than testing purposes. You can read more about this here -> http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fswt%2Fevents%2FTraverseEvent.html
And here: http://help.eclipse.org/helios/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fswt%2Fwidgets%2FShell.html
Because my "bug" is actually a normal behaviour of the SWT platform, I've used the following workaround:
/**
* Lazy initialization of the popup composite
*/
private void createPopup()
{
// popupContainer is now a field
if (popupContainer != null && !popupContainer.isDisposed())
return;
// ... create popup AND its contents ...
}
and in the button listener:
createPopup();
popup.show( Display.getDefault().map(parent, null, button.getLocation()) );
Thank you #blgt

How to update/refresh a view in an eclipse plug-in?

I have an eclipse plug-in with a single view (like the eclipse helloworld-view-plugin-project). In the view-file I get an event when I want to update the view.
In this view I have a GridData in a Group with multiple labels. I have several services which register to the programe and whose status should be shown in this GridData.
Edit: In order to better show my problem I updated this post and added the whole code:
CreatePartControl():
public void createPartControl(Composite _parent) {
parent = _parent;
createContents();
addBindings();
makeActions();
contributeToActionBars();
}
CreateContents():
protected void createContents() {
// fixed
checkIcon = //...
errorIcon = //...
smallFont = SWTResourceManager.getFont("Segoe UI", 7, SWT.NORMAL);
// content
gl_shell = new GridLayout(1, false);
//margins, etc. for gl_shell
parent.setLayout(gl_shell);
final Label lblGreeting = new Label(parent, SWT.NONE);
lblGreeting.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 1, 1));
lblGreeting.setText("Hi " + Preferences.getPreName());
// -- GROUP YOUR STATS (show all services)
createStatusGroupBox();
}
createStatusGroupBox():
private Group grpYourStatus = null; // outside the method for access in listener (below)
private void createStatusGroupBox() {
grpYourStatus = new Group(parent, SWT.NONE);
grpYourStatus.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1));
grpYourStatus.setText("Your upload status");
grpYourStatus.setLayout(new GridLayout(3, false));
// add message if no service is registered
if ( r.getServiceList().size() == 0 ) {
Label status = new Label(grpYourStatus, SWT.NONE);
status.setText("No service registered.");
new Label(grpYourStatus, SWT.NONE); //empty
new Label(grpYourStatus, SWT.NONE); //empty
}
// add labels (status, message, name) for each registered service
for ( IRecorderObject service : r.getServiceList() ) {
Label name = new Label(grpYourStatus, SWT.NONE);
Label status = new Label(grpYourStatus, SWT.NONE);
Label message = new Label(grpYourStatus, SWT.NONE);
message.setFont(smallFont);
message.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
service.getServiceViewItem().setLabelsAndIcons(name, status, message, checkIcon, errorIcon); //this also sets the values of the labels (label.setText(...) via data binding)
}
Unfortunately, I don't know what the right way is to update/reset it. I tried the following:
listener (which should update the view / the services-list):
r.addPropertyChangeListener(BindingNames.SERVICE_ADDED, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// This "redraws" the view, but just places the whole content (generated in createStatusGroupBox()) above the other parts.
//Display.getCurrent().update();
//createStatusGroupBox();
//parent.layout(true);
//parent.redraw();
// disposes grpYourStatus-Group but doesn't show anything then
grpYourStatus.dispose();
createStatusGroupBox();
grpYourStatus.layout();
grpYourStatus.redraw();
}
});
}
});
I also tried the following statements (individually); all without success:
parent.redraw();
parent.update();
parent.layout();
parent.layout(true);
parent.refresh();
In this post I read the following:
createPartControls is that time of the life cycle of a view, where its
contained widgets are created (when the view becomes visible
initially). This code is only executed once during the view life
cycle, therefore you cannot add anything here directly to refresh your
view.
Eclipse parts typically update their content as a reaction to a
changed selection inside of the workbench (e.g. the user might click
on another stack frame in the debug view).
Unfortunately, I don't know what to else I could try and I didn't find anything helpful with searches... thank's for your help and suggestions!
I finally found the answer (together with AndreiC's help!):
my listener now looks like this:
r.addPropertyChangeListener(BindingNames.SERVICE_ADDED, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// remove grpYourStatus from parent
grpYourStatus.dispose();
// add grpYourStatus (with updated values) to parent
createStatusGroupBox();
// refresh view
parent.pack();
parent.layout(true);
}
});
}
});
The rest is the same like the code above.
I do not know the API by heart, but have you tried parent.update()?

Categories