java - 在 JScrollPane 中拖动 JTree 节点时如何加快滚动速度

标签 java swing drag-and-drop jscrollpane jtree

我有一个 JTreeJScrollPane . JTree 相当长,因此将节点从树的顶部拖到底部需要一些时间。拖动节点时,JScrollPane 会滚​​动,但速度不如使用鼠标滚轮滚动快。实现 setUnitIncrement正如所选答案中的建议 here使鼠标滚轮滚动速度更快,但不会改变拖动节点的速度。执行setBlockIncrement时也是如此.拖动节点时的滚动速度与我按住向上或向下箭头并以这种方式遍历 JTree 大致相同。

如何加快拖动节点的速度?

更新 1:

这是应要求提供的 SSCCE。我从 here 中窃取了大部分代码因为它很好地说明了我遇到的问题。只需将一个节点拖到滚动 Pane 中不可见的树的一部分,您就会看到滚动有多慢。这就是我想要加速的。

package example;

import java.awt.datatransfer.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;

public class Example {

    private JScrollPane getContent() {
        ArrayList<String> arrayList = new ArrayList<String>();
        for( int i = 0; i < 3000; i++ ) {
            arrayList.add( String.format( "Node %d", i ) );
        }
        DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Root" );
        for( String s : arrayList ) {
            root.add( new DefaultMutableTreeNode( s ) );
        }
        JTree tree = new JTree( root );
        tree.setDragEnabled( true );
        tree.setDropMode( DropMode.ON_OR_INSERT );
        tree.setTransferHandler( new TreeTransferHandler() );
        tree.getSelectionModel().setSelectionMode( TreeSelectionModel.CONTIGUOUS_TREE_SELECTION );
        expandTree( tree );
        return new JScrollPane( tree );
    }

    private void expandTree( JTree tree ) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
        Enumeration e = root.breadthFirstEnumeration();
        while( e.hasMoreElements() ) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.nextElement();
            if( node.isLeaf() ) {
                continue;
            }
            int row = tree.getRowForPath( new TreePath( node.getPath() ) );
            tree.expandRow( row );
        }
    }

    public static void main( String[] args ) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        f.add( new Example().getContent() );
        f.setSize( 400, 400 );
        f.setLocation( 200, 200 );
        f.setVisible( true );
    }
}

class TreeTransferHandler extends TransferHandler {

    DataFlavor nodesFlavor;
    DataFlavor[] flavors = new DataFlavor[1];
    DefaultMutableTreeNode[] nodesToRemove;

    public TreeTransferHandler() {
        try {
            String mimeType = DataFlavor.javaJVMLocalObjectMimeType
                    + ";class=\""
                    + javax.swing.tree.DefaultMutableTreeNode[].class.getName()
                    + "\"";
            nodesFlavor = new DataFlavor( mimeType );
            flavors[0] = nodesFlavor;
        } catch( ClassNotFoundException e ) {
            System.out.println( "ClassNotFound: " + e.getMessage() );
        }
    }

