3

I followed approach 2 of this guide, so now I have a ModalInternalFrame that blocks input to all other frames, just as I wanted. However, I made one change from the example, and now I have two problems.

The Change

I removed the JOptionPane, because the whole point is to show my own pane. In order to make it close, I set closeable to true, and added an InternalFrameListener with the same code as the example's listener for the JOptionPane. That doesn't work, so I also added code at the end of doDefaultCloseAction.

The Problems

  1. The ModalInternal frame never goes away. I think some exception is being thrown but...
  2. I can't see any thrown exceptions, and I don't know where they're going. Usually when in debug mode, Eclipse will stop right before the exception is given to the UncaughtExceptionHandler, but that isn't happening in this case.

The Code

If my description of the problem doesn't help, here's my version of the ModalInternalFrame. If you want more code, I can post that as well. Sorry it's so long, but I tried to make it as concise as possible.

public class ModalInternalFrame extends JInternalFrame {
    public ModalInternalFrame(String title, JRootPane rootPane,
            Component desktop) {
        super(title, false, true, false, false);

        // create opaque glass pane
        final JPanel glass = new JPanel();
        glass.setOpaque(false);

        // Attach mouse listeners
        MouseInputAdapter adapter = new MouseInputAdapter() { };
        glass.addMouseListener(adapter);
        glass.addMouseMotionListener(adapter);

        this.addInternalFrameListener(new InternalFrameListenerAdapter() {
            public void internalFrameClosed(InternalFrameEvent e) { close(); }
            public void internalFrameClosing(InternalFrameEvent e){ close(); }
        });

        // Change frame border
        putClientProperty("JInternalFrame.frameType", "optionDialog");

        // Size frame
        Dimension size = getPreferredSize();
        Dimension rootSize = desktop.getSize();

        setBounds((rootSize.width - size.width) / 2,
                (rootSize.height - size.height) / 2, size.width, size.height);
        desktop.validate();
        try { setSelected(true); } 
        catch (PropertyVetoException ignored) { }

        glass.add(this);              // Add modal internal frame to glass pane
        rootPane.setGlassPane(glass); // Change glass pane to our panel
        glass.setVisible(true);       // Show glass pane, then modal dialog
    }

    private void close(){
        if (isVisible()) {
            try { setClosed(true); } 
            catch (PropertyVetoException ignored) { }
            setVisible(false);
            rootPane.getGlassPane().setVisible(false);
        }
    }

    @Override public void doDefaultCloseAction() {
        super.doDefaultCloseAction();
        close();
    }

    @Override public void setVisible(boolean flag) {
        super.setVisible(flag);
        if (flag) startModal();
        else stopModal();
    }

    private synchronized void startModal() {
        try {
            if (SwingUtilities.isEventDispatchThread()) {
                EventQueue theQueue = getToolkit().getSystemEventQueue();
                while (isVisible()) {
                    AWTEvent event = theQueue.getNextEvent();
                    Object source = event.getSource();
                    if (event instanceof ActiveEvent) {
                        ((ActiveEvent) event).dispatch();
                    } else if (source instanceof Component) {
                        ((Component) source).dispatchEvent(event);
                    } else if (source instanceof MenuComponent) {
                        ((MenuComponent) source).dispatchEvent(event);
                    } else {
                        System.err.println("Unable to dispatch: " + event);
                    }
                }
            } else { while (isVisible()) { wait(); } }
        } catch (InterruptedException ignored) {
        }

    }

    private synchronized void stopModal() { notifyAll(); }

}

Update: I've discovered that modal dialog boxes suit my needs fine, but if anyone does have an idea, I'd be glad to hear it. One thing I haven't tried is wrapping every method in a try {} catch (Exception e){} which would probably help a lot.

