Java Tips Weblog

  • Blog Stats

    • 1,180,752 hits
  • Categories

  • Archives

Moving Windows

Posted by Rob Camick on June 14, 2009

Over the years I’ve seen many questions asking about borderless windows. The common suggestion is to use a JWindow or an undecorated JFrame, depending on the requirements. Often you will then find a follow up question asking how to move the window now that there is no title bar. The common answer is that you need to add your own listeners to handle the dragging of the window.

Well, that answer is not necessarily as simple as it seems. Sure the basics are straight forward, you would add a MouseListener to handle the mousePressed event to track the original location of the window. Then you would add a MouseMotionListener to handle the mouseDragged event so the window can be moved with each drag event. I noticed a couple of gotchas that fooled me the first time I attempted to do this.

My first attempt resulted in flickering movement as the window would move to the new location and then back to its original location and finally back to the new location. I now better understand that the mouseDragged events are generated relative to the window. I wasn’t properly considering the fact that the window was changing location at the same time as the mouse was being dragged.

The second problem was a little more subtle. Normally you would add the listeners to the window. However, as we all know each window actually contains a content pane where you add components. So when you click on the window you are actually clicking on the content pane. This is generally not a problem as MouseEvents are passed from ancestor to ancestor to find a component that handles the event, which will usually end up being the window itself. The problem occurs when a MouseListener is added to a child component as the events no longer reach the window. This can happen innocently, for example, by adding a tooltip to a component contained in the window.

My solution for the above gotchas is the ComponentMover class. First it implements a proper algorithm for moving components without flicker. More importantly it gives you flexibility for controlling which component is responsible for moving a Window. You can registor one, or more components with the ComponentMover. This component could be the window itself, or it might be a custom “title bar” that you add to the window. Either way, any drag events generated on these components will affect the window.

Although the above discussion deals directly with moving windows, the actual implementation of the ComponentMover is more generic. Any component can be moved. It could be a top level container like a JFrame or JDialog. Or it could be a container like a JPanel or JInternalFrame. Finally it can be an individual component like a JTextField or JLabel. It depends on how the ComponentMover is constructed. First you need to specify a Component to be moved. Secondly you need to register components to listen for mouseDragged events. This is done using one of the following constructors:

  • ComponentMover() – the empty constructor indicates that each registered component will handle the mouseDragged events. Therefore each individual component can be moved.
  • ComponentMover(Class destinationClass, Component… components) – indicates that each registered component should apply the mouseDragged events to a parent component. In this case the parent component is the first ancestor that is of the specified Class type.
  • ComponentMover(Component destinationComponent, Component… components) – indicates that each registered component should apply the mouseDragged events to the specified component.

Simple examples using each of these constructors would be:

  • to move individual components:
     

    JPanel panel = new JPanel();
    panel.setLayout( null );
    panel.add(c1);
    panel.add(c2);
    panel.add(c3);
    ComponentMover cm = new ComponentMover();
    cm.registerComponent(c1, c2, c3)

     

  • to move a JWindow by dragging a custom title bar:
     

    JWindow window = new JWindow();
    window.add(titleBar, BorderLayout.NORTH);
    ComponentMover cm = new ComponentMover(JWindow.class, titleBar);

     

  • to move a specific Window by dragging a custom title bar:
     

    JWindow window = new JWindow();
    window.add(titleBar, BorderLayout.NORTH);
    ComponentMover cm = new ComponentMover(window, titleBar);

     

By default the ComponentMover will limit the movement of the component to remain within the bounds of its parent. This can be controlled by using the setEdgeInsets(…) method. Positive values will contain the component within the parent while negative values will allow you to drag the component outside the visible bounds of the parent.

The ComponentMover also supports a “snapping” feature. The setSnapSize(…) method allows you to specify a snap distance and the component can only be moved in increments of that value. Once the mouse is dragged half the snap distance the component will move the entire distance. This will allow the user to more easily align components in a grid.

There may be situations when you require the ability to both move and resize a component. In this case the mouseDragged event should only be handled by either the move or the resize, but not both. To handle this situation you can use the setDragInsets(…) method. The insets, which typicaly would be the Border insets, specify the area in which drag events will be ignored for the purposes of moving the component. Therefore when you click in this area you will not see the move cursor, but instead you will see the resizing cursor.

Many people will just use a null layout when dragging components, while others may prefer to use a custom layout. In the latter case, the setAutoLayout(…) method can be used to force a layout of the component’s parent after the component has been moved.

Hopefully this class will solve your simple dragging issues.

Note, I’d like to thank Maxideon from the Sun forums for solving the “jumping” component problem I noticed when attempting to drag a text field. It turns out that this problem is noticable on any component that use the setAutoScrolls(true) method. When a component is dragged rapidly it appears that the mouse temporarily leaves the bounds of the component which in turn is generating a “synthetic mouseDragged event” which somehow affects the moving of the component. The simple solution was to turn off auto scrolls during the dragging process.