    public boolean canImport( TransferHandler.TransferSupport support ) {
        if( !support.isDrop() ) {
            return false;
        }
        support.setShowDropLocation( true );
        if( !support.isDataFlavorSupported( nodesFlavor ) ) {
            return false;
        }
        // Do not allow a drop on the drag source selections.
        JTree.DropLocation dl = (JTree.DropLocation)support.getDropLocation();
        JTree tree = (JTree)support.getComponent();
        int dropRow = tree.getRowForPath( dl.getPath() );
        int[] selRows = tree.getSelectionRows();
        for( int i = 0; i < selRows.length; i++ ) {
            if( selRows[i] == dropRow ) {
                return false;
            }
        }
        // Do not allow MOVE-action drops if a non-leaf node is
        // selected unless all of its children are also selected.
        int action = support.getDropAction();
        if( action == MOVE ) {
            return haveCompleteNode( tree );
        }
        // Do not allow a non-leaf node to be copied to a level
        // which is less than its source level.
        TreePath dest = dl.getPath();
        DefaultMutableTreeNode target = (DefaultMutableTreeNode)dest.getLastPathComponent();
        TreePath path = tree.getPathForRow( selRows[0] );
        DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode)path.getLastPathComponent();
        if( firstNode.getChildCount() > 0 && target.getLevel() < firstNode.getLevel() ) {
            return false;
        }
        return true;
    }

    private boolean haveCompleteNode( JTree tree ) {
        int[] selRows = tree.getSelectionRows();
        TreePath path = tree.getPathForRow( selRows[0] );
        DefaultMutableTreeNode first = (DefaultMutableTreeNode)path.getLastPathComponent();
        int childCount = first.getChildCount();
        // first has children and no children are selected.
        if( childCount > 0 && selRows.length == 1 ) {
            return false;
        }
        // first may have children.
        for( int i = 1; i < selRows.length; i++ ) {
            path = tree.getPathForRow( selRows[i] );
            DefaultMutableTreeNode next = (DefaultMutableTreeNode)path.getLastPathComponent();
            if( first.isNodeChild( next ) ) {
                // Found a child of first.
                if( childCount > selRows.length - 1 ) {
                    // Not all children of first are selected.
                    return false;
                }
            }
        }
        return true;
    }

    protected Transferable createTransferable( JComponent c ) {
        JTree tree = (JTree)c;
        TreePath[] paths = tree.getSelectionPaths();
        if( paths != null ) {
            // Make up a node array of copies for transfer and
            // another for/of the nodes that will be removed in
            // exportDone after a successful drop.
            List<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>();
            List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>();
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)paths[0].getLastPathComponent();
            DefaultMutableTreeNode copy = copy( node );
            copies.add( copy );
            toRemove.add( node );
            for( int i = 1; i < paths.length; i++ ) {
                DefaultMutableTreeNode next = (DefaultMutableTreeNode)paths[i].getLastPathComponent();
                // Do not allow higher level nodes to be added to list.
                if( next.getLevel() < node.getLevel() ) {
                    break;
                } else if( next.getLevel() > node.getLevel() ) {  // child node
                    copy.add( copy( next ) );
                    // node already contains child
                } else {                                        // sibling
                    copies.add( copy( next ) );
                    toRemove.add( next );
                }
            }
            DefaultMutableTreeNode[] nodes = copies.toArray( new DefaultMutableTreeNode[copies.size()] );
            nodesToRemove = toRemove.toArray( new DefaultMutableTreeNode[toRemove.size()] );
            return new NodesTransferable( nodes );
        }
        return null;
    }

    /**
     * Defensive copy used in createTransferable.
     */
    private DefaultMutableTreeNode copy( TreeNode node ) {
        return new DefaultMutableTreeNode( node );
    }

    protected void exportDone( JComponent source, Transferable data, int action ) {
        if( ( action & MOVE ) == MOVE ) {
            JTree tree = (JTree)source;
            DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
            // Remove nodes saved in nodesToRemove in createTransferable.
            for( int i = 0; i < nodesToRemove.length; i++ ) {
                model.removeNodeFromParent( nodesToRemove[i] );
            }
        }
    }

    public int getSourceActions( JComponent c ) {
        return COPY_OR_MOVE;
    }

    public boolean importData( TransferHandler.TransferSupport support ) {
        if( !canImport( support ) ) {
            return false;
        }
        // Extract transfer data.
        DefaultMutableTreeNode[] nodes = null;
        try {
            Transferable t = support.getTransferable();
            nodes = (DefaultMutableTreeNode[])t.getTransferData( nodesFlavor );
        } catch( UnsupportedFlavorException ufe ) {
            System.out.println( "UnsupportedFlavor: " + ufe.getMessage() );
        } catch( java.io.IOException ioe ) {
            System.out.println( "I/O error: " + ioe.getMessage() );
        }
        // Get drop location info.
        JTree.DropLocation dl = (JTree.DropLocation)support.getDropLocation();
        int childIndex = dl.getChildIndex();
        TreePath dest = dl.getPath();
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode)dest.getLastPathComponent();
        JTree tree = (JTree)support.getComponent();
        DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
        // Configure for drop mode.
        int index = childIndex;    // DropMode.INSERT
        if( childIndex == -1 ) {     // DropMode.ON
            index = parent.getChildCount();
        }
        // Add data to model.
        for( int i = 0; i < nodes.length; i++ ) {
            model.insertNodeInto( nodes[i], parent, index++ );
        }
        return true;
    }

    public String toString() {
        return getClass().getName();
    }

    public class NodesTransferable implements Transferable {

        DefaultMutableTreeNode[] nodes;

        public NodesTransferable( DefaultMutableTreeNode[] nodes ) {
            this.nodes = nodes;
        }

        public Object getTransferData( DataFlavor flavor )
                throws UnsupportedFlavorException {
            if( !isDataFlavorSupported( flavor ) ) {
                throw new UnsupportedFlavorException( flavor );
            }
            return nodes;
        }

        public DataFlavor[] getTransferDataFlavors() {
            return flavors;
        }

        public boolean isDataFlavorSupported( DataFlavor flavor ) {
            return nodesFlavor.equals( flavor );
        }
    }
}

