我在JScrollPane中有一个JTree。它相当长,因此将节点从树的顶部拖到底部需要一段时间。拖动节点时会滚动,但速度不如使用鼠标滚轮滚动的快。按照此处所选答案中的建议实施setUnitIncrement可使鼠标滚轮滚动速度更快,但不会改变拖动节点的速度。实现setBlockIncrement时也是如此。拖动节点时的滚动速度与我按住向上或向下箭头并以这种方式遍历的速度大致相同。JTree
JScrollPane
JTree
如何加快拖动节点的速度?
更新 1:
这是应要求提供的 SSCCE。我从这里撕掉了大部分代码,因为它很好地说明了我遇到的问题。只需将一个节点拖到滚动窗格中不可见的树的一部分,您就会看到滚动有多慢。这就是我想要加速的。
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 );
}
}
}