使用mouseDrag滚动时暂时禁用或阻止重新绘制JViewPort

我编写了一个如下定义的MouseListener ,以便我可以移动JButton来重新排序JPanel的组件。 JPanel位于JScrollPane因此当添加多个组件时,可以滚动它们。

我遇到的问题是,当拖动组件并且鼠标离开滚动窗格/视口时,组件将快照回到它在JPanel位置,然后将在正确的位置绘制。 我认为这种行为是由于当我调用scrollRectToVisible()时,Viewport调用其子项的重绘

有没有办法可以防止这种情况发生?

请注意,我仅限于Java 5

倾听者

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

我在你的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(); } } 

当然我假设您的真正鼠标释放代码将具有将按钮插入Container中的适当位置的逻辑,因此其实际位置可以由GridLayout维护,否则该组件将返回其原始位置。

编辑:

这是一个在释放鼠标按钮时将按钮移动到新位置的版本。 有点复杂,因为你需要担心ZOrder。 那就是拖动一个组件就可以了。 但是如果你试图向上拖动一个组件,那么它会被绘制在其他按钮下方。 暂时重置ZOrder可以解决此问题。

男孩的代码开始是一个大黑客:)临时空布局和临时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(); } }); } } 

你有两个核心问题,第一个是你正在尝试对抗布局管理器这一事实,它将在组件失效时重新布局组件,第二个是你真正,奇怪的方式进行拖动过程。

当视口的可视区域发生更改时,组件将重新生效,这会导致重新计算组件的位置,从而使其暂时返回到其初始位置。

在这个时刻你有一个选择,就是没有它

 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和/或Drag’n’Drop API,基本上从组件的当前容器中删除组件,并根据组件层次结构在组件层次结构中的不同位置重新添加组件地点。 这允许您继续使用布局管理器;)

举个例子,看看Java – 如何拖放JPanel及其组件

更新了DnD示例

DragNDrop

好吧,所以这个例子借鉴了Java – 如何拖放JPanel及其组件 ,但允许你在删除组件时“重新定位”组件。 作为一个额外的奖励,有一个很好的“指标”组件“应该”出现在哪里…

因为组件在“导出”时被序列化,所以这不会导致侦听器和DragGestureRecognizer出现问题。 为此,我实现了一个DragDropManager其唯一目的是在拖放过程中的某些点installuninstall DragGestureHandlerDragGestureRecognizer …这就是为什么我倾向于传输状态而不是组件: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 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); } } }