简体   繁体   中英

True application modal SWT shell from AWT JFrame

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 .

  1. 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

  2. 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) .

  3. I used on WindowListener on the frame and a MouseListener on the GlassPane

  4. 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.

  5. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM