I have a Java Swing application and I'm embedding an SWT widget to it. I'm trying to display an SWT Shell
from my AWT JFrame
but cannot make it application modal. The JFrame
can still be focused and button clicks will register to the EDT. What steps do I take to make the Shell
behave like a modal AWT dialog?
I have read this outdated tutorial but it only explains how to do it if the application is running on the SWT event thread. I also tried to hack this with a modal JDialog
but the behavior is ugly at best. Remove the comments from the minimum working example to demo it. SWT.ON_TOP
is troublesome in itself, since ithe shell
will stay on top of every window.
This question did not help.
SSCE
public class ModalDialogExample extends JFrame {
public ModalDialogExample()
{
this.setSize(500, 500);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
public static void main(String[] args)
{
JFrame frame = new ModalDialogExample();
JButton button = new JButton("CLick me");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// JDialog modalDialog = new JDialog();
// modalDialog.setSize(new Dimension(0,0));
// modalDialog.setModalityType(ModalityType.APPLICATION_MODAL);
Display display = new Display();
Shell shell = new Shell(display, SWT.CLOSE | SWT.TITLE | SWT.BORDER | SWT.OK | SWT.ON_TOP | SWT.APPLICATION_MODAL);
shell.setSize(200,200);
shell.open();
shell.forceActive();
// modalDialog.setVisible(true);
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
});
frame.getContentPane().add(button);
frame.setVisible(true);
}
}
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();
}
});
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.