Try The Demo

Launch – Using Java™ Web Start (JRE 6 required)

Get The Code

ComponentMover.java

See Also

Resizing Components

About these ads

49 Responses to “Moving Windows”

  1. Allen Bagwell said

    I’ve tried your code. I’m adding a JPanel inside an undecorated JFrame then using your class code constructor per above:


    new ComponentMover(myFrame, myPanel);

    or


    new ComponentMover(JFrame.class, myPanel);

    The JPanel is simply painted black, no other components — so it just looks like a black rectangle when started. However, the gui still flickers just as badly when dragging it around the screen. I’m running this on a RedHat ENT4 box with Java 6 update 14. Is there something I’m missing here?

    • Rob Camick said

      Both approaches work fine for me. I use JDK6u7 on XP. Create a SSCCE (search the web if you don’t know what a SSCCE is) and send it to me using the “Contact Us” page and I’ll do a quick test to see if it is a code or version problem.

    • Rob Camick said

      Thanks to Allen for looking at the problem on Linux. The solution was to change the code to use:

      MouseEvent.getLocationOnScreen();

      instead of:

      SwingUtilities.convertPointToScreen(...);

      Not sure why the SwingUtilities method occasionally fails on Linux.

  2. Rnan said

    Thank a lot.
    It’s wonderful.

  3. Fraser said

    Here is a modification to the above code that allow for resizing of windows.


    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;

    /**
    * This class allows you to move a Component by using a mouse. The Component
    * moved can be a high level Window (ie. Window, Frame, Dialog) in which case
    * the Window is moved within the desktop. Or the Component can belong to a
    * Container in which case the Component is moved within the Container.
    *
    * When moving a Window, the listener can be added to a child Component of
    * the Window. In this case attempting to move the child will result in the
    * Window moving. For example, you might create a custom "Title Bar" for an
    * undecorated Window and moving of the Window is accomplished by moving the
    * title bar only. Multiple components can be registered as "window movers".
    *
    * Components can be registered when the class is created. Additional
    * components can be added at any time using the registerComponent() method.
    */
    public class ComponentMover extends MouseAdapter {
    // private boolean moveWindow;

    private Class destinationClass;
    private Component destinationComponent;
    private Component destination;
    private Component source;
    private boolean changeCursor = true;
    private Point pressed;
    private Point location;
    private Point sp;
    private Cursor originalCursor;
    private boolean autoscrolls;
    private Insets dragInsets = new Insets(0, 0, 0, 0);
    private Dimension snapSize = new Dimension(1, 1);
    private int compStartHeight;
    private int compStartWidth;
    private int minHeight;
    private int minWidth;

    /**
    * Constructor for moving individual components. The components must be
    * regisetered using the registerComponent() method.
    */
    public ComponentMover() {
    }

    /**
    * Constructor to specify a Class of Component that will be moved when
    * drag events are generated on a registered child component. The events
    * will be passed to the first ancestor of this specified class.
    *
    * @param destinationClass the Class of the ancestor component
    * @param component the Components to be registered for forwarding
    * drag events to the ancestor Component.
    */
    public ComponentMover(Class destinationClass, Component... components) {
    this.destinationClass = destinationClass;
    registerComponent(components);
    }

    /**
    * Constructor to specify a parent component that will be moved when drag
    * events are generated on a registered child component.
    *
    * @param destinationComponent the component drage events should be forwareded to
    * @param components the Components to be registered for forwarding drag
    * events to the parent component to be moved
    */
    public ComponentMover(Component destinationComponent, Component... components) {
    this.destinationComponent = destinationComponent;
    registerComponent(components);
    }

    /**
    * Get the change cursor property
    *
    * @return the change cursor property
    */
    public boolean isChangeCursor() {
    return changeCursor;
    }

    /**
    * Set the change cursor property
    *
    * @param changeCursor when true the cursor will be changed to the
    * Cursor.MOVE_CURSOR while the mouse is pressed
    */
    public void setChangeCursor(boolean changeCursor) {
    this.changeCursor = changeCursor;
    }

    /**
    * Get the drag insets
    *
    * @return the drag insets
    */
    public Insets getDragInsets() {
    return dragInsets;
    }

    /**
    * Set the drag insets. The insets specify an area where mouseDragged
    * events should be ignored and therefore the component will not be moved.
    * This will prevent these events from being confused with a
    * MouseMotionListener that supports component resizing.
    *
    * @param dragInsets
    */
    public void setDragInsets(Insets dragInsets) {
    this.dragInsets = dragInsets;
    }

    /**
    * Remove listeners from the specified component
    *
    * @param component the component the listeners are removed from
    */
    public void deregisterComponent(Component... components) {
    for (Component component : components) {
    component.removeMouseListener(this);
    }
    }

    /**
    * Add the required listeners to the specified component
    *
    * @param component the component the listeners are added to
    */
    public void registerComponent(Component... components) {
    for (Component component : components) {
    component.addMouseListener(this);
    component.addMouseMotionListener(this);
    this.minHeight = component.getHeight();
    this.minWidth = component.getWidth();
    }
    }

    /**
    * Get the snap size
    *
    * @return the snap size
    */
    public Dimension getSnapSize() {
    return snapSize;
    }

    /**
    * Set the snap size. Forces the component to be snapped to
    * the closest grid position. Snapping will occur when the mouse is
    * dragged half way.
    */
    public void setSnapSize(Dimension snapSize) {
    this.snapSize = snapSize;
    }

    /**
    * Setup the variables used to control the moving of the component:
    *
    * source - the source component of the mouse event
    * destination - the component that will ultimately be moved
    * pressed - the Point where the mouse was pressed in the destination
    * component coordinates.
    */
    @Override
    public void mousePressed(MouseEvent e) {

    if (((JFrame) e.getComponent()).getCursor().getType() == Cursor.N_RESIZE_CURSOR ||
    ((JFrame) e.getComponent()).getCursor().getType() == Cursor.E_RESIZE_CURSOR ||
    ((JFrame) e.getComponent()).getCursor().getType() == Cursor.SE_RESIZE_CURSOR) {
    sp = e.getLocationOnScreen();
    compStartHeight = ((JFrame) e.getComponent()).getSize().height;
    compStartWidth = ((JFrame) e.getComponent()).getSize().width;

    } else {

    source = e.getComponent();
    int width = source.getSize().width - dragInsets.left - dragInsets.right;
    int height = source.getSize().height - dragInsets.top - dragInsets.bottom;
    Rectangle r = new Rectangle(dragInsets.left, dragInsets.top, width, height);

    if (r.contains(e.getPoint())) {
    setupForDragging(e);
    }
    }
    }

    private void setupForDragging(MouseEvent e) {

    // Determine the component that will ultimately be moved

    if (destinationComponent != null) {
    destination = destinationComponent;
    } else if (destinationClass == null) {
    destination = source;
    } else // forward events to destination component
    {
    destination = SwingUtilities.getAncestorOfClass(destinationClass, source);
    }

    pressed = e.getLocationOnScreen();
    location = destination.getLocation();

    if (changeCursor) {
    originalCursor = source.getCursor();
    source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    }

    // Making sure autoscrolls is false will allow for smoother dragging of
    // individual components

    if (destination instanceof JComponent) {
    JComponent jc = (JComponent) destination;
    autoscrolls = jc.getAutoscrolls();
    jc.setAutoscrolls(false);
    }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    Point moved = e.getLocationOnScreen();
    //Is the mouse in the bottom right corder (South East)
    if (moved.x > ((JFrame) e.getComponent()).getSize().width - 5 &&
    moved.y > ((JFrame) e.getComponent()).getSize().height - 5) {
    ((JFrame) e.getComponent()).setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
    return;
    }
    //Is the mouse in the right corder (East)
    else if (moved.x > ((JFrame) e.getComponent()).getSize().width - 5) {
    ((JFrame) e.getComponent()).setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
    return;
    }
    //Is the mouse in the bottom of the frame (South)
    if (moved.y > ((JFrame) e.getComponent()).getSize().height - 5) {
    ((JFrame) e.getComponent()).setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
    return;
    }
    //Otherwise set the cursor as default
    else {
    ((JFrame) e.getComponent()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    }

    /**
    * Move the component to its new location. The dragged Point must be in
    * the destination coordinates.
    */
    @Override
    public void mouseDragged(MouseEvent e) {
    Point dragged = e.getLocationOnScreen();
    int compWidth = e.getComponent().getSize().width;

    if (((JFrame) e.getComponent()).getCursor().getType() == Cursor.N_RESIZE_CURSOR) {
    int nextHeight = compStartHeight + dragged.y - sp.y;
    if (nextHeight > minHeight) {
    ((JFrame) e.getComponent()).setSize(compWidth, nextHeight);
    ((JFrame) e.getComponent()).setVisible(true);
    mouseDraggedEvent(e);
    }

    } else if (((JFrame) e.getComponent()).getCursor().getType() == Cursor.E_RESIZE_CURSOR) {
    int nextWidth = compStartWidth + dragged.x - sp.x;
    if (nextWidth > minWidth) {
    ((JFrame) e.getComponent()).setSize(nextWidth, compStartHeight);
    ((JFrame) e.getComponent()).setVisible(true);
    mouseDraggedEvent(e);
    }

    } else if (((JFrame) e.getComponent()).getCursor().getType() == Cursor.SE_RESIZE_CURSOR) {
    int nextWidth = compStartWidth + dragged.x - sp.x;
    int nextHeight = compStartHeight + dragged.y - sp.y;
    if (nextWidth > minWidth && nextHeight > minHeight) {
    ((JFrame) e.getComponent()).setSize(nextWidth, nextHeight);
    ((JFrame) e.getComponent()).setVisible(true);
    mouseDraggedEvent(e);
    }

    } else {

    int dragX = getDragDistance(dragged.x, pressed.x, snapSize.width);
    int dragY = getDragDistance(dragged.y, pressed.y, snapSize.height);
    destination.setLocation(location.x + dragX, location.y + dragY);
    }
    }

    /*
    * Determine how far the mouse has moved from where dragging started
    * (Assume drag direction is down and right for positive drag distance)
    */
    private int getDragDistance(int larger, int smaller, int snapSize) {
    int halfway = snapSize / 2;
    int drag = larger - smaller;
    drag += (drag < 0) ? -halfway : halfway;
    drag = (drag / snapSize) * snapSize;

    return drag;
    }

    /**
    * Restore the original state of the Component
    */
    @Override
    public void mouseReleased(MouseEvent e) {
    source.setCursor(originalCursor);

    }

    public void mouseDraggedEvent(MouseEvent e) {
    }
    }

  4. Fraser said

    You will also need this

    ComponentMover mover = new ComponentMover() {

    @Override
    public void mouseDraggedEvent(MouseEvent e) {
    //overridden mouseDraggedEvent just in case you want to resize or position components inside your
    //frame.
    }
    };

  5. Shane said

    I’ve just implemented both this and the re-sizer into my code for testing of a generic customisable layout edit for our clients…had the basics running within a few minutes.

    Very nice. Saved me a lot of time

  6. Rowan Baker said

    This is very helpful, and extremely east to find with a google search. Cheers.

  7. Daniel Sampedro said

    Awesome, its really good, thanks you. Don’t you think java might be a little easier? I mean, why Swing components have no a boolean moveable attribute?

  8. Anonymous said

    This is wonderful!
    Finding an example this complete and robust is hard!
    Keep up the good work!

  9. Rob Camick said

    Glad you like it. I just added another feature today to control movement within the bounds of the parent container.

  10. Derik said

    I know this post is old, but you just saved me a heap of time! I would even donate if you had a donate button!

  11. byo19 said

    Great job, thank you very much!

  12. Tom Lurge said

    I’m trying to use this code but got some problems: I’m working on a Petri Net Editor. “Places” and “Transitions” are drawn as simple circles and rectangles, with a name and markings attached. I declared the Place and Transition classes to extend Component and was glad that it compiled fine. I also get no runtime errors. But nothing is draggable :(

    I see two possibilities what might go wrong:
    1) extending Component doesn’t work (even if it compiles)
    2) or maybe I embedded it the wrong way. Here is my code for the drawing panel:

    public class DrawPanel extends JPanel
    {
        public void paintComponent(Graphics g)
        {
            super.paintComponent(g);
    
            cm = new ComponentMover();
    
            for(Place p : PlaceArray.getPlaces())
            {
                drawPlace(g, p);
                cm.registerComponent(p);
            }
    
            for(Transition t : TransArray.getTransitions())
            {
                drawTransition(g, t);
                cm.registerComponent(t);
            }
    
            for(Edge e : EdgeArray.getEdges())
            {
                drawEdge(g, e);
                cm.registerComponent(e);
            }
        }
    }
    

    The drawing panel itself get’s added to a scrollPanel (which doesn’t scroll right now but that shouldn’t be the source of this problem). That scrollPanel gets added to a bigger JPanel that also holds the menu system and which itself is finally added to the contentPane of a JFrame.

    I hope this is the right place to ask this question. Stackoverflow would seem to be a better option to ask for help with coding problems but since I’m trying to reuse _this_ code I figured it more appropriate to start here.

    Thanks for your time,
    Thomas

    • Rob Camick said

      You override painting methods to do custom painting. The paintComponent() method is called whenever Swing determines the panel needs to be repainted. I gave examples of how to add components to a panel and of how to register the component with the ComponentMover.

      There is no need to create a custom panel and override the paintComponent() method for this. All you do is create a panel, add a component to it and register the component with the ComponentMover.

      I suggest you try this with one of your components. Once you get it working for one then you start adding multiple components. Also you should be extending JComponent NOT Component since you should not mix AWT components with Swing.

      • Tom Lurge said

        Thanks a lot! Your answer did put me on the right track. I ran into another problem though. As I already said I’m trying to build a Petri Net Editor. I had planned to make not only the nodes draggable but the edges as well. Now I realized that I can’t listen for mouseclicks on a (graphical) line but only on the JPanel the line is drawn on. That becomes a problem when an edge connects 2 nodes in different corners of the workspace and consequently a line is drawn across the workspace and the respective JPanel covers a lot of other nodes and edges. Although the JPanel renders transparent it blocks the mouseevents reaching components below. Are you aware of a way around this problem?

      • Rob Camick said

        I think you need to override the contains() method of your panel. This method is used for hit detection on a component. Check out the Round Button example to get you started. Sorry, I don’t know how to specify the Shape to match your line.

  13. Anonymous said

    you are a guru! simply superb!!

  14. Oliver said

    From the Demo I did something like the following expect to work, purpose is to swap two components.

            _frame = new JFrame();
            _frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            _frame.setVisible(true);
            
            Container contentPane = _frame.getContentPane();
            contentPane.setLayout(new MigLayout());
            JPanel p1 = new JPanel();
            JPanel p2 = new JPanel();
            
            ComponentMover mover = new ComponentMover();
            mover.setAutoLayout(true);
    
            contentPane.add(p1, "w 50!, h 50!");
            contentPane.add(p2, "w 50!, h 50!");
            
            mover.registerComponent(p1, p2);
    

    Any idea, why this isn’t working (with GridLayout it doesn’t work too)? Or did I make a mistake somewhere?

  15. I found this while searching for completing the Nimbus LnF. I’ve used it, and I really appreciate the work you’ve put into this, but I find on multi-screen desktops, it snaps to the primary screen, and doesn’t let you drag beyond it.

    Can you point me in the right direction as to where it’s doing the bounds calculation?

    My Swing-Fu is not strong, but my java is. I tried modifying the getBoundSize(Component) method to return the full size of the entire desktop, but that doesn’t seem to work.

    Thanks!

  16. Anonymous said

    Grat job. Works perfectly. I’ve just added draging frame funcionality to my dialogs and it works perfect

  17. Jure said

    Hi!

    Great job. Will help me a lot. Thanks.

    However, I have found one performance problem when using funcitonality with Windows (frames, dialogs, ..) – at least on Linux machines.
    When requesting getBoundingSize, which happens on every mouseDragged event, java invokes native call – env.getMaximumWindowBounds().
    One way is to store the returned value locally or event better to use default Toolkit – which in fact works also on multi-screen configurations.

    - GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    - Rectangle bounds = env.getMaximumWindowBounds();
    - return new Dimension(bounds.width, bounds.height);

    + return Toolkit.getDefaultToolkit().getScreenSize();

    - Jure

    • I’m not sure that any of this really works consistently, and you need to do a lot of acrobatics just to figure out the different scenarios. I have two dual-head video cards, driving 3 monitors. So in my instance, when I run the following:

      GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
      
      System.out.println("MaxWinBounds");
      Rectangle bounds = env.getMaximumWindowBounds();
      System.out.println(String.format(" height: %s\n width: %s\n x: %s\n y: %s", bounds.height, bounds.width, bounds.x, bounds.y));
      
      System.out.println("\nToolkitScreenSize");
      bounds = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
      System.out.println(String.format(" height: %s\n width: %s\n x: %s\n y: %s", bounds.height, bounds.width, bounds.x, bounds.y));
      
      System.out.println("\nScreenDevices");
      for (GraphicsDevice device : env.getScreenDevices()) {
        bounds = device.getConfigurations()[0].getBounds();
        System.out.println(String.format(" height: %s\n width: %s\n x: %s\n y: %s", bounds.height, bounds.width, bounds.x, bounds.y));
      }
      

      Regards of how my monitors are plugged in or arranged in the Display Properties -> Settings, the width and height for every call (MaxBounds, ScreenDevice, or Toolkit) is the size of the monitor (1280 x 1024), not the not desktop size (3840, 1024). However, the bounds.x coordinate, does map the virtual coordinate properly of the upper left hand corner *in relation to* the primary monitor.

      So when the primary monitor is center, it’s x-coord is 0, the left monitor is -1280, and the right monitor is 1280. When the primary monitor is left, then the x-coord go: 0, 1280, 2560. Setting the edge insets, for these values, instead of getting the value from getBoundingSize() seems to be the best solution I could come up with to completely handle a true multi-monitor setup. Though I would love to hear if anyone has alternative solutions, cause this still doesn’t seem to be completely reliable.

  18. Anitha said

    Excellent and Brilliant Example. It helped me to come up with Restaurant Table Layout Manager.
    I have onemore question. While moving the components is it possible that each component can have a space around them like padding.
    Thanks inadvance

  19. Anonymous said

    I fixed it so it works for multiple screens and also so you don’t lose your window off the screen

    public class ComponentMover extends MouseAdapter
        {
        private Insets dragInsets = new Insets(0, 0, 0, 0);
        private Dimension snapSize = new Dimension(1, 1);
        private Insets edgeInsets = new Insets(0, 0, 0, 0);
        private boolean changeCursor = true;
        private boolean autoLayout = false;
        private int offsetFromBounds = 20;
        private Class destinationClass;
        private Component destinationComponent;
        private Component destination;
        private Component source;
    
        private Point pressed;
        private int offsetX;
        private int offsetY;
        private Point location;
    
        private Cursor originalCursor;
        private boolean autoscrolls;
        private boolean potentialDrag;
    
    
        /**
         * Constructor for moving individual components. The components must be
         * regisetered using the registerComponent() method.
         */
        public ComponentMover()
            {
            }
    
        /**
         * Constructor to specify a Class of Component that will be moved when
         * drag events are generated on a registered child component. The events
         * will be passed to the first ancestor of this specified class.
         *
         * @param destinationClass the Class of the ancestor component
         * @param components       the Components to be registered for forwarding
         *                         drag events to the ancestor Component.
         */
        public ComponentMover(Class destinationClass, Component... components)
            {
            this.destinationClass = destinationClass;
            registerComponent(components);
            }
    
        /**
         * Constructor to specify a parent component that will be moved when drag
         * events are generated on a registered child component.
         *
         * @param destinationComponent the component drage events should be forwareded to
         * @param components           the Components to be registered for forwarding drag
         *                             events to the parent component to be moved
         */
        public ComponentMover(Component destinationComponent, Component... components)
            {
            this.destinationComponent = destinationComponent;
            registerComponent(components);
            }
    
        /**
         * Get the auto layout property
         *
         * @return the auto layout property
         */
        public boolean isAutoLayout()
            {
            return autoLayout;
            }
    
        /**
         * Set the auto layout property
         *
         * @param autoLayout when true layout will be invoked on the parent container
         */
        public void setAutoLayout(boolean autoLayout)
            {
            this.autoLayout = autoLayout;
            }
    
        /**
         * Get the change cursor property
         *
         * @return the change cursor property
         */
        public boolean isChangeCursor()
            {
            return changeCursor;
            }
    
        /**
         * Set the change cursor property
         *
         * @param changeCursor when true the cursor will be changed to the
         *                     Cursor.MOVE_CURSOR while the mouse is pressed
         */
        public void setChangeCursor(boolean changeCursor)
            {
            this.changeCursor = changeCursor;
            }
    
        /**
         * Get the drag insets
         *
         * @return the drag insets
         */
        public Insets getDragInsets()
            {
            return dragInsets;
            }
    
        /**
         * Set the drag insets. The insets specify an area where mouseDragged
         * events should be ignored and therefore the component will not be moved.
         * This will prevent these events from being confused with a
         * MouseMotionListener that supports component resizing.
         *
         * @param dragInsets
         */
        public void setDragInsets(Insets dragInsets)
            {
            this.dragInsets = dragInsets;
            }
    
        /**
         * Get the bounds insets
         *
         * @return the bounds insets
         */
        public Insets getEdgeInsets()
            {
            return edgeInsets;
            }
    
        /**
         * Set the edge insets. The insets specify how close to each edge of the parent
         * component that the child component can be moved. Positive values means the
         * component must be contained within the parent. Negative values means the
         * component can be moved outside the parent.
         *
         * @param edgeInsets
         */
        public void setEdgeInsets(Insets edgeInsets)
            {
            this.edgeInsets = edgeInsets;
            }
    
        /**
         * Remove listeners from the specified component
         *
         * @param components the component the listeners are removed from
         */
        public void deregisterComponent(Component... components)
            {
            for (Component component : components)
                component.removeMouseListener(this);
            }
    
        /**
         * Add the required listeners to the specified component
         *
         * @param components the component the listeners are added to
         */
        public void registerComponent(Component... components)
            {
            for (Component component : components)
                component.addMouseListener(this);
            }
    
        /**
         * Get the snap size
         *
         * @return the snap size
         */
        public Dimension getSnapSize()
            {
            return snapSize;
            }
    
        /**
         * Set the snap size. Forces the component to be snapped to
         * the closest grid position. Snapping will occur when the mouse is
         * dragged half way.
         */
        public void setSnapSize(Dimension snapSize)
            {
            if (snapSize.width &lt; 1
                    || snapSize.height &lt; 1)
                throw new IllegalArgumentException(&quot;Snap sizes must be greater than 0&quot;);
    
            this.snapSize = snapSize;
            }
    
        /**
         * Setup the variables used to control the moving of the component:
         * 
         * source - the source component of the mouse event
         * destination - the component that will ultimately be moved
         * pressed - the Point where the mouse was pressed in the destination
         * component coordinates.
         */
        @Override
        public void mousePressed(MouseEvent e)
            {
            source = e.getComponent();
            int width = source.getSize().width - dragInsets.left - dragInsets.right;
            int height = source.getSize().height - dragInsets.top - dragInsets.bottom;
            Rectangle r = new Rectangle(dragInsets.left, dragInsets.top, width, height);
    
            if (r.contains(e.getPoint()))
                setupForDragging(e);
            }
    
    
        private void setupForDragging(MouseEvent e)
            {
            source.addMouseMotionListener(this);
            potentialDrag = true;
    
            //  Determine the component that will ultimately be moved
    
            if (destinationComponent != null)
                {
                destination = destinationComponent;
                }
            else if (destinationClass == null)
                {
                destination = source;
                }
            else  //  forward events to destination component
                {
                destination = SwingUtilities.getAncestorOfClass(destinationClass, source);
                }
    
            pressed = e.getLocationOnScreen();
            location = destination.getLocation();
            offsetX = (int) (pressed.getX() - location.getX());
            offsetY = (int) (pressed.getY() - location.getY());
    
            if (changeCursor)
                {
                originalCursor = source.getCursor();
                source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
                }
    
            //  Making sure autoscrolls is false will allow for smoother dragging of
            //  individual components
    
            if (destination instanceof JComponent)
                {
                JComponent jc = (JComponent) destination;
                autoscrolls = jc.getAutoscrolls();
                jc.setAutoscrolls(false);
                }
            }
    
        /**
         * Move the component to its new location. The dragged Point must be in
         * the destination coordinates.
         */
        @Override
        public void mouseDragged(MouseEvent e)
            {
            Point dragged = e.getLocationOnScreen();
            int dragX = getDragDistance(dragged.x, pressed.x, snapSize.width);
            int dragY = getDragDistance(dragged.y, pressed.y, snapSize.height);
    
            int locationX = location.x + dragX;
            int locationY = location.y + dragY;
    
            //  Mouse dragged events are not generated for every pixel the mouse
            //  is moved. Adjust the location to make sure we are still on a
            //  snap value.
    
            while (locationX &lt; edgeInsets.left)
                locationX += snapSize.width;
    
            while (locationY &lt; edgeInsets.top)
                locationY += snapSize.height;
    
            int userPreferredNewLocationX = (int) (dragged.getX() - offsetX);
            int userPreferredNewLocationY = (int) (dragged.getY() - offsetY);
            destination.setLocation(userPreferredNewLocationX, userPreferredNewLocationY);
    
            }
    
        /*
          *  Determine how far the mouse has moved from where dragging started
          *  (Assume drag direction is down and right for positive drag distance)
          */
        private int getDragDistance(int larger, int smaller, int snapSize)
            {
            int halfway = snapSize / 2;
            int drag = larger - smaller;
            drag += (drag  maxWidth)
                {
                xAdjust+= 5;
                change = true;
                }
    
            while (endX -xAdjust maxHeight)
                {
                yAdjust+=5;
                change = true;
                }
    
            while (originY -yAdjust&lt; offsetFromBounds)
                {
                yAdjust-=5;
                change = true;
                }
    
            if (change)
                destination.setLocation((int)(destination.getLocation().getX()-xAdjust), (int)(destination.getLocation().getY()-yAdjust));
    
            if (changeCursor)
                source.setCursor(originalCursor);
    
            if (destination instanceof JComponent)
                {
                ((JComponent) destination).setAutoscrolls(autoscrolls);
                }
    
            //  Layout the components on the parent container
    
            if (autoLayout)
                {
                if (destination instanceof JComponent)
                    {
                    ((JComponent) destination).revalidate();
                    }
                else
                    {
                    destination.validate();
                    }
                }
            }
    
        public  Point getLocationOnCurrentScreen(final Component c)
            {
            final Point relativeLocation = c.getLocationOnScreen();
    
            final Rectangle currentScreenBounds = c.getGraphicsConfiguration().getBounds();
    
            relativeLocation.x -= currentScreenBounds.x;
            relativeLocation.y -= currentScreenBounds.y;
    
            return relativeLocation;
            }
    
        public GraphicsDevice getGraphicsDeviceAt(Point pos)
            {
            GraphicsDevice device = null;
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsDevice lstGDs[] = ge.getScreenDevices();
            ArrayList lstDevices = new ArrayList(lstGDs.length);
            for (GraphicsDevice gd : lstGDs)
                {
                GraphicsConfiguration gc = gd.getDefaultConfiguration();
                Rectangle screenBounds = gc.getBounds();
                if (screenBounds.contains(pos))
                    {
                    lstDevices.add(gd);
                    }
                }
            if (lstDevices.size() == 1)
                {
                device = lstDevices.get(0);
                }
            return device;
            }
    
    
        public Rectangle getScreenBoundsAt(Point pos)
            {
            GraphicsDevice gd = getGraphicsDeviceAt(pos);
            Rectangle bounds = null;
    
            if (gd != null)
                {
                GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    
                if (env.getDefaultScreenDevice() == gd)
                    {
                    bounds = env.getMaximumWindowBounds();
                    }
                else
                    {
                    bounds = gd.getDefaultConfiguration().getBounds();
                    //for some reason it doesn't use zero zero in this case
                    bounds = new Rectangle(0, 0, (int) bounds.getWidth(), (int) bounds.getHeight());
                    }
    
                System.err.println(bounds);
                }
            return bounds;
    
            }
    
    
        public int getOffsetFromBounds()
            {
            return offsetFromBounds;
            }
    
        public void setOffsetFromBounds(int offsetFromBounds)
            {
            this.offsetFromBounds = offsetFromBounds;
            }
        }
    
    • Tommy said

      I can’t compile it, especially the method getDragDistance is missing parts. Can you attach it again, I am really interested to see it working, since I want to move windows between multiple screens.

  20. Lis said

    It is very cool class for dragging swing components! I’m hightly appreciate your work! Thank you!

  21. Diego said

    When I minimize the window, everything goes back to its original position, any possible solution??

    • Rob Camick said

      I can’t reproduce the problem. Maybe you forgot to set the LayoutManager to null on your panel.

      • Ion said

        I can confirm what Diego says. Any refresh or resize on the container window will reset the position for all objects initially moved. Also, setting a null layout lead to nothing displayed in the target panel. I work on Mac

      • Rob Camick said

        Yes, when using a null layout you are now responsible for setting the size/location of components. In my demo code when I created the labels I did:

        JLabel label1 = new JLabel("Dragable Label 1");
        label1.setSize( label1.getPreferredSize() );
        label1.setLocation(50, 50);
        
  22. Tim said

    Rob,
    Great little class. Got a couple of questions for you, both related to the limiting area while moving a component…

    (1) I run multiple monitors and have made a simple kind of change to create one large limiting area. It basically works, but since my monitors resolution and sizes are slightly different, the real limit is not a true rectangle, but two slightly offset rectangles. Is there a way set create a limiting area that would take that into account?

    (2) I am using this class for a project where the main window is Round. When I register a JButton that is on the RoundPanel and move it, I can move the JButton off the Panel. The limiting area in this case is Round, not the Rectangular area minus the Insets. Is there a way set create a limiting area that would take that into account?

    Thanks,
    Tim

    • Rob Camick said

      Tim,

      Both good questions. Unfortunately I don’t have answers for either one.

      1. I have no idea how multiple monitors work, so I’m not sure what would need to be changed.

      2. Again, I’ve never played with non rectangular windows. Maybe you can change the getBoundingSize() method to return a Shape. Then the mouseDragged code would have to be changed to check if the Shape contains the destination component. I have no idea how you will know that the parent container is round instead of rectangular.

  23. >>
    The common suggestion is to use a JWindow or an undecorated JFrame, depending on the requirements. Often you will then find a follow up question asking how to move the window now that there is no title bar. The common answer is that you need to add your own listeners to handle the dragging of the window.
    >>

    I would say that’s more of a general windowing issue than a Java issue myself. The solution on my system is simple: hold down the Alt button while dragging.

  24. Anonymous said

    Extremely useful code Rob – this has definitely saved me some hours coding!

    I had a small issue with the mouse cursor not changing back and forth correctly (I had a JPanel within a JPanel and after the drag, hovering over the inner JPanel would give me the MOVE_CURSOR).

    I fixed it with the following slight adjustments to your code:

    >> In setupForDragging()
    if (changeCursor && originalCursor == null)
    {
    originalCursor = source.getCursor();

    >> In mouseReleased()
    if (changeCursor) {
    source.setCursor( originalCursor );
    originalCursor = null;
    }

    I have an issue still with the way the JPanel displays as I drag it around. It seems that the leading edge always disappears as I move it around the screen. Does this happen for you? Do you have any suggestions how I might fix it?

    Many thanks again for sharing your work! :)

  25. Sorin said

    I get a NullPointerException at this line : component.addMouseListener( this ); All i did was add the ComponentMover.java to my project and then in my Main.java add ComponentMover cm = new ComponentMover();
    cm.registerComponent(jLabel13);

    • Rob Camick said

      Your label is null.

      • Sorin said

        What do you mean ? I tried with other components and they didn’t work either. How can they be null ? And how can i fix this ? Thanks dude,your blog is articles are great,keep it up !

      • Rob Camick said

        “How can they be null ?” Defining a variable is not the same as creating an instance of a component.

        Did you try:

        System.out.println(jLabel13);
        ComponentMover cm = new ComponentMover();
         cm.registerComponent(jLabel13);
        

        Does it display “null” or information about the label?

      • Sorin said

        Yes,it displays null.Why is that ? how can i fix this ? Sorry,total beginner here :( Searched google,didn’t find much about this.

      • Rob Camick said

        See reply 21 for an example of create an instance of a component. You need to use the “new” keyword. This is my last replay because this question has nothing to do with using this class. This is a question about basic Java and should be asked in a Java forum, or you should be reading a text book. We can’t teach you the basics.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 91 other followers

%d bloggers like this: