Archives

Meta

Subscribe

Moving Windows

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.

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.

Basic Dragging

The ComponentMover class is relatively complex code because of all the features its supports. However the basic code for dragging a component is very simple. If you find the ComponentMover too complex for your needs then you can try using the simple DragListener presented below. This class just does the basics of changing the location of any component as it is dragged:

Try The Demo

Get The Code

See Also

Like this:

LikeLoading...

Related

This entry was posted on June 14, 2009 at 1:52 pm and is filed under Awt, Classes.
You can follow any responses to this entry through the RSS 2.0 feed.
You can skip to the end and leave a response. Pinging is currently not allowed.

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?

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.

/**
* 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;

/**
* 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);
}

/**
* 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;
}

/**
* 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;
}

/**
* 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) {

/**
* 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;

Tom Lurgesaid

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:

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.

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 Lurgesaid

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?

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.

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.

Juresaid

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.

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:

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.

Anithasaid

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

Ionsaid

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

Timsaid

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?

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.

>>
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.

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?

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);

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.