2013-01-23 38 views
5

Tôi có một JTree trong một JScrollPane. Các JTree là khá dài vì vậy phải mất một thời gian để kéo một nút từ trên cùng của cây xuống phía dưới. Khi kéo một nút, cuộn JScrollPane, nhưng không nhanh bằng cách cuộn bằng cách sử dụng con lăn chuột. Thực hiện setUnitIncrement như được đề xuất trong câu trả lời được chọn here làm cho tốc độ cuộn bánh xe chuột thậm chí còn nhanh hơn, nhưng không thay đổi tốc độ kéo nút. Điều này cũng đúng khi thực hiện setBlockIncrement. Tốc độ cuộn khi kéo một nút giống như khi tôi giữ mũi tên lên hoặc xuống và di chuyển ngang qua JTree theo cách đó.Cách tăng tốc độ cuộn khi kéo một nút JTree bên trong JScrollPane

Làm cách nào để tăng tốc độ kéo nút?

UPDATE 1:

Dưới đây là một SSCCE theo yêu cầu. Hầu hết mã này tôi đã tách khỏi here vì nó minh họa tốt vấn đề tôi đang gặp phải. Chỉ cần kéo một nút đến một phần của cây không hiển thị trong ngăn cuộn và bạn sẽ thấy cách cuộn chậm. Đây là những gì tôi muốn tăng tốc.

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

THÀNH CÔNG!

Cuối cùng tôi đã có thể tăng tốc độ cuộn khi kéo. Trong thực tế, tôi cũng có thể tạo biến tốc độ cuộn dựa trên cách đóng con trỏ lên trên cùng hoặc dưới cùng của cây. Kết thúc được đơn giản hơn rất nhiều so với tôi đã làm cho nó. Thậm chí không cần một người nghe. Chỉ cần phân biệt hai ví dụ mã này để xem những gì tôi đã thêm vào.

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); 
     } 
    } 
} 
+0

Vui lòng chỉnh sửa câu hỏi của bạn để bao gồm [sscce] (http://sscce.org/) làm rõ khái niệm _rather long_. – trashgod

+0

@trashgod đã thêm SSCCE theo yêu cầu. – ubiquibacon

+0

+1 [sscce] (http://sscce.org/) của bạn làm cho nó rõ ràng rằng di chuyển thường xuyên stutters để ngăn chặn như là một mục tiêu thả mới tự động cuộn vào xem. Xin lỗi, tôi không biết một công việc xung quanh. Được cắt và dán một lựa chọn hợp lý? – trashgod

Trả lời

2

Khi không hoạt động tốt với SDK, tôi bắt đầu phương pháp tấn công, trong trường hợp này, đó là một loại: nghe chuột. Khi chuột di chuyển xuống dưới 10 pixel, hơn là tạo một sự kiện khi chuột di chuyển 100 pixel theo cùng một hướng. Trong Windows, trong Excel nếu bạn kéo dưới thanh tác vụ, nó sẽ di chuyển lên trên. Một cái gì đó tương tự nên ở đây, nó có giá trị một thử.

+1

Tôi đã đưa ra đề xuất của bạn, nhưng tôi không thể làm mọi thứ hoạt động chính xác bằng cách sử dụng người nghe. Người nghe chuột của Java không chơi tốt với các phương pháp DnD. Ví dụ, tôi đã không thể phát hiện một sự kiện chuột lên trong khi một hoạt động DnD đang diễn ra. Mã tôi thêm vào câu hỏi của tôi không di chuyển xuống cây dựa trên vị trí chuột như bạn đề xuất mặc dù, vì vậy tôi sẽ cung cấp cho một trong những ya :) – ubiquibacon

Các vấn đề liên quan