Preventing JInternalFrame from being moved out of a JDesktopPane
Asked Answered
W

5

8

When I have a JInternalFrame in a JDesktopPane, the JInternalFrame is movable (which is good). However, it's possible to move it outside of the visible scope of the JDesktopPane (which I'm not so fond of)

To see for yourself, here's some sample code:

public static void main(String[] args) {

  JFrame frame = new JFrame("JDesktopPane");
  JDesktopPane tableDisplay = new JDesktopPane();

  JInternalFrame internalFrame = new JInternalFrame("JInternalFrame",true,true,true,true);
  internalFrame.setContentPane(new JLabel("Content"));
  internalFrame.pack();
  internalFrame.setVisible(true);
  tableDisplay.add(internalFrame, JDesktopPane.POPUP_LAYER);

  frame.setContentPane(tableDisplay);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.setMinimumSize(new Dimension(400, 300));
  frame.setVisible(true);
}

Is it possible to set either the JInternalFrame or JDesktopPane so that they won't allow this?

Worldwide answered 15/11, 2011 at 13:19 Comment(0)
M
8

The collaborator which is responsible for doing the move/resize is the DesktopPaneManager. So I would try to limit the movement to within the pane. Here's a quick & dirty proof of concept:

    JDesktopPane background = new JDesktopPane();
    JInternalFrame internalFrame = new JInternalFrame("Internal Frame",
            true, true, true, true);
    DesktopManager manager = new DefaultDesktopManager() {
        /** This moves the <code>JComponent</code> and repaints the damaged areas. */
        @Override
        public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
            boolean didResize = (f.getWidth() != newWidth || f.getHeight() != newHeight);
            if (!inBounds((JInternalFrame) f, newX, newY, newWidth, newHeight)) return;
            f.setBounds(newX, newY, newWidth, newHeight);
            if(didResize) {
                f.validate();
            } 
        }

        protected boolean inBounds(JInternalFrame f, int newX, int newY, int newWidth, int newHeight) {
            if (newX < 0 || newY < 0) return false;
            if (newX + newWidth > f.getDesktopPane().getWidth()) return false;
            if (newY + newHeight > f.getDesktopPane().getHeight()) return false;
            return true;
        }

    };
    background.setDesktopManager(manager);

There are some issues to solve, obviously :-) F.i.

  • use the manager as appropriate for the LAF, which could be done by implementing a wrapper DesktopManager which delegates everything else to the LAF installed
  • check for side-effects (the drag appears a unresponsive after having hit a wall, there might be other things needed)

Edit

just to clarify: with "unresponsive" I mean that the user has to release and press/drag again (once the internal frame has hit the desktop bounds) to further move the. That's not overly surprising, as the BorderListener (that's the mouseListener installed by BasicInternalFrame) keeps some state related to the initial press and then requests re-locates relative to that initial location. Dragging the mouse with the frame stuck somewhere confuses that internal state.

Interestingly, looking at the code, it seems like there had been intentions to limit the movement to not push it to the outside,

    // Make sure we stay in-bounds
    if(newX + i.left <= -__x)
        newX = -__x - i.left + 1;
    if(newY + i.top <= -__y)
        newY = -__y - i.top + 1;
    if(newX + __x + i.right >= pWidth)
        newX = pWidth - __x - i.right - 1;
    if(newY + __y + i.bottom >= pHeight)
        newY =  pHeight - __y - i.bottom - 1;

that's relative to the current mouse location, though.

Microsurgery answered 15/11, 2011 at 15:42 Comment(4)
+1 - a cracking answer, thanks. I think I've solved the unresponsiveness issue (I'll post my solution separately).Worldwide
+1 for help as i am getting same problem. Really thanks for post.Angeliaangelic
If I use this trick and then try to minimize the JInternalFrames then I get an NPR, null Window w.Josefajosefina
Careful, this doesn't work with dragMode == FASTER_DRAG_MODE (the default), because dragFrameFaster in DefaultDesktopManager does not call setBoundsForFrame. For this to "work", you need to force the drag mode to slow: background.setDragMode(1337);.Ventage
W
8

Full credit to kleopatra for pointing me in the right direction.

I think I've solved the unresponsiveness issue, and am sharing my solution here. Following kleopatra's answer, if the mouse point is outside of the pane, the internal frame is at the edge of the pane. Also, this continues to follow the mouse - i.e. if you move the mouse off the bottom of the pane, and then round to the right hand side of the pane, the frame will follow along the bottom of the pane, and then up the right sand of the pane.

public class BoundedDesktopManager extends DefaultDesktopManager {

  @Override
  public void beginDraggingFrame(JComponent f) {
    // Don't do anything. Needed to prevent the DefaultDesktopManager setting the dragMode
  }

  @Override
  public void beginResizingFrame(JComponent f, int direction) {
    // Don't do anything. Needed to prevent the DefaultDesktopManager setting the dragMode
  }

  @Override
  public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
    boolean didResize = (f.getWidth() != newWidth || f.getHeight() != newHeight);
    if (!inBounds((JInternalFrame) f, newX, newY, newWidth, newHeight)) {
      Container parent = f.getParent();
      Dimension parentSize = parent.getSize();
      int boundedX = (int) Math.min(Math.max(0, newX), parentSize.getWidth() - newWidth);
      int boundedY = (int) Math.min(Math.max(0, newY), parentSize.getHeight() - newHeight);
      f.setBounds(boundedX, boundedY, newWidth, newHeight);
    } else {
      f.setBounds(newX, newY, newWidth, newHeight);
    }
    if(didResize) {
      f.validate();
    }
  }

  protected boolean inBounds(JInternalFrame f, int newX, int newY, int newWidth, int newHeight) {
    if (newX < 0 || newY < 0) return false;
    if (newX + newWidth > f.getDesktopPane().getWidth()) return false;
    if (newY + newHeight > f.getDesktopPane().getHeight()) return false;
    return true;
  }
}
Worldwide answered 16/11, 2011 at 12:11 Comment(0)
B
0

Try to set FlowLayout of JFrame and use add() method to add JDesktopPane.

frame.setLayout(new FlowLayout());
tableDisplay.setPreferredSize(new Dimension(100,100));
frame.add(tableDisplay);
Basel answered 15/11, 2011 at 13:29 Comment(1)
First, this doesn't seem to work - i.e. it doesn't solve the moving outside of the JDesktopPane problem. Second, I'm looking for a solution at the JDesktopPane or JInternalFrame level, not the JFrame level. (I want to use these components as part of a larger GUI.)Worldwide
G
0

It is important that the top of the JInternalFrame not get hidden, since that is where its grab bar and control buttons are located. So, as a last step before setting bounds, make sure that your y value is not negative: for example, with a statement like y = Math.max(0, y).

Grata answered 26/8, 2015 at 0:0 Comment(0)
V
0

This fully satisfies my needs. It keeps the dragged frame fully visible and it "slides" the window on the edge of the desktop pane area when the mouse cursor is moved beyond it.

JDesktopPane desktop = new JDesktopPane();
DesktopManager manager = new DefaultDesktopManager() {
    @Override
    public void dragFrame(JComponent f, int newX, int newY) {
        JDesktopPane desktop = ((JInternalFrame) f).getDesktopPane();
        newX = Math.clamp(newX, 0, desktop.getWidth() - f.getWidth());
        newY = Math.clamp(newY, 0, desktop.getHeight() - f.getHeight());
        super.dragFrame(f, newX, newY);
    }
};
background.setDesktopManager(desktop);

Math.clamp is available in Java 21. Implement it yourself if needed.

Ventage answered 7/10, 2023 at 22:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.