Question:
Can you show any simple example or explanation of SWT display part(which updates the window/shell)? Or can you any sites that you think they are the best for SWT application development?
Background:
I am new to SWT application and currently building an application for running some tests.
It has a main display shell class with text area which keeps getting updated after user clicked on a run button.
The run button starts another thread process which updates public static object such as AtomicCounter in the StartView class.
Current Stage
The program seems running well, however, it does not update the text area in realtime.
Well, I can't say realtime but it shows a little bit delayed information.(I can say it's delayed because I print out on the console as well)
It seems like I don't understand displaying concept of SWT well enough to do whatever I am trying to do with it.
Goal
A. Main Display class which starts and stops C regardless of B running or not
B. Threaded process which updates text area of A class with public static object of A
C. Threaded process which does its job and updating public static object of A
Example Code (Working Code)
public class UnitTest {
public static Display display;
private Shell shell;
public static AtomicInteger counter = new AtomicInteger(0);
public static Text text;
private TestThread test1 = null, test2 = null;
public UnitTest()
{
display = Display.getDefault();
this.shell = new Shell(display, SWT.CLOSE);
this.shell.setSize(226, 120);
text = new Text(shell, SWT.BORDER);
text.setBounds(10, 10, 199, 19);
Button btnStart = new Button(shell, SWT.NONE);
btnStart.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent arg0) {
test1 = new TestThread();
test1.start();
test2 = new TestThread();
test2.start();
}
});
btnStart.setBounds(10, 54, 94, 28);
btnStart.setText("Start");
Button btnStop = new Button(shell, SWT.NONE);
btnStop.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent arg0) {
test1.interrupt();
test2.interrupt();
counter.set(0);
}
});
btnStop.setBounds(115, 54, 94, 28);
btnStop.setText("Stop");
this.shell.open();
this.shell.layout();
this.shell.addListener(SWT.Close, new Listener(){
public void handleEvent(Event event)
{
shell.dispose();
}
});
while(!this.shell.isDisposed())
{
if(!display.readAndDispatch())
{
//text.setText(""+counter.get());
display.sleep();
}
}
}
public static void main(String[] args)
{
new UnitTest();
}
}
class TestThread extends Thread
{
public void run()
{
try
{
int i = 0;
while(i++ < 1000 && !this.isInterrupted() )
{
UnitTest.counter.getAndIncrement();
try {
TestThread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
if(UnitTest.display.isDisposed())
return;
UnitTest.display.asyncExec(new Runnable() {
public void run() {
if (UnitTest.text.isDisposed())
return;
UnitTest.text.setText(""+UnitTest.counter.get());
}
});
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
System.out.println("Existing thread...");
}
}
}
You should carefully use UI updates from separate threads. Please, read this:
http://goo.gl/At8hC
Related
I have a JButton that will not allow me to perform the same action on any subsequent click on it after the first in the same Swing GUI instance.
JButton Run = new JButton("Run");
Run.setLocation(290, 70);
Run.setSize(120, 30);
buttonPanel.add(Run);
Run.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (Run.isEnabled()) {
errorLabel.setText("");
Result result = JUnitCore.runClasses(Run.class);
errorMessageDisplay(result);
}
}
});
totalGUI.setOpaque(true);
return totalGUI;
}
So far I thought about and tried removing the JPanel and painting all of the buttons back on, and disabling/renabling buttons.
The errorMessageDisplay method is as follows:
public void errorMessageDisplay(Result resultPass) {
if (resultPass.getFailureCount() > 0) {
errorLabel.setForeground(Color.red);
errorLabel.setVisible(true);
errorLabel.setText(" Failed");
}
else {
errorLabel.setForeground(Color.green);
errorLabel.setText(" Passed");
errorLabel.setVisible(true);
}
}
At first glance, the JUnitCore.runClasses(Run.class); call is suspicous. Also, it would be good to know what does the errorMessageDisplay() do. I believe, the problem is with one of these methods.
You can verify this with the following experimental code. Just be careful not to push it into production.
JButton run = new JButton("Run");
run.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (Run.isEnabled()) {
errorLabel.setText("");
System.out.println("Run action peformed.");
}
}
Update Since the errorMessageDisplay() looks okay, it's probably a Threading problem with JUniCore. Thus I'd try the following code:
final ExecutorService executor = Executors.newFixedThreadPool(5); // this runs stuff in background
JButton run = new JButton("Run");
// ..
run.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (Run.isEnabled()) {
executor.execute(new Runnable() { // This is how we run stuff in background. You can use lambdas instead of Runnables.
public void run() {
final Result result = JUnitCore.runClasses(Run.class); // Run.class is different from the current JButton run.
SwingUtilities.invokeLater(new Runnable() { // Now we go back to the GUI thread
public void run() {
errorMessageDisplay(result);
}
});
}
});
}
});
I am testing a simple SWT StackLayout example to learn how it works but things are not working as I expected.
I created a StackLayout with two buttons on them, both set to cycle the top control between the two of them five times when they are selected, with a 2-second pause every time the top control changes. However, when I run the problem I do not see anything happen.
Any ideas on what I am missing?
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
public class MyApp {
protected Shell shlMyFirstSwt;
Button btnOne;
Button btnTwo;
/**
* Launch the application.
* #param args
*/
public static void main(String[] args) {
try {
MyApp window = new MyApp();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open() {
Display display = Display.getDefault();
createContents();
shlMyFirstSwt.open();
shlMyFirstSwt.layout();
while (!shlMyFirstSwt.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Create contents of the window.
* #throws InterruptedException
*/
protected void createContents() {
shlMyFirstSwt = new Shell();
shlMyFirstSwt.setSize(621, 416);
shlMyFirstSwt.setText("My First SWT Application");
StackLayout layout = new StackLayout();
shlMyFirstSwt.setLayout(layout);
Button btnOne = new Button(shlMyFirstSwt, SWT.NONE);
btnOne.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
for (int i = 0; i != 10; i++) {
layout.topControl = i % 2 == 0? btnOne : btnTwo;
shlMyFirstSwt.layout();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
});
btnOne.setText("One");
Button btnTwo = new Button(shlMyFirstSwt, SWT.NONE);
btnTwo.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
for (int i = 0; i != 10; i++) {
layout.topControl = i % 2 == 0? btnOne : btnTwo;
shlMyFirstSwt.layout();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
});
btnTwo.setText("Two");
}
}
Elaborating after first answer:
Trying a simpler approach without delaying. Now I modified the event handler to simply have one button switch the top control to be the other button, as shown below. I expected the two buttons to alternate as top control, but instead when I click on the first button, the window turns blank. Any idea why?
Button btnOne = new Button(shlMyFirstSwt, SWT.NONE);
btnOne.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
layout.topControl = btnTwo;
shlMyFirstSwt.layout();
}
});
btnOne.setText("One");
Button btnTwo = new Button(shlMyFirstSwt, SWT.NONE);
btnTwo.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
layout.topControl = btnOne;
shlMyFirstSwt.layout();
}
});
btnTwo.setText("Two");
Your Thread.sleep calls are blocking the user interface thread so the GUI does not get updated. You must never block the user interface thread like this. Calls to methods like layout do not update instantly - they require that display.readAndDispatch runs to dispatch the various updates that are generated.
If you want to delay something use Display.timerExec:
Display.getDefault().timerExec(2000, new Runnable() {
#Override
public void run()
{
... code to be run after the delay
}
});
So you will have to rework your code to use this to do the timed updated.
Figured it out: just the silly mistake of including btnTwo in the first event handler before it was initialized, even though it is used after initialization.
I've seen this but it doesn't work for my code. This is my unique class:
public static void main(String[] args) {
try {
Main window = new Main();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
public void open() {
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
protected void createContents() {
shell = new Shell(SWT.CLOSE | SWT.MIN | SWT.TITLE | SWT.ON_TOP);
shell.setSize(301, 212);
shell.setText("MyShell");
// ...Other contents...
btn = new Button(shell, SWT.NONE);
btn.setBounds(114, 151, 76, 25);
btn.setText("BUTTON!");
btn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
doSomething();
}
});
}
The method doSomething() is a caller for another method, like this:
private void doSomething()
{
Thread th = new Thread() {
public void run() {
threadMethod();
}
};
th.start();
}
When I click my button, an "Invalid Thread Access" raises from Thread-0, and it points to the first instruction of threadMethod() (wich doesn't access to UI widgets). I've tried to surround my button listener with
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// ...
}
});
but it doesn't work either. I need the doSomething() method because it checks some code before creating the thread.
This is threadMethod()
private void threadMethod()
{
String[] listItems = list.getItems();
String fileName;
Path source, target;
File folder = new File(dir + File.separator);
if (!folder.exists()) {
folder.mkdir();
}
try
{
for(int i = 0; i < list.getItemCount(); i++)
{
// do something with non UI widgets
}
list.removeAll();
}
catch(IOException | InterruptedException e)
{
//print error
}
}
Why I've got Invalid thread access? Thank you!
List is an SWT widget and if you call the getItems() method on it outside of the UI Thread (in this case your main thread), you get an ERROR_THREAD_INVALID_ACCESS SWTException. This is defined in the List API: ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
The Thread that created the receiver, is the the thread that created the Display. If a Display does not exist, the first call to Display.getDefault() creates one. Therefore your main thread, which calls the open() method, is the UI thread. Your code will work if you wrap the contents of the threadMethod():
private void threadMethod() {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// threadMethod contents
}
});
}
It will then be executed in the UI thread.
When I have a operation running in the back ground, I am setting my cursor to busy until the process completes. Is there a way to also grey out and disable the current Display/Dialog/Shell until the process completes. I want to visually let the user know that something is working and they have to wait.
EDIT
plotButton.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event arg0) {
getShell().setEnabled(!getShell().getEnabled());
getShell().setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_WAIT));
recursiveSetEnabled(getShell(), getShell().getEnabled());
startPrinterListOperation(); <== This is method that runs operation
}
});
Method that runs a printer operation.
private void startPrinterListOperation() {
listOp = new AplotPrinterListOperation(appReg.getString("aplot.message.GETPRINTERLIST"), session);
listOp.addOperationListener(new MyOperationListener(this) {
public void endOperationImpl() {
try {
printers.clear();
printers.addAll((ArrayList<PrinterProfile>) listOp.getPrinters());
Display.getDefault().asyncExec(new Runnable() {
public void run() {
showAplotPlotterDialog(); <== When operation returns - opens selection dialog
}
});
}
finally {
listOp.removeOperationListener(this);
listOp = null;
}
}
});
session.queueOperation(listOp);
} // end startPrinterListOperation()
showAplotPlotterDialog() (Seperate Class) opens a dialog with network printers, then with a button push sends a job to the selected printer. When that operation finishes the Plotter Dialog closes - This is the end of that method - baseDialog is the MAIN GUI
finally {
plotOp.removeOperationListener(this);
plotOp = null;
Display.getDefault().asyncExec(new Runnable() {
public void run() {
baseDialog.removeAllTableRows();
baseDialog.plotRequestCompleted = true;
baseDialog.setResultsButtonVisibility();
getShell().close();
}
});
}
The following should do what you want. It will recursively disable and grey out all the Controls in your Shell. The Shell itself does not have a setGrayed method, but this will work:
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
Button button = new Button(shell, SWT.PUSH);
button.setText("Button");
button.addListener(SWT.Selection, new Listener() {
#Override
public void handleEvent(Event arg0) {
shell.setEnabled(!shell.getEnabled());
shell.setCursor(new Cursor(display, SWT.CURSOR_WAIT));
recursiveSetEnabled(shell, shell.getEnabled());
}
});
new Text(shell, SWT.NONE).setText("TEXT");
shell.setSize(400, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void recursiveSetEnabled(Control control, boolean enabled) {
if (control instanceof Composite)
{
Composite comp = (Composite) control;
for (Control c : comp.getChildren())
recursiveSetEnabled(c, enabled);
}
else
{
control.setEnabled(enabled);
}
}
Use
BusyIndicator.showWhile(Display.getDefault(), new Runnable()
{
public void run()
{
//operation
}
});
It sets the busy cursor on all Shells (Window, Dialog, ...) for the current Display until the Runnable.run() is executed.
Baz's answer was a great start for me, but doesn't act on Combo since it extends Composite. By making the call to setEnabled unconditional, every Control (including Combo) are enabled/disabled correctly.
private static void recursiveSetEnabled(Control control, boolean enabled) {
if (control instanceof Composite)
{
Composite comp = (Composite) control;
for (Control c : comp.getChildren())
recursiveSetEnabled(c, enabled);
}
control.setEnabled(enabled);
}
I am designing interface of java application using window builder. What I need to do is..
Click on a button does two things 1. do some background task
2. while this is in progress, display an indeterminate progressbar n new window.
I know I need multithreading to accomplish this.
I tried taking the help of some tutorials but could not implement this.
Can anyone help?
code:
Function from where I want to open the progress bar window
public void mouseDown(MouseEvent e) {
pbar p=new pbar();
p.caller();
dowork();
p.closeprogress();
}
Progressbar class
import org.eclipse.swt.widgets.Display;
public class pbar {
protected Shell shell;
public void pcaller() {
try {
//System.err.println("Error: " + bod);
//System.err.println("Error: " + lines);
pbar window = new pbar();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open() {
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Create contents of the window.
*/
protected void createContents() {
shell = new Shell();
shell.setSize(315, 131);
shell.setText("Updating!!! Please Wait");
ProgressBar progressBar = new ProgressBar(shell, SWT.INDETERMINATE);
progressBar.setBounds(47, 34, 195, 17);
// ProgressBar pb2 = new ProgressBar(shell, SWT.HORIZONTAL |
SWT.INDETERMINATE);
// pb2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
}
public void close()
{
shell.close();
}
}
I want when I call p.caller(), progress bar will appear. Then, control should come to original program and execute dowork() method. when i am done with this method, it will call p.progress.close().
I Don't know why isn't understanding this simple program, forget about answering.
Is use of JFace acceptable?
If so you can use ProgressMonitorDialog class.
ProgressMonitorDialog dialog = new ProgressMonitorDialog(parent.getShell());
dialog.run(true, true, new SomeTask());
...
class SomeTask implements IRunnableWithProgress {
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException,
InterruptedException {
monitor.beginTask("Doing some task", IProgressMonitor.UNKNOWN);
for(int i = 0; i < 1000; i++) {
if (!monitor.isCanceled())
Thread.sleep(10);
}
monitor.done();
}
}
You can find complete example of usage here.
Try using.....
JProgressBar p = new JProgressBar();
p.setStringPainted();
Now where the value needs to be set.
p.setValue(val);
To display a message when done.
p.setString("done");