Nate Parsons
  • 14,431
  • 13
  • 51
  • 67
  • If debugging is failing, at least add System out / err's to the currently ignored catch blocks. – Pool May 12 '09 at 19:46
  • Did David Moles' solution work for you? If so, you should accept it, as this question is still open. – BoffinBrain Dec 23 '10 at 13:57
  • @BoffinbraiN Actually, I abandoned the modal internal frame approach before I had tried David's solution. I'd like to accept it, but only if I have independent confirmation that it works, and I don't have time to try it myself. – Nate Parsons Jan 05 '11 at 19:41
  • Wrapping every method in a try {} catch (Exception e){} block doesn't sound like a good idea. You might want to write a custom [Uncaught Exception Handler](http://stuffthathappens.com/blog/2007/10/07/programmers-notebook-uncaught-exception-handlers/) and take a look at this [bug](http://stuffthathappens.com/blog/2007/10/15/one-more-note-on-uncaught-exception-handlers/) regarding modal dialogs and Uncaught Exception Handlers. – Dario Seidl Sep 20 '11 at 03:03
  • Also it's maybe better to wrap PropertyVetoException and InterruptedException in some unchecked exception and rethrow them instead of ignoring them - just in case they ever get thrown. – Dario Seidl Sep 20 '11 at 03:04

5 Answers5

2

Try this. I got it from the Webby IT blog post on JInternal Frames: http://webbyit.blogspot.com/2011/03/managing-jinternalframes-within.html

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;

/**
 * An extended <code>JInternalFrame</code> that provides modality in a child/parent hierarchy.
 * <a href="From http://webbyit.blogspot.com/2011/03/managing-jinternalframes-within.html">source: webby it internal frames blog post</a>
 *
 * @author webbyit
 */
public class ModalityInternalFrame extends JInternalFrame {

    protected JDesktopPane desktopPane;
    protected JComponent parent;
    protected ModalityInternalFrame childFrame;
    protected JComponent focusOwner;
    private boolean wasCloseable;

    public ModalityInternalFrame() {
        init(); // here to allow netbeans to use class in gui builder
    }

    public ModalityInternalFrame(JComponent parent) {
        this(parent, null);
    }

    public ModalityInternalFrame(JComponent parent, String title) {
        this(parent, title, false);
    }

    public ModalityInternalFrame(JComponent parent, String title, boolean resizable) {
        this(parent, title, resizable, false);
    }

    public ModalityInternalFrame(JComponent parent, String title, boolean resizable, boolean closeable) {
        this(parent, title, resizable, closeable, false);
    }

    public ModalityInternalFrame(JComponent parent, String title, boolean resizable, boolean closeable,
            boolean maximizable) {
        this(parent, title, resizable, closeable, maximizable, false);
    }

