Vito Vito - 1 month ago 15
Java Question

Mixing frames from SWT and Swing (JFrame)

I was given a large number of small JFrame based windows that originally worked as separate micro-applications. So each will have its own main method with Runnable that is added to the EventQueue.

I am supposed to merge it with SWT Main-Frame application so that user can call any of separate Swing-based micro-applications. So the final would be to have a single top Frame SWT that calls individual JFrame sub-apps. I know this is not the way GUI apps should be build properly, but this is the task I was given.


  1. Things work ok when SWT is a main item and it calls multiple Swing-based JFrames (you can try running SWTApp.Main). It seems that my task was not as difficult as I have initially thought.



2. I tried to play with this more and I did it the other way around. When you run SwingApp.main it allows you to run multiple SWT sub-apps. I was surprised to see that SWT does not like to be called after it has finished. It produced the following error:


org.eclipse.swt.SWTException: Invalid thread access at
org.eclipse.swt.SWT.error(SWT.java:4533) at
org.eclipse.swt.SWT.error(SWT.java:4448) at
org.eclipse.swt.SWT.error(SWT.java:4419) at
org.eclipse.swt.widgets.Widget.error(Widget.java:482) at
org.eclipse.swt.widgets.Shell.(Shell.java:286) at
org.eclipse.swt.widgets.Shell.(Shell.java:277) at
org.eclipse.swt.widgets.Shell.(Shell.java:226) at
org.eclipse.swt.widgets.Shell.(Shell.java:160) at
com.stackoverflow.SWTApp.createContents(SWTApp.java:46) at
com.stackoverflow.SWTApp.open(SWTApp.java:32) at
com.stackoverflow.SWTApp.main(SWTApp.java:21) at
com.stackoverflow.SwingApp$2$1.run(SwingApp.java:48) at
java.lang.Thread.run(Thread.java:745)


Even though this was not the goal of my final application - I wonder why is it so and how to workaround problem with SWT. It seems that SWT leaves some trace in some of its singletons so it knows that it has executed and cannot be rerun again. Any ideas how to tackle it?

package com.stackoverflow;

import java.awt.EventQueue;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class SwingApp extends javax.swing.JFrame {

private JPanel contentPane;

/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SwingApp frame = new SwingApp();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});

}

/**
* Create the frame.
*/
public SwingApp() {
setTitle("Swing");
setDefaultCloseOperation(SwingApp.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
//contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);

JButton startSWTApp = new JButton("Start SWT");
startSWTApp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
public void run() {
SWTApp.main(null);
}
}).start();;

}
});

startSWTApp.setBounds(34, 26, 97, 25);
contentPane.add(startSWTApp);

JButton btnClose = new JButton("Close");
btnClose.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SwingApp.this.dispose();
}
});
btnClose.setBounds(34, 124, 97, 25);
contentPane.add(btnClose);
}
}


And Swing-based individual JFrames (simplified here):

package com.stackoverflow;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;

public class SWTApp {

protected Shell shell;

/**
* Launch the application.
* @param args
*/
public static void main(String[] args) {
try {
SWTApp window = new SWTApp();
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(450, 300);
shell.setText("SWT Application");

Button btnCloseSwtApp = new Button(shell, SWT.NONE);
btnCloseSwtApp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
shell.dispose();
}
});
btnCloseSwtApp.setBounds(56, 78, 126, 30);
btnCloseSwtApp.setText("Close SWT App.");

Button btnSwingApp = new Button(shell, SWT.NONE);
btnSwingApp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
new Thread(new Runnable() {
public void run() {
SwingApp.main(null);
}}).start();

}
});
btnSwingApp.setText("Open Swing App");
btnSwingApp.setBounds(56, 156, 126, 30);

}
}

Answer

Calling Display.getDefault() when no display has already been created will establish the current thread as the SWT User Interface thread. This thread remains the same until the display is closed. Executing SWT UI code on any other thread will give you the 'Invalid thread access' error. So presumably your apps are running on different threads.

You might get the code to run by creating and closing the display each time:

Display display = new Display();

... create and main loop

display.close();

However this will probably not work on Macs as on macOS SWT requires the display to be created on the first app thread.

So basically running SWT code from Swing is a bad idea.