成功!

我终于能够在拖动时加快滚动速度。事实上,我还可以根据光标离树的顶部或底部的距离来调整滚动速度。最终比我做的要简单得多。甚至不需要听众。只需比较这两个代码示例即可查看我添加的内容。

package example;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;

public class Example {

    private JScrollPane getContent() {
        ArrayList<String> arrayList = new ArrayList<String>();
        for( int i = 0; i < 3000; i++ ) {
            arrayList.add( String.format( "Node %d", i ) );
        }
        DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Root" );
        for( String s : arrayList ) {
            root.add( new DefaultMutableTreeNode( s ) );
        }
        JTree tree = new JTree( root );
        tree.setDragEnabled( true );
        tree.setDropMode( DropMode.ON_OR_INSERT );
        tree.setTransferHandler( new TreeTransferHandler() );
        tree.getSelectionModel().setSelectionMode( TreeSelectionModel.CONTIGUOUS_TREE_SELECTION );
        expandTree( tree );
        return new JScrollPane( tree );
    }

    private void expandTree( JTree tree ) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
        Enumeration e = root.breadthFirstEnumeration();
        while( e.hasMoreElements() ) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.nextElement();
            if( node.isLeaf() ) {
                continue;
            }
            int row = tree.getRowForPath( new TreePath( node.getPath() ) );
            tree.expandRow( row );
        }
    }

    public static void main( String[] args ) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        f.add( new Example().getContent() );
        f.setSize( 400, 400 );
        f.setLocation( 200, 200 );
        f.setVisible( true );
    }
}

class TreeTransferHandler extends TransferHandler {

    DataFlavor nodesFlavor;
    DataFlavor[] flavors = new DataFlavor[1];
    DefaultMutableTreeNode[] nodesToRemove;

    public TreeTransferHandler() {
        try {
            String mimeType = DataFlavor.javaJVMLocalObjectMimeType
                    + ";class=\""
                    + javax.swing.tree.DefaultMutableTreeNode[].class.getName()
                    + "\"";
            nodesFlavor = new DataFlavor( mimeType );
            flavors[0] = nodesFlavor;
        } catch( ClassNotFoundException e ) {
            System.out.println( "ClassNotFound: " + e.getMessage() );
        }
    }