    public ModalityInternalFrame(JComponent parent, String title, boolean resizable, boolean closeable,
            boolean maximizable,
            boolean iconifiable) {
        super(title, resizable, closeable, maximizable, iconifiable);
        setParentFrame(parent);
        //setFocusTraversalKeysEnabled(false);
        if (parent != null && parent instanceof ModalityInternalFrame) {
            ((ModalityInternalFrame) parent).setChildFrame(ModalityInternalFrame.this);

            /*
             * set focus to the new frame and show the frame Code added by Jasir
             */
            try {
                ((ModalityInternalFrame) parent).setSelected(false);
                setSelected(true);
                setVisible(true);
            } catch (PropertyVetoException ex) {
                Logger.getLogger(ModalityInternalFrame.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        // Add glass pane
        ModalityInternalGlassPane glassPane = new ModalityInternalGlassPane(this);
        setGlassPane(glassPane);


        // Add frame listeners
        addFrameListener();

        // Add frame veto listenr
        addFrameVetoListener();

        init();


        // calculate size and position


    }

    private void setParentFrame(JComponent parent) {
        desktopPane = JOptionPane.getDesktopPaneForComponent(parent);
        this.parent = parent == null ? JOptionPane.getDesktopPaneForComponent(parent) : parent; // default to desktop if no parent given
    }

    public JComponent getParentFrame() {
        return parent;
    }

    public void setChildFrame(ModalityInternalFrame childFrame) {
        this.childFrame = childFrame;
    }

    public ModalityInternalFrame getChildFrame() {
        return childFrame;
    }

    public boolean hasChildFrame() {
        return (childFrame != null);
    }

    protected void addFrameVetoListener() {
        addVetoableChangeListener(new VetoableChangeListener() {

            public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
                if (evt.getPropertyName().equals(JInternalFrame.IS_SELECTED_PROPERTY)
                        && evt.getNewValue().equals(Boolean.TRUE)) {
                    if (hasChildFrame()) {
                        //childFrame.setSelected(true);
                        if (childFrame.isIcon()) {
                            childFrame.setIcon(false);
                        }
                        throw new PropertyVetoException("no!", evt);
                    }
                }
            }
        });
    }

    /**
     * Method to control the display of the glass pane, dependant on the frame
     * being active or not
     */
    protected synchronized void addFrameListener() {
        addInternalFrameListener(new InternalFrameAdapter() {

            @Override
            public void internalFrameActivated(InternalFrameEvent e) {
                if (hasChildFrame() == true) {
                    getGlassPane().setVisible(true);
                    grabFocus();
                } else {
                    getGlassPane().setVisible(false);
                }
            }

            @Override
            public void internalFrameOpened(InternalFrameEvent e) {
                getGlassPane().setVisible(false);
                try {
                    setSelected(true);
                } catch (PropertyVetoException ex) {
                    Logger.getLogger(ModalityInternalFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            @Override
            public void internalFrameClosing(InternalFrameEvent e) {
                if (parent != null && parent instanceof ModalityInternalFrame) {
                    ((ModalityInternalFrame) parent).childClosing();
                }
            }
        });
    }

    /**
     * Method to handle child frame closing and make this frame available for
     * user input again with no glass pane visible
     */
    protected void childClosing() {
        setClosable(wasCloseable);
        getGlassPane().setVisible(false);
        if (focusOwner != null) {
            java.awt.EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    try {
                        moveToFront();
                        setSelected(true);
                        focusOwner.grabFocus();
                    } catch (PropertyVetoException ex) {
                    }
                }
            });
            focusOwner.grabFocus();
        }
        getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        setChildFrame(null);
        getDesktopPane().setSelectedFrame(this);
        System.out.println(getDesktopPane().getSelectedFrame());
    }

    /*
     * Method to handle child opening and becoming visible.
     */
    protected void childOpening() {
        // record the present focused component
        wasCloseable = isClosable();
        setClosable(false);
        focusOwner = (JComponent) getMostRecentFocusOwner();
        grabFocus();
        getGlassPane().setVisible(true);
        getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }

    @Override
    public void show() {
        if (parent != null && parent instanceof ModalityInternalFrame) {
            // Need to inform parent its about to lose its focus due
            // to child opening
            ((ModalityInternalFrame) parent).childOpening();
        }
        calculateBounds();
        super.show();
    }

    protected void init() {
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGap(0, 394, Short.MAX_VALUE));
        layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGap(0, 274, Short.MAX_VALUE));

        pack();
    }

    public void calculateBounds() {
        Dimension frameSize = getPreferredSize();
        Dimension parentSize = new Dimension();
        Dimension rootSize = new Dimension(); // size of desktop
        Point frameCoord = new Point();

        if (desktopPane != null) {
            rootSize = desktopPane.getSize(); // size of desktop
            frameCoord = SwingUtilities.convertPoint(parent, 0, 0, desktopPane);
            parentSize = parent.getSize();
        }

        //setBounds((rootSize.width - frameSize.width) / 2, (rootSize.height - frameSize.height) / 2, frameSize.width, frameSize.height);

        // We want dialog centered relative to its parent component
        int x = (parentSize.width - frameSize.width) / 2 + frameCoord.x;
        int y = (parentSize.height - frameSize.height) / 2 + frameCoord.y;

        // If possible, dialog should be fully visible
        int ovrx = x + frameSize.width - rootSize.width;
        int ovry = y + frameSize.height - rootSize.height;
        x = Math.max((ovrx > 0 ? x - ovrx : x), 0);
        y = Math.max((ovry > 0 ? y - ovry : y), 0);
        setBounds(x, y, frameSize.width, frameSize.height);
    }

    /**
     * Glass pane to overlay. Listens for mouse clicks and sets selected on
     * associated modal frame. Also if modal frame has no children make class
     * pane invisible
     */
    class ModalityInternalGlassPane extends JComponent {

