8

I have a Undecorated Modal JDialog which I want to setVisible(false) when the user clicks outside of the modal dialog.

Is this possible in Swing?

What I am doing is popping up a custom editor for a text field like a date selector. Is there an easier way to do what I want?

EDIT

Remember that modal blocks on the call to setVisible(true), so you can't just say "don't use a modal dialog"

And I've tried focus listeners on the dialog, they don't trigger when its modal.

Pyrolistical
  • 27,624
  • 21
  • 81
  • 106
  • Did you try the addAWTEventListener method, this should give you events for all specified event types, e.g. in the example I gave below this would be all mouse events. – vickirk Nov 04 '09 at 22:33
  • I know you said "so you can't just say "don't use a modal dialog"", presumably this is because you have code that executes straight after the setVisible call? Could you not move this into maybe a listener for when the dialog is closed? Without knowing details of your app it may provide a cleaner design, especially when it comes to unit testing, I like to move dialogs out into a strategy for getting user responses, that way I can inject mock strategies without hanging a unit test when it runs headless or without having to mess around with creating events programatically. – vickirk Nov 04 '09 at 22:34

7 Answers7

12

EDIT: Changed to use WindowFocusListener instead of FocusListener, as well as check for descending components on the focus lost in order to not hide if a child component gains focus.

A simple way would be to add a window focus listener on the dialog that hides it when focus is lost. I don't see the need for modality in this case. For example:

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class ClickAwayDialog extends JDialog {

    public ClickAwayDialog(final Frame owner) {
        super(owner);
        JPanel pnl = new JPanel(new BorderLayout());
        pnl.add(new JLabel("Click outside this dialog in the parent frame to close it"), BorderLayout.NORTH);
        JButton btn = new JButton("Click Me");
        btn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(ClickAwayDialog.this, "New Child Window");
            }
        });
        pnl.add(btn, BorderLayout.CENTER);
        this.setContentPane(pnl);
        this.pack();
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.setLocationRelativeTo(owner);
        this.setAlwaysOnTop(true);
        this.addWindowFocusListener(new WindowFocusListener() {

            public void windowGainedFocus(WindowEvent e) {
                //do nothing
            }

            public void windowLostFocus(WindowEvent e) {
                if (SwingUtilities.isDescendingFrom(e.getOppositeWindow(), ClickAwayDialog.this)) {
                    return;
                }
                ClickAwayDialog.this.setVisible(false);
            }

        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame parent = new JFrame();
                parent.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
                parent.setSize(300, 300);
                parent.setLocationByPlatform(true);
                parent.setVisible(true);
                ClickAwayDialog dlg = new ClickAwayDialog(parent);
                dlg.setVisible(true);                
            }
        });
    }
}
Chris B.
  • 2,532
  • 1
  • 15
  • 9
  • Focus lost would have to ensure the component that gained the focus is not a child component of the dialog so you will need to search up the component hierarchy via `getParent()` – vickirk Nov 04 '09 at 20:13
  • @vickirk -- good point. I changed the example above to check for descendant components (arguably an edge case, but I enhanced the example to allow creating a child window of the dialog). I also changed it to use a window focus listener. – Chris B. Nov 04 '09 at 22:07
  • Yes I totally understand how one can do this without a modal dialog, but the point of the question was if it was possible to do it with a modal dialog. The main advantage of using a modal dialog is it blocks when setVisible(true). My goal was to not need to restructure the program to use a non-modal dialog or write a utility to emulate the blocking nature in a non-modal dialog. – Pyrolistical Nov 13 '11 at 07:13
6

It's not a modal dialog if you can click outside of it and "something" happens. All the answers are correct, you should be creating a non-modal dialog and then deal with your use case via a FocusListener.

taftster
  • 243
  • 2
  • 4
  • This is the closest answer to "No". That's all I was asking. Not how I can achieve this without a non-modal dialog. – Pyrolistical Nov 13 '11 at 07:15
2

It's not necessary to be a modal dialog (modal means that it prevents you from using the owner window until you hide the dialog). Better try this:

final JDialog dlg ...
dlg.setModal(false);

dlg.addWindowFocusListener(new WindowFocusListener() {            
    public void windowLostFocus(WindowEvent e) {
        dlg.setVisible(false);
    }            
    public void windowGainedFocus(WindowEvent e) {
    }
});
Gill
  • 21
  • 1
2

Try to set the modal to false, and then use windowsDeactivated() for close de dialog (dialog.dispose()), works for me.

1

Use a WindowListener and handle the windowDeactivated() event.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • I just tested this and for me this approach only seemed to work when clicking completely outside of the java application, but not when trying to click on the java main frame that spawned the modal dialog. – Reto Höhener May 10 '15 at 20:17
  • @Zalumon, I guess this answer was not very clear. This was a suggestion to NOT use a modal JDialog. When you use a modal JDialog as a popup, then you can't close the dialog if you click outside the dialog. This is a solution to allow you to close a `non modal dialog` when you click outside the dialog area, so the "popup dialog" gets closed. – camickr May 10 '15 at 23:10
0

Not really a modal dialog then if clicking else where closes it, maybe you want setAlwaysOnTop

However, something like the following should do the trick (untested). Note, I would recommend moving the code into something better designed than use as provided.

static JDialog dialog = ...

Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
    public void eventDispatched(AWTEvent e) {
        dialog.setVisible(false);

        SwingUtils.invokeLater(new Runnable(){
            public void run(){
                Toolkit.getDefaultToolkit().removeAWTEventListener(this);
            }
        });        
    }
}, AWTEvent.MOUSE_EVENT_MASK);

dialog.setVisible(true);
vickirk
  • 3,979
  • 2
  • 22
  • 37
  • I just tested this and it looks like this kind of listener also does not get any mouse events from outside the modal dialog. – Reto Höhener May 10 '15 at 20:10
0

Probably add a FocusListener and hide the dialog when it looses the focus. May be tricky if some elements in the dialog can have focus. Anyways, experiment with it.

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68
  • Can a modal dialog (and child components) loose focus for anything other than switching to another application? – vickirk Nov 04 '09 at 19:47
  • Ah, got what you mean, you meant instead of a modal dialog! Don't know why this was voted down – vickirk Nov 04 '09 at 20:08