    public boolean canImport( TransferHandler.TransferSupport support ) {
        if( !support.isDrop() ) {
            return false;
        }

        boolean isScrolling = false;
        JTree tree = (JTree)support.getComponent();
        JViewport vp = (JViewport)tree.getParent();
        Point vpMousePosition = vp.getMousePosition();
        Rectangle treeVisibleRectangle = tree.getVisibleRect();

        // Don't attempt scroll if mouse isn't over tree
        if( vpMousePosition != null ) {
            Integer newY = null;

            // Make sure we aren't already scrolled all the way down
            if( tree.getHeight() - treeVisibleRectangle.y != vp.getHeight() ) {
                /*
                 * Get Y coordinate for scrolling down
                 */
                if( vp.getHeight() - vpMousePosition.y < 10 ) {
                    newY = treeVisibleRectangle.y + 500;
                } else if( vp.getHeight() - vpMousePosition.y < 20 ) {
                    newY = treeVisibleRectangle.y + 400;
                } else if( vp.getHeight() - vpMousePosition.y < 30 ) {
                    newY = treeVisibleRectangle.y + 300;
                } else if( vp.getHeight() - vpMousePosition.y < 40 ) {
                    newY = treeVisibleRectangle.y + 200;
                } else if( vp.getHeight() - vpMousePosition.y < 50 ) {
                    newY = treeVisibleRectangle.y + 100;
                } else if( vp.getHeight() - vpMousePosition.y < 60 ) {
                    newY = treeVisibleRectangle.y + 50;
                } else if( vp.getHeight() - vpMousePosition.y < 70 ) {
                    newY = treeVisibleRectangle.y + 25;
                } else if( vp.getHeight() - vpMousePosition.y < 80 ) {
                    newY = treeVisibleRectangle.y + 10;
                }
            }

            // Make sure we aren't already scrolled all the way up
            if( newY == null && treeVisibleRectangle.y != 0 ) {
                /*
                 * Get Y coordinate for scrolling up
                 */
                if( 10 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 500;
                } else if( 20 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 400;
                } else if( 30 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 300;
                } else if( 40 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 200;
                } else if( 50 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 100;
                } else if( 60 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 50;
                } else if( 70 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 25;
                } else if( 80 > vpMousePosition.y ) {
                    newY = treeVisibleRectangle.y - 10;
                }
            }

            // Do the scroll
            if( newY != null  ) {
                Rectangle treeNewVisibleRectangle = new Rectangle( treeVisibleRectangle.x, newY, treeVisibleRectangle.width, treeVisibleRectangle.height );
                tree.scrollRectToVisible( treeNewVisibleRectangle );
                isScrolling = true;
            }
        }

        if( isScrolling ) {
            return false;
        }

        support.setShowDropLocation( true );
        if( !support.isDataFlavorSupported( nodesFlavor ) ) {
            return false;
        }
        // Do not allow a drop on the drag source selections.
        JTree.DropLocation dl = (JTree.DropLocation)support.getDropLocation();
        int dropRow = tree.getRowForPath( dl.getPath() );
        int[] selRows = tree.getSelectionRows();
        for( int i = 0; i < selRows.length; i++ ) {
            if( selRows[i] == dropRow ) {
                return false;
            }
        }
        // Do not allow MOVE-action drops if a non-leaf node is
        // selected unless all of its children are also selected.
        int action = support.getDropAction();
        if( action == MOVE ) {
            return haveCompleteNode( tree );
        }
        // Do not allow a non-leaf node to be copied to a level
        // which is less than its source level.
        TreePath dest = dl.getPath();
        DefaultMutableTreeNode target = (DefaultMutableTreeNode)dest.getLastPathComponent();
        TreePath path = tree.getPathForRow( selRows[0] );
        DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode)path.getLastPathComponent();
        if( firstNode.getChildCount() > 0 && target.getLevel() < firstNode.getLevel() ) {
            return false;
        }
        return true;
    }

    private boolean haveCompleteNode( JTree tree ) {
        int[] selRows = tree.getSelectionRows();
        TreePath path = tree.getPathForRow( selRows[0] );
        DefaultMutableTreeNode first = (DefaultMutableTreeNode)path.getLastPathComponent();
        int childCount = first.getChildCount();
        // first has children and no children are selected.
        if( childCount > 0 && selRows.length == 1 ) {
            return false;
        }
        // first may have children.
        for( int i = 1; i < selRows.length; i++ ) {
            path = tree.getPathForRow( selRows[i] );
            DefaultMutableTreeNode next = (DefaultMutableTreeNode)path.getLastPathComponent();
            if( first.isNodeChild( next ) ) {
                // Found a child of first.
                if( childCount > selRows.length - 1 ) {
                    // Not all children of first are selected.
                    return false;
                }
            }
        }
        return true;
    }

    protected Transferable createTransferable( JComponent c ) {
        JTree tree = (JTree)c;
        TreePath[] paths = tree.getSelectionPaths();
        if( paths != null ) {
            // Make up a node array of copies for transfer and
            // another for/of the nodes that will be removed in
            // exportDone after a successful drop.
            List<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>();
            List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>();
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)paths[0].getLastPathComponent();
            DefaultMutableTreeNode copy = copy( node );
            copies.add( copy );
            toRemove.add( node );
            for( int i = 1; i < paths.length; i++ ) {
                DefaultMutableTreeNode next = (DefaultMutableTreeNode)paths[i].getLastPathComponent();
                // Do not allow higher level nodes to be added to list.
                if( next.getLevel() < node.getLevel() ) {
                    break;
                } else if( next.getLevel() > node.getLevel() ) {  // child node
                    copy.add( copy( next ) );
                    // node already contains child
                } else {                                        // sibling
                    copies.add( copy( next ) );
                    toRemove.add( next );
                }
            }
            DefaultMutableTreeNode[] nodes = copies.toArray( new DefaultMutableTreeNode[copies.size()] );
            nodesToRemove = toRemove.toArray( new DefaultMutableTreeNode[toRemove.size()] );
            return new NodesTransferable( nodes );
        }
        return null;
    }

    /**
     * Defensive copy used in createTransferable.
     */
    private DefaultMutableTreeNode copy( TreeNode node ) {
        return new DefaultMutableTreeNode( node );
    }

    protected void exportDone( JComponent source, Transferable data, int action ) {
        if( ( action & MOVE ) == MOVE ) {
            JTree tree = (JTree)source;
            DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
            // Remove nodes saved in nodesToRemove in createTransferable.
            for( int i = 0; i < nodesToRemove.length; i++ ) {
                model.removeNodeFromParent( nodesToRemove[i] );
            }
        }
    }

    public int getSourceActions( JComponent c ) {
        return COPY_OR_MOVE;
    }

    public boolean importData( TransferHandler.TransferSupport support ) {
        if( !canImport( support ) ) {
            return false;
        }
        // Extract transfer data.
        DefaultMutableTreeNode[] nodes = null;
        try {
            Transferable t = support.getTransferable();
            nodes = (DefaultMutableTreeNode[])t.getTransferData( nodesFlavor );
        } catch( UnsupportedFlavorException ufe ) {
            System.out.println( "UnsupportedFlavor: " + ufe.getMessage() );
        } catch( java.io.IOException ioe ) {
            System.out.println( "I/O error: " + ioe.getMessage() );
        }
        // Get drop location info.
        JTree.DropLocation dl = (JTree.DropLocation)support.getDropLocation();
        int childIndex = dl.getChildIndex();
        TreePath dest = dl.getPath();
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode)dest.getLastPathComponent();
        JTree tree = (JTree)support.getComponent();
        DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
        // Configure for drop mode.
        int index = childIndex;    // DropMode.INSERT
        if( childIndex == -1 ) {     // DropMode.ON
            index = parent.getChildCount();
        }
        // Add data to model.
        for( int i = 0; i < nodes.length; i++ ) {
            model.insertNodeInto( nodes[i], parent, index++ );
        }
        return true;
    }

    public String toString() {
        return getClass().getName();
    }

    public class NodesTransferable implements Transferable {

        DefaultMutableTreeNode[] nodes;

        public NodesTransferable( DefaultMutableTreeNode[] nodes ) {
            this.nodes = nodes;
        }

        public Object getTransferData( DataFlavor flavor )
                throws UnsupportedFlavorException {
            if( !isDataFlavorSupported( flavor ) ) {
                throw new UnsupportedFlavorException( flavor );
            }
            return nodes;
        }

        public DataFlavor[] getTransferDataFlavors() {
            return flavors;
        }

        public boolean isDataFlavorSupported( DataFlavor flavor ) {
            return nodesFlavor.equals( flavor );
        }
    }
}

最佳答案

当它不能很好地与 SDK 一起工作时,我开始使用我的 hackish 方法,在这种情况下它是一种:听鼠标。当鼠标向下移动 10 个像素时,当鼠标在同一方向上移动 100 个像素时会产生一个事件。在 Windows 中,在 Excel 中,如果您将鼠标拖到任务栏下方,它将滚动。类似的东西应该在这里,值得一试。

关于java - 在 JScrollPane 中拖动 JTree 节点时如何加快滚动速度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14485732/

相关文章:

java - 如何从 Java SWT 中的树中删除 TreeItem?

java - 如何为隐式转换提供附加参数

java - Jdeveloper 12 支持创建 Swing 应用程序吗?

Java: getSelectedItem() 对于字符串类型未定义

ios - nstableview 拖放自定义单元格 View

java - 将 JsonNode 转换为 java 数组

java - 一个类如何扩展 Thread 和 Gui?

java - 无法在 JTable 中返回 Double 值

javascript - 使用哪个拖放? jQuery draggable droppable 还是 HTML5 native drag-and-drop?或者其他?

delphi - 使用对话框时无法使 DragAcceptFiles 工作