在使用 mouseDrag 滚动时暂时禁用或阻止重新绘制 JViewPort
Temporarily disable or prevent repainting JViewPort on scrolling with a mouseDrag
我写了一个 MouseListener
如下定义,这样我就可以移动 JButton
来重新排序 JPanel
中的组件。 JPanel
位于 JScrollPane
内,以便在添加多个组件时可以滚动它们。
我遇到的问题是,当拖动组件并且鼠标离开 scrollpane/viewport 时,组件将快速回到其在 JPanel
内的位置,然后将以正确的方式绘制地点。我假设此行为是由于视口在我调用 scrollRectToVisible()
时调用其子项的重绘
有什么方法可以防止这种情况发生?
请注意我限Java5
监听器
import java.awt.Component;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class DragListener extends MouseInputAdapter
{
private Point location;
private MouseEvent pressed;
private MouseEvent dragged;
private MouseEvent dropped;
@Override
public void mousePressed(MouseEvent me)
{
pressed = me;
}
@Override
public void mouseDragged(MouseEvent me)
{
dragged = me;
Component component = dragged.getComponent();
Container parent = component.getParent();
Container superParent = parent.getParent();
if(superParent instanceof JViewport)
{
JViewport vp = (JViewport)superParent;
Rectangle vpb = vp.getBounds();
Point pt = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(pt, vp);
if(!vpb.contains(pt))
{
int yDiff = (pt.y < vpb.y ) ? pt.y : pt.y - vpb.height;
vpb.translate(0, yDiff);
vp.scrollRectToVisible(vpb);
}
}
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
// Mouse release omitted
}
Gui(在 NetBeans 中创建)
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
public class DragginTest extends javax.swing.JFrame
{
public DragginTest()
{
initComponents();
addListeners(jButton1, jButton2, jButton3, jButton4, jButton5, jButton6, jButton7, jButton8, jButton9);
}
private void addListeners(JButton... buttons)
{
DragListener drag = new DragListener();
for(JButton b : buttons)
{
b.addMouseListener(drag);
b.addMouseMotionListener(drag);
}
}
@SuppressWarnings("unchecked")
private void initComponents()
{
jLayeredPane1 = new javax.swing.JLayeredPane();
jScrollPane1 = new javax.swing.JScrollPane();
mainPanel = new javax.swing.JPanel();
jButton1 = new javax.swing.JButton();
jButton2 = new javax.swing.JButton();
jButton3 = new javax.swing.JButton();
jButton4 = new javax.swing.JButton();
jButton5 = new javax.swing.JButton();
jButton6 = new javax.swing.JButton();
jButton7 = new javax.swing.JButton();
jButton8 = new javax.swing.JButton();
jButton9 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setPreferredSize(new java.awt.Dimension(450, 450));
mainPanel.setLayout(new java.awt.GridLayout(5, 2, 2, 2));
// Below Repeated for buttons 1-9 (left out for conciseness)
jButton1.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
jButton1.setForeground(new java.awt.Color(255, 0, 0));
jButton1.setText("1");
mainPanel.add(jButton1);
// End Repeat
jScrollPane1.setViewportView(mainPanel);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(40, 40, 40)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 205, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(38, 38, 38))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(40, 40, 40)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 226, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(53, Short.MAX_VALUE))
);
pack();
}
public static void main(String args[])
{
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable()
{
public void run()
{
new DragginTest().setVisible(true);
}
});
}
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JButton jButton3;
private javax.swing.JButton jButton4;
private javax.swing.JButton jButton5;
private javax.swing.JButton jButton6;
private javax.swing.JButton jButton7;
private javax.swing.JButton jButton8;
private javax.swing.JButton jButton9;
private javax.swing.JLayeredPane jLayeredPane1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JPanel mainPanel;
}
你有两个核心问题,第一个是你试图对抗布局管理器,它会在无效时重新布局组件,第二个是你要在一个拖拽过程中真的,奇怪的方式。
当视口的可视区域发生变化时,组件将重新生效,这会导致重新计算组件的位置,使其暂时 return 到其初始位置。
此时此刻你的一个选择就是没有它
mainPanel.setLayout(null);
mainPanel.setPreferredSize(new Dimension(200, 600));
// Below Repeated for buttons 1-9 (left out for conciseness)
jButton1.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
jButton1.setForeground(new java.awt.Color(255, 0, 0));
jButton1.setText("1");
jButton1.setBounds(0, 0, 100, 100);
mainPanel.add(jButton1);
这会在您引入更多组件时引起问题。
您的 mouseDragged
方法还可以大大简化
@Override
public void mouseDragged(MouseEvent me) {
JComponent source = (JComponent) me.getComponent();
JComponent parent = (JComponent) source.getParent();
Point p = me.getPoint();
p = SwingUtilities.convertPoint(source, p, parent);
Rectangle bounds = source.getBounds();
bounds.setLocation(p);
bounds.x -= pressed.getX();
bounds.y -= pressed.getY();
source.setBounds(bounds);
parent.scrollRectToVisible(bounds);
}
更好的解决方案是使用已经存在的 Transferable
API and/or 拖放 API ,基本上从中删除组件当前容器并根据其放置位置将其重新添加到组件层次结构中的不同位置。这允许您继续使用布局管理器 ;)
举个例子,看看Java - How to drag and drop JPanel with its components
更新了 DnD 示例
好的,所以这个例子是从 Java - How to drag and drop JPanel with its components 借用的,但允许您在组件被删除时 "reposition"。作为额外的好处,组件 "should" 出现的位置很好 "indicator"...
因为组件在 "exported" 时被序列化,这导致监听器和 DragGestureRecognizer
的问题无休止。为此,我实现了一个 DragDropManager
,其唯一目的是 install
和 uninstall
DragGestureHandler
和 DragGestureRecognizer
在 [=53] 中的某些点=] 过程...这就是为什么我倾向于转移状态而不是组件 :P
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DragginTest extends javax.swing.JFrame {
public DragginTest() {
initComponents();
}
@SuppressWarnings("unchecked")
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
mainPanel = new javax.swing.JPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setPreferredSize(new java.awt.Dimension(450, 450));
mainPanel.setLayout(new GridLayout(10, 0));
// Below Repeated for buttons 1-9 (left out for conciseness)
for (int index = 0; index < 10; index++) {
JButton btn = new JButton(String.valueOf(index));
btn.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
btn.setForeground(new java.awt.Color(255, 0, 0));
DragDropManager.INSTANCE.installDrag(btn);
mainPanel.add(btn);
}
// End Repeat
DropHandler dropHandler = new DropHandler();
DropTarget dropTarget = new DropTarget(mainPanel, DnDConstants.ACTION_MOVE, dropHandler, true);
mainPanel.setDropTarget(dropTarget);
jScrollPane1.setViewportView(mainPanel);
getContentPane().setLayout(new BorderLayout());
add(jScrollPane1);
pack();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new DragginTest().setVisible(true);
}
});
}
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JPanel mainPanel;
public enum DragDropManager {
INSTANCE;
private Map<Component, DragManager> handlers = new HashMap<>(25);
protected void installDrag(Component comp) {
handlers.put(comp, new DragManager(comp));
}
protected void uninstallDrag(Component comp) {
DragManager manager = handlers.remove(comp);
if (manager != null) {
manager.uninstall();
}
}
protected class DragManager {
DragGestureHandler dragGestureHandler;
DragGestureRecognizer dgr;
public DragManager(Component comp) {
dragGestureHandler = new DragGestureHandler(comp);
dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
comp,
DnDConstants.ACTION_MOVE,
dragGestureHandler);
}
public void uninstall() {
dgr.removeDragGestureListener(dragGestureHandler);
dragGestureHandler = null;
dgr = null;
}
}
}
public static class ComponentDataFlavor extends DataFlavor {
// This saves me having to make lots of copies of the same thing
public static final ComponentDataFlavor SHARED_INSTANCE = new ComponentDataFlavor();
public ComponentDataFlavor() {
super(JPanel.class, null);
}
}
public static class ComponentTransferable implements Transferable {
private DataFlavor[] flavors = new DataFlavor[]{ComponentDataFlavor.SHARED_INSTANCE};
private Component component;
public ComponentTransferable(Component panel) {
this.component = panel;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
// Okay, for this example, this is over kill, but makes it easier
// to add new flavor support by subclassing
boolean supported = false;
for (DataFlavor mine : getTransferDataFlavors()) {
if (mine.equals(flavor)) {
supported = true;
break;
}
}
return supported;
}
public Component getComponent() {
return component;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
Object data = null;
if (isDataFlavorSupported(flavor)) {
data = getComponent();
} else {
throw new UnsupportedFlavorException(flavor);
}
return data;
}
}
public static class DragGestureHandler implements DragGestureListener, DragSourceListener {
private Container parent;
private final Component component;
public DragGestureHandler(Component child) {
this.component = child;
}
public Component getComponent() {
return component;
}
public void setParent(Container parent) {
this.parent = parent;
}
public Container getParent() {
return parent;
}
@Override
public void dragGestureRecognized(DragGestureEvent dge) {
// When the drag begins, we need to grab a reference to the
// parent container so we can return it if the drop
// is rejected
Container parent = getComponent().getParent();
setParent(parent);
// Remove the panel from the parent. If we don't do this, it
// can cause serialization issues. We could over come this
// by allowing the drop target to remove the component, but that's
// an argument for another day
parent.remove(getComponent());
// Update the display
parent.invalidate();
parent.repaint();
// Create our transferable wrapper
Transferable transferable = new ComponentTransferable(getComponent());
// Start the "drag" process...
DragSource ds = dge.getDragSource();
ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), transferable, this);
DragDropManager.INSTANCE.uninstallDrag(getComponent());
}
@Override
public void dragEnter(DragSourceDragEvent dsde) {
}
@Override
public void dragOver(DragSourceDragEvent dsde) {
}
@Override
public void dropActionChanged(DragSourceDragEvent dsde) {
}
@Override
public void dragExit(DragSourceEvent dse) {
}
@Override
public void dragDropEnd(DragSourceDropEvent dsde) {
// If the drop was not sucessful, we need to
// return the component back to it's previous
// parent
if (!dsde.getDropSuccess()) {
getParent().add(getComponent());
getParent().invalidate();
getParent().repaint();
}
}
}
public class DropHandler implements DropTargetListener {
private JComponent spacer = new JPanel();
public DropHandler() {
spacer.setBackground(Color.RED);
}
@Override
public void dragEnter(DropTargetDragEvent dtde) {
// Determine if can actual process the contents comming in.
// You could try and inspect the transferable as well, but
// There is an issue on the MacOS under some circumstances
// where it does not actually bundle the data until you accept the
// drop.
if (dtde.isDataFlavorSupported(ComponentDataFlavor.SHARED_INSTANCE)) {
dtde.acceptDrag(DnDConstants.ACTION_MOVE);
} else {
dtde.rejectDrag();
}
}
@Override
public void dragOver(DropTargetDragEvent dtde) {
if (dtde.isDataFlavorSupported(ComponentDataFlavor.SHARED_INSTANCE)) {
Point p = dtde.getLocation();
DropTargetContext dtc = dtde.getDropTargetContext();
Container parent = (Container) dtc.getComponent();
Component target = parent.getComponentAt(p);
int insertPoint = Math.max(0, parent.getComponentZOrder(target));
if (spacer.getParent() == null) {
parent.add(spacer, insertPoint);
} else {
parent.setComponentZOrder(spacer, insertPoint);
}
parent.revalidate();
parent.repaint();
Point pic = SwingUtilities.convertPoint(spacer, p, target);
Rectangle bounds = spacer.getBounds();
bounds.setLocation(pic);
((JComponent) parent).scrollRectToVisible(bounds);
}
}
@Override
public void dropActionChanged(DropTargetDragEvent dtde) {
}
@Override
public void dragExit(DropTargetEvent dte) {
Container parent = (Container) dte.getDropTargetContext().getComponent();
parent.remove(spacer);
parent.revalidate();
parent.repaint();
}
@Override
public void drop(DropTargetDropEvent dtde) {
boolean success = false;
// Basically, we want to unwrap the present...
if (dtde.isDataFlavorSupported(ComponentDataFlavor.SHARED_INSTANCE)) {
Transferable transferable = dtde.getTransferable();
try {
Object data = transferable.getTransferData(ComponentDataFlavor.SHARED_INSTANCE);
if (data instanceof Component) {
Component target = (Component) data;
DropTargetContext dtc = dtde.getDropTargetContext();
Component component = dtc.getComponent();
if (component instanceof JComponent) {
Container parent = target.getParent();
if (parent != null) {
parent.remove(target);
}
parent = (Container) component;
Point p = dtde.getLocation();
Component before = parent.getComponentAt(p);
int insertPoint = Math.max(0, parent.getComponentZOrder(before));
parent.remove(spacer);
System.out.println(insertPoint);
parent.add(target, insertPoint);
parent.revalidate();
parent.repaint();
DragDropManager.INSTANCE.installDrag(target);
success = true;
dtde.acceptDrop(DnDConstants.ACTION_MOVE);
invalidate();
repaint();
} else {
success = false;
dtde.rejectDrop();
}
} else {
success = false;
dtde.rejectDrop();
}
} catch (Exception exp) {
success = false;
dtde.rejectDrop();
exp.printStackTrace();
}
} else {
success = false;
dtde.rejectDrop();
}
dtde.dropComplete(success);
}
}
}
我在您的 DragListener 代码中添加了 hack。基本上它会在您拖动时删除布局管理器,因此重新验证什么都不做,并在释放鼠标时恢复布局管理器:
import java.awt.*;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class DragListener extends MouseInputAdapter
{
private Point location;
private MouseEvent pressed;
private MouseEvent dragged;
private MouseEvent dropped;
private LayoutManager layout;
@Override
public void mousePressed(MouseEvent me)
{
pressed = me;
Component component = me.getComponent();
Container parent = component.getParent();
parent.setPreferredSize(parent.getPreferredSize());
layout = parent.getLayout();
parent.setLayout(null);
}
@Override
public void mouseDragged(MouseEvent me)
{
dragged = me;
Component component = dragged.getComponent();
Container parent = component.getParent();
Container superParent = parent.getParent();
if(superParent instanceof JViewport)
{
JViewport vp = (JViewport)superParent;
Rectangle vpb = vp.getBounds();
Point pt = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(pt, vp);
if(!vpb.contains(pt))
{
int yDiff = (pt.y < vpb.y ) ? pt.y : pt.y - vpb.height;
vpb.translate(0, yDiff);
vp.scrollRectToVisible(vpb);
}
}
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
// Mouse release omitted
@Override
public void mouseReleased(MouseEvent me)
{
Component component = me.getComponent();
Container parent = component.getParent();
parent.setPreferredSize( null );
parent.setLayout(layout);
parent.validate();
parent.repaint();
}
}
当然,我假设您的真实 mouseReleased 代码具有将按钮插入容器中适当位置的逻辑,这样它的真实位置可以由 GridLayout 维护,否则组件将返回到其原始位置。
编辑:
这是一个在释放鼠标按钮时将按钮移动到新位置的版本。有点复杂,因为您需要担心 ZOrder。那就是把一个组件往下拉就可以了。但是,如果您尝试向上拖动一个组件,那么它会被绘制在其他按钮下方。临时重置 ZOrder 可以解决此问题。
天哪,代码开始变成一个大 hack:) 临时空布局和临时 ZOrder。
无论如何这是代码:
import java.awt.*;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class DragListener extends MouseInputAdapter
{
private Point location;
private MouseEvent pressed;
private MouseEvent dragged;
private MouseEvent dropped;
private LayoutManager layout;
private Rectangle originalBounds;
private int originalZOrder;
@Override
public void mousePressed(MouseEvent me)
{
pressed = me;
Component component = me.getComponent();
Container parent = component.getParent();
originalBounds = component.getBounds();
originalZOrder = parent.getComponentZOrder(component);
parent.setPreferredSize(parent.getPreferredSize());
layout = parent.getLayout();
parent.setLayout(null);
parent.setComponentZOrder(component, 0);
}
@Override
public void mouseDragged(MouseEvent me)
{
JComponent source = (JComponent) me.getComponent();
JComponent parent = (JComponent) source.getParent();
Point p = me.getPoint();
p = SwingUtilities.convertPoint(source, p, parent);
Rectangle bounds = source.getBounds();
bounds.setLocation(p);
bounds.x -= pressed.getX();
bounds.y -= pressed.getY();
source.setLocation(0, bounds.y);
parent.scrollRectToVisible(bounds);
}
@Override
public void mouseReleased(MouseEvent me)
{
boolean moved = false;
Component component = me.getComponent();
Container parent = component.getParent();
Point location = component.getLocation();
if (location.y < 0)
{
parent.add(component, 0);
moved = true;
}
else
{
for (int i = 0; i < parent.getComponentCount(); i++)
{
Component c = parent.getComponent(i);
Rectangle bounds = c.getBounds();
if (c == component)
bounds = originalBounds;
// Component is released in the space originally occupied
// by the component or over an existing component
if (bounds.contains(0, location.y))
{
if (c == component)
{
parent.setComponentZOrder(component, originalZOrder);
}
else
{
parent.add(component, i);
}
moved = true;
break;
}
}
}
// Component is positioned below all components in the container
if (!moved)
{
parent.add(component, parent.getComponentCount() - 1);
}
// Restore layout manager
parent.setPreferredSize( null );
parent.setLayout(layout);
parent.validate();
parent.repaint();
component.requestFocusInWindow();
}
private static void createAndShowGUI()
{
JPanel panel = new JPanel( new GridLayout(0, 1) );
DragListener drag = new DragListener();
for (int i = 0; i <10; i++)
{
JButton button = new JButton("" + i);
button.setFont(new java.awt.Font("Tahoma", 1, 48));
button.setForeground(new java.awt.Color(255, 0, 0));
button.addMouseListener(drag);
button.addMouseMotionListener(drag);
panel.add( button );
}
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane(panel) );
frame.setLocationByPlatform( true );
frame.setSize(200, 400);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
我写了一个 MouseListener
如下定义,这样我就可以移动 JButton
来重新排序 JPanel
中的组件。 JPanel
位于 JScrollPane
内,以便在添加多个组件时可以滚动它们。
我遇到的问题是,当拖动组件并且鼠标离开 scrollpane/viewport 时,组件将快速回到其在 JPanel
内的位置,然后将以正确的方式绘制地点。我假设此行为是由于视口在我调用 scrollRectToVisible()
有什么方法可以防止这种情况发生?
请注意我限Java5
监听器
import java.awt.Component;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class DragListener extends MouseInputAdapter
{
private Point location;
private MouseEvent pressed;
private MouseEvent dragged;
private MouseEvent dropped;
@Override
public void mousePressed(MouseEvent me)
{
pressed = me;
}
@Override
public void mouseDragged(MouseEvent me)
{
dragged = me;
Component component = dragged.getComponent();
Container parent = component.getParent();
Container superParent = parent.getParent();
if(superParent instanceof JViewport)
{
JViewport vp = (JViewport)superParent;
Rectangle vpb = vp.getBounds();
Point pt = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(pt, vp);
if(!vpb.contains(pt))
{
int yDiff = (pt.y < vpb.y ) ? pt.y : pt.y - vpb.height;
vpb.translate(0, yDiff);
vp.scrollRectToVisible(vpb);
}
}
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
// Mouse release omitted
}
Gui(在 NetBeans 中创建)
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
public class DragginTest extends javax.swing.JFrame
{
public DragginTest()
{
initComponents();
addListeners(jButton1, jButton2, jButton3, jButton4, jButton5, jButton6, jButton7, jButton8, jButton9);
}
private void addListeners(JButton... buttons)
{
DragListener drag = new DragListener();
for(JButton b : buttons)
{
b.addMouseListener(drag);
b.addMouseMotionListener(drag);
}
}
@SuppressWarnings("unchecked")
private void initComponents()
{
jLayeredPane1 = new javax.swing.JLayeredPane();
jScrollPane1 = new javax.swing.JScrollPane();
mainPanel = new javax.swing.JPanel();
jButton1 = new javax.swing.JButton();
jButton2 = new javax.swing.JButton();
jButton3 = new javax.swing.JButton();
jButton4 = new javax.swing.JButton();
jButton5 = new javax.swing.JButton();
jButton6 = new javax.swing.JButton();
jButton7 = new javax.swing.JButton();
jButton8 = new javax.swing.JButton();
jButton9 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setPreferredSize(new java.awt.Dimension(450, 450));
mainPanel.setLayout(new java.awt.GridLayout(5, 2, 2, 2));
// Below Repeated for buttons 1-9 (left out for conciseness)
jButton1.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
jButton1.setForeground(new java.awt.Color(255, 0, 0));
jButton1.setText("1");
mainPanel.add(jButton1);
// End Repeat
jScrollPane1.setViewportView(mainPanel);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(40, 40, 40)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 205, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(38, 38, 38))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(40, 40, 40)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 226, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(53, Short.MAX_VALUE))
);
pack();
}
public static void main(String args[])
{
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable()
{
public void run()
{
new DragginTest().setVisible(true);
}
});
}
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JButton jButton3;
private javax.swing.JButton jButton4;
private javax.swing.JButton jButton5;
private javax.swing.JButton jButton6;
private javax.swing.JButton jButton7;
private javax.swing.JButton jButton8;
private javax.swing.JButton jButton9;
private javax.swing.JLayeredPane jLayeredPane1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JPanel mainPanel;
}
你有两个核心问题,第一个是你试图对抗布局管理器,它会在无效时重新布局组件,第二个是你要在一个拖拽过程中真的,奇怪的方式。
当视口的可视区域发生变化时,组件将重新生效,这会导致重新计算组件的位置,使其暂时 return 到其初始位置。
此时此刻你的一个选择就是没有它
mainPanel.setLayout(null);
mainPanel.setPreferredSize(new Dimension(200, 600));
// Below Repeated for buttons 1-9 (left out for conciseness)
jButton1.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
jButton1.setForeground(new java.awt.Color(255, 0, 0));
jButton1.setText("1");
jButton1.setBounds(0, 0, 100, 100);
mainPanel.add(jButton1);
这会在您引入更多组件时引起问题。
您的 mouseDragged
方法还可以大大简化
@Override
public void mouseDragged(MouseEvent me) {
JComponent source = (JComponent) me.getComponent();
JComponent parent = (JComponent) source.getParent();
Point p = me.getPoint();
p = SwingUtilities.convertPoint(source, p, parent);
Rectangle bounds = source.getBounds();
bounds.setLocation(p);
bounds.x -= pressed.getX();
bounds.y -= pressed.getY();
source.setBounds(bounds);
parent.scrollRectToVisible(bounds);
}
更好的解决方案是使用已经存在的 Transferable
API and/or 拖放 API ,基本上从中删除组件当前容器并根据其放置位置将其重新添加到组件层次结构中的不同位置。这允许您继续使用布局管理器 ;)
举个例子,看看Java - How to drag and drop JPanel with its components
更新了 DnD 示例
好的,所以这个例子是从 Java - How to drag and drop JPanel with its components 借用的,但允许您在组件被删除时 "reposition"。作为额外的好处,组件 "should" 出现的位置很好 "indicator"...
因为组件在 "exported" 时被序列化,这导致监听器和 DragGestureRecognizer
的问题无休止。为此,我实现了一个 DragDropManager
,其唯一目的是 install
和 uninstall
DragGestureHandler
和 DragGestureRecognizer
在 [=53] 中的某些点=] 过程...这就是为什么我倾向于转移状态而不是组件 :P
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DragginTest extends javax.swing.JFrame {
public DragginTest() {
initComponents();
}
@SuppressWarnings("unchecked")
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
mainPanel = new javax.swing.JPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setPreferredSize(new java.awt.Dimension(450, 450));
mainPanel.setLayout(new GridLayout(10, 0));
// Below Repeated for buttons 1-9 (left out for conciseness)
for (int index = 0; index < 10; index++) {
JButton btn = new JButton(String.valueOf(index));
btn.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
btn.setForeground(new java.awt.Color(255, 0, 0));
DragDropManager.INSTANCE.installDrag(btn);
mainPanel.add(btn);
}
// End Repeat
DropHandler dropHandler = new DropHandler();
DropTarget dropTarget = new DropTarget(mainPanel, DnDConstants.ACTION_MOVE, dropHandler, true);
mainPanel.setDropTarget(dropTarget);
jScrollPane1.setViewportView(mainPanel);
getContentPane().setLayout(new BorderLayout());
add(jScrollPane1);
pack();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new DragginTest().setVisible(true);
}
});
}
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JPanel mainPanel;
public enum DragDropManager {
INSTANCE;
private Map<Component, DragManager> handlers = new HashMap<>(25);
protected void installDrag(Component comp) {
handlers.put(comp, new DragManager(comp));
}
protected void uninstallDrag(Component comp) {
DragManager manager = handlers.remove(comp);
if (manager != null) {
manager.uninstall();
}
}
protected class DragManager {
DragGestureHandler dragGestureHandler;
DragGestureRecognizer dgr;
public DragManager(Component comp) {
dragGestureHandler = new DragGestureHandler(comp);
dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
comp,
DnDConstants.ACTION_MOVE,
dragGestureHandler);
}
public void uninstall() {
dgr.removeDragGestureListener(dragGestureHandler);
dragGestureHandler = null;
dgr = null;
}
}
}
public static class ComponentDataFlavor extends DataFlavor {
// This saves me having to make lots of copies of the same thing
public static final ComponentDataFlavor SHARED_INSTANCE = new ComponentDataFlavor();
public ComponentDataFlavor() {
super(JPanel.class, null);
}
}
public static class ComponentTransferable implements Transferable {
private DataFlavor[] flavors = new DataFlavor[]{ComponentDataFlavor.SHARED_INSTANCE};
private Component component;
public ComponentTransferable(Component panel) {
this.component = panel;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
// Okay, for this example, this is over kill, but makes it easier
// to add new flavor support by subclassing
boolean supported = false;
for (DataFlavor mine : getTransferDataFlavors()) {
if (mine.equals(flavor)) {
supported = true;
break;
}
}
return supported;
}
public Component getComponent() {
return component;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
Object data = null;
if (isDataFlavorSupported(flavor)) {
data = getComponent();
} else {
throw new UnsupportedFlavorException(flavor);
}
return data;
}
}
public static class DragGestureHandler implements DragGestureListener, DragSourceListener {
private Container parent;
private final Component component;
public DragGestureHandler(Component child) {
this.component = child;
}
public Component getComponent() {
return component;
}
public void setParent(Container parent) {
this.parent = parent;
}
public Container getParent() {
return parent;
}
@Override
public void dragGestureRecognized(DragGestureEvent dge) {
// When the drag begins, we need to grab a reference to the
// parent container so we can return it if the drop
// is rejected
Container parent = getComponent().getParent();
setParent(parent);
// Remove the panel from the parent. If we don't do this, it
// can cause serialization issues. We could over come this
// by allowing the drop target to remove the component, but that's
// an argument for another day
parent.remove(getComponent());
// Update the display
parent.invalidate();
parent.repaint();
// Create our transferable wrapper
Transferable transferable = new ComponentTransferable(getComponent());
// Start the "drag" process...
DragSource ds = dge.getDragSource();
ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), transferable, this);
DragDropManager.INSTANCE.uninstallDrag(getComponent());
}
@Override
public void dragEnter(DragSourceDragEvent dsde) {
}
@Override
public void dragOver(DragSourceDragEvent dsde) {
}
@Override
public void dropActionChanged(DragSourceDragEvent dsde) {
}
@Override
public void dragExit(DragSourceEvent dse) {
}
@Override
public void dragDropEnd(DragSourceDropEvent dsde) {
// If the drop was not sucessful, we need to
// return the component back to it's previous
// parent
if (!dsde.getDropSuccess()) {
getParent().add(getComponent());
getParent().invalidate();
getParent().repaint();
}
}
}
public class DropHandler implements DropTargetListener {
private JComponent spacer = new JPanel();
public DropHandler() {
spacer.setBackground(Color.RED);
}
@Override
public void dragEnter(DropTargetDragEvent dtde) {
// Determine if can actual process the contents comming in.
// You could try and inspect the transferable as well, but
// There is an issue on the MacOS under some circumstances
// where it does not actually bundle the data until you accept the
// drop.
if (dtde.isDataFlavorSupported(ComponentDataFlavor.SHARED_INSTANCE)) {
dtde.acceptDrag(DnDConstants.ACTION_MOVE);
} else {
dtde.rejectDrag();
}
}
@Override
public void dragOver(DropTargetDragEvent dtde) {
if (dtde.isDataFlavorSupported(ComponentDataFlavor.SHARED_INSTANCE)) {
Point p = dtde.getLocation();
DropTargetContext dtc = dtde.getDropTargetContext();
Container parent = (Container) dtc.getComponent();
Component target = parent.getComponentAt(p);
int insertPoint = Math.max(0, parent.getComponentZOrder(target));
if (spacer.getParent() == null) {
parent.add(spacer, insertPoint);
} else {
parent.setComponentZOrder(spacer, insertPoint);
}
parent.revalidate();
parent.repaint();
Point pic = SwingUtilities.convertPoint(spacer, p, target);
Rectangle bounds = spacer.getBounds();
bounds.setLocation(pic);
((JComponent) parent).scrollRectToVisible(bounds);
}
}
@Override
public void dropActionChanged(DropTargetDragEvent dtde) {
}
@Override
public void dragExit(DropTargetEvent dte) {
Container parent = (Container) dte.getDropTargetContext().getComponent();
parent.remove(spacer);
parent.revalidate();
parent.repaint();
}
@Override
public void drop(DropTargetDropEvent dtde) {
boolean success = false;
// Basically, we want to unwrap the present...
if (dtde.isDataFlavorSupported(ComponentDataFlavor.SHARED_INSTANCE)) {
Transferable transferable = dtde.getTransferable();
try {
Object data = transferable.getTransferData(ComponentDataFlavor.SHARED_INSTANCE);
if (data instanceof Component) {
Component target = (Component) data;
DropTargetContext dtc = dtde.getDropTargetContext();
Component component = dtc.getComponent();
if (component instanceof JComponent) {
Container parent = target.getParent();
if (parent != null) {
parent.remove(target);
}
parent = (Container) component;
Point p = dtde.getLocation();
Component before = parent.getComponentAt(p);
int insertPoint = Math.max(0, parent.getComponentZOrder(before));
parent.remove(spacer);
System.out.println(insertPoint);
parent.add(target, insertPoint);
parent.revalidate();
parent.repaint();
DragDropManager.INSTANCE.installDrag(target);
success = true;
dtde.acceptDrop(DnDConstants.ACTION_MOVE);
invalidate();
repaint();
} else {
success = false;
dtde.rejectDrop();
}
} else {
success = false;
dtde.rejectDrop();
}
} catch (Exception exp) {
success = false;
dtde.rejectDrop();
exp.printStackTrace();
}
} else {
success = false;
dtde.rejectDrop();
}
dtde.dropComplete(success);
}
}
}
我在您的 DragListener 代码中添加了 hack。基本上它会在您拖动时删除布局管理器,因此重新验证什么都不做,并在释放鼠标时恢复布局管理器:
import java.awt.*;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class DragListener extends MouseInputAdapter
{
private Point location;
private MouseEvent pressed;
private MouseEvent dragged;
private MouseEvent dropped;
private LayoutManager layout;
@Override
public void mousePressed(MouseEvent me)
{
pressed = me;
Component component = me.getComponent();
Container parent = component.getParent();
parent.setPreferredSize(parent.getPreferredSize());
layout = parent.getLayout();
parent.setLayout(null);
}
@Override
public void mouseDragged(MouseEvent me)
{
dragged = me;
Component component = dragged.getComponent();
Container parent = component.getParent();
Container superParent = parent.getParent();
if(superParent instanceof JViewport)
{
JViewport vp = (JViewport)superParent;
Rectangle vpb = vp.getBounds();
Point pt = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(pt, vp);
if(!vpb.contains(pt))
{
int yDiff = (pt.y < vpb.y ) ? pt.y : pt.y - vpb.height;
vpb.translate(0, yDiff);
vp.scrollRectToVisible(vpb);
}
}
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
// Mouse release omitted
@Override
public void mouseReleased(MouseEvent me)
{
Component component = me.getComponent();
Container parent = component.getParent();
parent.setPreferredSize( null );
parent.setLayout(layout);
parent.validate();
parent.repaint();
}
}
当然,我假设您的真实 mouseReleased 代码具有将按钮插入容器中适当位置的逻辑,这样它的真实位置可以由 GridLayout 维护,否则组件将返回到其原始位置。
编辑:
这是一个在释放鼠标按钮时将按钮移动到新位置的版本。有点复杂,因为您需要担心 ZOrder。那就是把一个组件往下拉就可以了。但是,如果您尝试向上拖动一个组件,那么它会被绘制在其他按钮下方。临时重置 ZOrder 可以解决此问题。
天哪,代码开始变成一个大 hack:) 临时空布局和临时 ZOrder。
无论如何这是代码:
import java.awt.*;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class DragListener extends MouseInputAdapter
{
private Point location;
private MouseEvent pressed;
private MouseEvent dragged;
private MouseEvent dropped;
private LayoutManager layout;
private Rectangle originalBounds;
private int originalZOrder;
@Override
public void mousePressed(MouseEvent me)
{
pressed = me;
Component component = me.getComponent();
Container parent = component.getParent();
originalBounds = component.getBounds();
originalZOrder = parent.getComponentZOrder(component);
parent.setPreferredSize(parent.getPreferredSize());
layout = parent.getLayout();
parent.setLayout(null);
parent.setComponentZOrder(component, 0);
}
@Override
public void mouseDragged(MouseEvent me)
{
JComponent source = (JComponent) me.getComponent();
JComponent parent = (JComponent) source.getParent();
Point p = me.getPoint();
p = SwingUtilities.convertPoint(source, p, parent);
Rectangle bounds = source.getBounds();
bounds.setLocation(p);
bounds.x -= pressed.getX();
bounds.y -= pressed.getY();
source.setLocation(0, bounds.y);
parent.scrollRectToVisible(bounds);
}
@Override
public void mouseReleased(MouseEvent me)
{
boolean moved = false;
Component component = me.getComponent();
Container parent = component.getParent();
Point location = component.getLocation();
if (location.y < 0)
{
parent.add(component, 0);
moved = true;
}
else
{
for (int i = 0; i < parent.getComponentCount(); i++)
{
Component c = parent.getComponent(i);
Rectangle bounds = c.getBounds();
if (c == component)
bounds = originalBounds;
// Component is released in the space originally occupied
// by the component or over an existing component
if (bounds.contains(0, location.y))
{
if (c == component)
{
parent.setComponentZOrder(component, originalZOrder);
}
else
{
parent.add(component, i);
}
moved = true;
break;
}
}
}
// Component is positioned below all components in the container
if (!moved)
{
parent.add(component, parent.getComponentCount() - 1);
}
// Restore layout manager
parent.setPreferredSize( null );
parent.setLayout(layout);
parent.validate();
parent.repaint();
component.requestFocusInWindow();
}
private static void createAndShowGUI()
{
JPanel panel = new JPanel( new GridLayout(0, 1) );
DragListener drag = new DragListener();
for (int i = 0; i <10; i++)
{
JButton button = new JButton("" + i);
button.setFont(new java.awt.Font("Tahoma", 1, 48));
button.setForeground(new java.awt.Color(255, 0, 0));
button.addMouseListener(drag);
button.addMouseMotionListener(drag);
panel.add( button );
}
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane(panel) );
frame.setLocationByPlatform( true );
frame.setSize(200, 400);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}