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 );
ComponentMover cm = new ComponentMover();
cm.registerComponent(c1, c2, c3)
- to move a JWindow by dragging a custom title bar:
JWindow window = new JWindow();
ComponentMover cm = new ComponentMover(JWindow.class, titleBar);
- to move a specific Window by dragging a custom title bar:
JWindow window = new JWindow();
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.