Here is what I came up with. It is not perfect but does the job. Recall I had a JFrame
on top of which I needed a modal SWT Shell
behaving like a modal JDialog
.
The Shell
that is started can not live in the AWT event dispatch thread. We need access to the thread to intercept mouse and window events later. So start the Shell
in a new Thread
I placed a GlassPane
on my JFrame
for the time the Shell
was open. The purpose was to block other interactive Swing components in the frame and it's containers and to intercept all mouse events. I needed the mouse events, so I could not simply do Frame.setEnabled(false)
.
I used on WindowListener
on the frame and a MouseListener
on the GlassPane
I used a DisposeListener
on the Shell
to detect the moment when the JFrame
has to lose the GlassPane
and other modifications done for the time the Shell
is live.
To control the visibility of the Shell
with my Swing listeners I needed access to the SWT event thread. This is done by executing Runnable
with shell.getDisplay().syncExec(...)
This is how I launch the Shell
, activate the GlassPane
, attach "modality listeners" and also remove them when the Shell
is closed. The Shell
is started in a new thread.
new Thread(new Runnable() {
@Override
public void run() {
final Shell shell = myWidget.getShell();
final EditorWindowListener ewl = new EditorWindowListener(shell);
myFrame.addWindowListener(ewl);
final EditorClickListener ecl = new EditorClickListener(shell);
myFrame.getGlassPane().addMouseListener(ecl);
myFrame.getGlassPane().setVisible(true);
shell.addDisposeListener(new DisposeListener() {
//Remove the disabled status
@Override
public void widgetDisposed(DisposeEvent arg0) {
myFrame.removeWindowListener(ewl);
myFrame.getGlassPane().removeMouseListener(ecl);
myFrame.getGlassPane().setVisible(false);
}
});
//The method that starts the shell
myWidget.show();
}
}).start();
This is what happens in myWidget.show()
(standard SWT stuff, no modifications)
shell.open();
shell.forceActive();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
Here are the two listeners I add to the frame and glassPane. First the MouseListener
that detects clicks on the glass pane and if any event is caught, brings the Shell
to the top. I know this would not be needed in a perfectly modal dialog, and mostly it is not needed in this case either, but some dual monitor setups caused problems that were solved with this listener.
class EditorClickListener extends MouseAdapter
{
private Shell shell;
public EditorClickListener(Shell s)
{
this.shell = s;
}
@Override
public void mouseClicked(MouseEvent e) {
shellToFront(shell);
}
}
Then the WindowListener
attached to the frame. It makes sure anytime the frame is active, the shell jumps on top.
class EditorWindowListener extends WindowAdapter
{
Shell shell;
public EditorWindowListener(Shell s)
{
this.shell = s;
}
@Override
public void windowOpened(WindowEvent e) {
shellToFront(shell);
}
@Override
public void windowDeiconified(WindowEvent e) {
shellToFront(shell);
}
@Override
public void windowActivated(WindowEvent e) {
//Set the shell on top of the frame
//Fixes some problems with dual monitor setups.
final java.awt.Point framePoint = myFrame.getLocation();
shellToFront(shell);
shell.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
shell.setMinimized(false);
shell.setActive();
org.eclipse.swt.graphics.Point shellPoint = aikataulu.getLocation();
shellPoint.x = (int) framePoint.getX();
shellPoint.y = (int) framePoint.getY();
shell.setLocation(shellPoint);
}
});
}
}
And finally the method to pop the shell to your face to create a feeling of modality. Note that all events need to assigned to happen in the SWT event thread. I had some trouble with setting the minimized state to false
. So I had to add an artificial minimization in case the Shell
was not minimized and was indeed behind other windows. This results in a stupid unnecessary animation in some use cases, but for now it will make do
private void shellToFront(final Shell shell)
{
shell.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!shell.getMinimized())
{
shell.setMinimized(true);
}
shell.setMinimized(false);
shell.setActive();
}
});
}