        private ModalityInternalFrame modalFrame;

        public ModalityInternalGlassPane(ModalityInternalFrame frame) {
            modalFrame = frame;
            addMouseListener(new MouseAdapter() {

                @Override
                public void mouseClicked(MouseEvent e) {
                    if (modalFrame.isSelected() == false) {
                        try {
                            modalFrame.setSelected(true);
                            if (modalFrame.hasChildFrame() == false) {
                                setVisible(false);
                            }
                        } catch (PropertyVetoException e1) {
                            //e1.printStackTrace();
                        }
                    }
                }
            });
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.setColor(new Color(255, 255, 255, 100));
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }
}
Barett
  • 5,826
  • 6
  • 51
  • 55
Jasir
  • 677
  • 1
  • 8
  • 26
2

I can't quite get your code to run, but here's a simpler version, based on the Sun example, that does work -- the main frame has a button in it (taking up all the available space), but clicking the button is blocked until the internal frame has been closed.

You can see, pretty much all I did was replace the new JOptionPane().createInternalFrame() business with my own frame. My guess is that you're overcomplicating things when you try to do your own event dispatching.

Or am I missing something?

public class Foo {

  public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.setSize(600, 400);
    frame.setLocationByPlatform(true);

    JButton desktop = new JButton(new AbstractAction("Click me if you can") {
      @Override
      public void actionPerformed(ActionEvent e) {
        System.out.println("I have been clicked");
      }
    });
    frame.getContentPane().add(desktop);

    frame.setVisible(true);

    JInternalFrame modal = 
      new JInternalFrame("Modal Popup", false, true, false, false);
    JLabel popupContent = new JLabel("I am the popup");
    popupContent.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
    modal.add(popupContent);
    modal.pack();

    JPanel glass = new JPanel();
    glass.setOpaque(false);
    glass.add(modal);
    frame.setGlassPane(glass);
    glass.setVisible(true);
    modal.setVisible(true);

    modal.addInternalFrameListener(new ModalAdapter(glass));
  }
}

class ModalAdapter extends InternalFrameAdapter {
  Component glass;

  public ModalAdapter(Component glass) {
    this.glass = glass;

    // Associate dummy mouse listeners
    // Otherwise mouse events pass through
    MouseInputAdapter adapter = new MouseInputAdapter() {
    };
    glass.addMouseListener(adapter);
    glass.addMouseMotionListener(adapter);
  }

  public void internalFrameClosed(InternalFrameEvent e) {
    glass.setVisible(false);
  }
}
David Moles
  • 48,006
  • 27
  • 136
  • 235
1

I just had to do this thing for a project. All I did was passed the main window object to Jinternalframe. The main object has a semaphore that tracks whether modal is locked or not. Upon closing the Jinternalframe (extension) calls the main object's semaphore. Very simple. This is not the main code but you get the idea :

//called frame
public CallingFrame parent;
public void setParent(CallingFrame parent_){
    this.parent=parent_;
}
private void frameClosed(javax.swing.event.InternalFrameEvent evt) {
     parent.modalLocked=false; 
}

In my case the application uses a label with image-parts to call internalframes, so the code starts with

 //calling frame     
 CalledFrame cf=new CalledFrame();
 cf.setParent(this); 
 cf.setVisible(true);  
 modalLoacked=true;
 private void jLabel1MouseReleased(java.awt.event.MouseEvent evt) {
    if (modalLocked)
    return;
    else// (do your things)
  }

I followed the tutorials but most of them are overcomplicating things when a single semaphore will do the tricks of not letting you click on any area while one Called Frame is not closed.

Mr. Zen
  • 704
  • 3
  • 7
  • 17
0
     public void internalFrameClosing(InternalFrameEvent e){ close(); }

calling close() will cause internalFrameClosing() to be called again, until the stack overflows.

Try removing that listener altogether.

objects
  • 8,637
  • 4
  • 30
  • 38
  • Thanks, I'll try this out when I get the chance. I found an alternative to modal JInternalFrames altogether, but I still want to see this work. – Nate Parsons Jul 08 '09 at 03:56
-2

You can just add setClosable(true); in the constructor

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82