adi.neag
adi.neag

Reputation: 643

Close SWT shell by clicking outside of it

I have this JFace dialog:

setShellStyle(SWT.APPLICATION_MODAL | SWT.CLOSE);
setBlockOnOpen(false);

Is there a way to make it close by clicking somewhere outside the dialog? Maybe something like listening for a click event on the whole screen and detecting if it's outside the dialog, and then closing.

Upvotes: 7

Views: 1471

Answers (2)

Rüdiger Herrmann
Rüdiger Herrmann

Reputation: 20985

You can attach an SWT.Deactivate listener to the underlying Shell of the dialog.

To attach the listener, you could override Window::configureShell like this:

@Override
protected void configureShell(Shell shell) {
  super.configureShell(shell);
  shell.addListener(SWT.Deactivate, event -> shell.close());
}

And here a standalone SWT example to illustrate the bare mechanism:

Display display = new Display();
Shell parentShell = new Shell(display);
parentShell.setSize(500, 500);
parentShell.open();
Shell shell = new Shell(parentShell);
shell.addListener(SWT.Deactivate, event -> shell.close());
shell.setSize(300, 300);
shell.setText("Closes on Deactivate");
shell.open();
while (!parentShell.isDisposed()) {
  if (!display.readAndDispatch())
    display.sleep();
}
display.dispose();

Upvotes: 4

avojak
avojak

Reputation: 2360

With the Dialog being modal I believe that causes some challenges using the Shell of the base application to listen for MouseEvents because the Dialog intercepts them.

If you're not opposed to using an additional library you could consider using JNativeHook to listen for global mouse click events. This would allow you to listen for a click anywhere on the computer and close the dialog if the click occurred outside the dialog bounds, if that's what you're looking for.

For example:

GlobalScreen.addNativeMouseListener(new NativeMouseInputAdapter() {
    public void nativeMouseClicked(final NativeMouseEvent nativeMouseEvent) {
        display.syncExec(new Runnable() {
            public void run() {
                if (dialog.getShell() == null || dialog.getShell().isDisposed()) {
                    return;
                }
                // Close the dialog if there is a mouse click outside the bounds of the dialog
                if (!dialog.getShell().getBounds().contains(awtToSwtPoint(nativeMouseEvent.getPoint()))) {
                    dialog.close();
                }
            }
        });
    }
});

Other than that, I'm not aware of a way to listen to mouse clicks that are outside of the base application / anywhere on the screen.


Full example:

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.mouse.NativeMouseEvent;
import org.jnativehook.mouse.NativeMouseInputAdapter;

public class DialogCloseTest {

    private final Display display;
    private final Shell shell;

    public DialogCloseTest() {
        display = new Display();
        shell = new Shell(display);
        shell.setSize(450, 450);

        final Dialog dialog = new MyDialog(shell);
        dialog.open();

        registerNativeHook();

        GlobalScreen.addNativeMouseListener(new NativeMouseInputAdapter() {
            public void nativeMouseClicked(final NativeMouseEvent nativeMouseEvent) {
                display.syncExec(new Runnable() {
                    public void run() {
                        if (dialog.getShell() == null || dialog.getShell().isDisposed()) {
                            return;
                        }
                        // Close the dialog if there is a mouse click outside the bounds of the dialog
                        if (!dialog.getShell().getBounds().contains(awtToSwtPoint(nativeMouseEvent.getPoint()))) {
                            dialog.close();
                        }
                    }
                });
            }
        });
    }

    private org.eclipse.swt.graphics.Point awtToSwtPoint(final java.awt.Point point) {
        return new org.eclipse.swt.graphics.Point(point.x, point.y);
    }

    private static void registerNativeHook() {
        try {
            GlobalScreen.registerNativeHook();
        } catch (NativeHookException ex) {
            System.err.println("There was a problem registering the native hook.");
            System.err.println(ex.getMessage());
            System.exit(1);
        }
    }

    private static void unregisterNativeHook() {
        try {
            GlobalScreen.unregisterNativeHook();
        } catch (NativeHookException e) {
            System.err.println("There was a problem unregistering the native hook.");
            System.err.println(e.getMessage());
        }
    }

    private static class MyDialog extends Dialog {

        MyDialog(final Shell parent) {
            super(parent);
        }

        @Override
        protected void configureShell(final Shell shell) {
            super.configureShell(shell);
            setShellStyle(SWT.APPLICATION_MODAL | SWT.CLOSE);
            setBlockOnOpen(false);
        }    

    }

    public void run() {
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        display.dispose();
        unregisterNativeHook();
    }

    public static void main(String... args) {
        new DialogCloseTest().run();
    }

}

Note: This will close the Dialog even if it is not visible (eg. if you alt-tab away), so you could add some logic to check whether the dialog is visible as well, if you would like)

Upvotes: 0

Related Questions