I have a container in which components can be dragged. The problem I encountered is whenever I picked up a component and it was automatically added to a window, the component would stop firing drag events even if the mouse was still dragging on the handle. Then the next time I would click and drag on the handle (now in the floating window) it would continue dragging. Here's some basic code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Box.Filler;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
/**
*
*/
@SuppressWarnings("serial")
public class DraggableDemo extends JPanel {
private class DraggablePanel extends JPanel {
private class MyMouseAdapter extends MouseAdapter {
private DraggablePanel floater;
private Point dragOffset;
@Override
public void mouseDragged(MouseEvent e) {
if (floater == null) {
startDragging(e);
}
Point transformedEventPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), owner);
updateWindowLocation(transformedEventPoint);
}
private void startDragging(MouseEvent e) {
Component component = e.getComponent();
floater = (DraggablePanel) SwingUtilities.getAncestorOfClass(DraggablePanel.class, component);
Point floaterAbsoluteLocation = SwingUtilities.convertPoint(floater.getParent(), floater.getLocation(), owner);
Point transformedEventPoint = SwingUtilities.convertPoint(component, e.getPoint(), owner);
// designate a drag offset so the component's corner doesn't teleport to mouse location
dragOffset = new Point(transformedEventPoint.x - floaterAbsoluteLocation.x, transformedEventPoint.y - floaterAbsoluteLocation.y);
swapComponents(getFiller(), floater);
// place the floating component in a window
window.add(floater);
window.pack();
floater.setBorder(new LineBorder(Color.YELLOW));
updateWindowLocation(transformedEventPoint);
window.setVisible(true);
}
private void updateWindowLocation(Point point) {
Point p = new Point(point.x - dragOffset.x, point.y - dragOffset.y);
SwingUtilities.convertPointToScreen(p, owner);
window.setLocation(p);
}
private JComponent getFiller() {
Dimension dimension = floater.getSize();
JComponent filler = (JComponent) Box.createRigidArea(dimension);
// border
Border dashedBorder = BorderFactory.createDashedBorder(Color.gray, 3f, 3f, 2f, true);
filler.setBorder(dashedBorder);
filler.setOpaque(true);
return filler;
}
@Override
public void mouseReleased(MouseEvent e) {
Point transformedEventPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), owner);
Component compDroppedOn = SwingUtilities.getDeepestComponentAt(owner, transformedEventPoint.x, transformedEventPoint.y);
if (compDroppedOn instanceof Filler) {
window.remove(floater);
swapComponents(floater, compDroppedOn);
window.setVisible(false);
floater = null;
}
}
}
private JWindow window;
private MyMouseAdapter mouseAdapter;
private DraggableDemo owner;
DraggablePanel(DraggableDemo owner) {
this.owner = owner;
JPanel smallPanel = new JPanel();
smallPanel.setPreferredSize(new Dimension(200, 100));
smallPanel.setBackground(Color.green);
JPanel bigPanel = new JPanel();
bigPanel.setPreferredSize(new Dimension(200, 400));
bigPanel.setBackground(Color.red);
setLayout(new BorderLayout());
add(smallPanel, BorderLayout.NORTH);
add(bigPanel, BorderLayout.CENTER);
setBorder(new LineBorder(Color.blue));
mouseAdapter = new MyMouseAdapter();
smallPanel.addMouseListener(mouseAdapter);
smallPanel.addMouseMotionListener(mouseAdapter);
owner.addMouseListener(mouseAdapter);
owner.addMouseMotionListener(mouseAdapter);
window = new JWindow();
window.setAlwaysOnTop(true);
}
}
private GridBagConstraints gbc;
/**
*
*/
public DraggableDemo() {
setLayout(new GridBagLayout());
gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
for (int i = 0; i < 2; i++) {
add(new DraggablePanel(this), gbc);
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DraggableDemo newContentPane = new DraggableDemo();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
/**
*/
private void swapComponents(Component toAdd, Component toRemove) {
Container parent = toRemove.getParent();
int index = parent.getComponentZOrder(toRemove);
parent.remove(toRemove);
parent.add(toAdd, gbc, index);
revalidate();
repaint();
}
}
What I've tried:
The original container holds a few components per column, each firing different mouse events.
Initially I registered the mouse listener to the main container and got and moved the component based on coordinates, but that didn't satisfy the condition of mouse entering / exiting smaller components firing events themselves.
After this I attempted to register multiple mouse listeners, each doing its own thing, but I've learned that these would eat up events that would happen in the hierarchy.
Finally, I decided to register a single listener to every single component I needed to register to and based on the component instance returned from events, I would delegate these events to an appropriate mouse 'adapter'.
How can I fix the component in order to drag correctly when it is picked up? Like I've said, I can't register only to the main container, because then I wouldn't have access to events fired by smaller components, and I can't register multiple listeners because events would then be eaten up by the first one that fired.
It is not just the component that stops firing the drag event. It appear that no drag event is generated for any component:
In your
createAndShowGUI()
method, after the frame is visible, I added:Which should display all mouse events generated for any component. However, once the Window is displayed, no further events are generated for any component, confirming your problem.
Next I removed the above code and replaced it with a custom EventQueue to simply display every event that is generated:
Now I do see all the mouse drag events.
So maybe you can create a custom EventQueue that handles mouse events that are generated on your DraggablePanel?