Jtree使用詳細教程

原帖地址: http://feipigzi.iteye.com/blog/969571


英文文檔地址: http://docs.oracle.com/javase/tutorial/uiswing/components/tree.html

如何使用 Jtree

(1)創建樹

(2)對節點的選擇做出響應

(3)自定義樹的外觀表現

(4)動態改變一棵樹

(5)創建樹的數據模型

(6)懶加載孩子

(7)如何寫expansion linstener

(8)如何寫tree-will-expand listener

 

利用 JTree 類,你可以顯示等級體系的數據。一個 JTree 對象並沒有包含實際的數據;它只是提供了數據的一個視圖。像其他非平凡的( nontrivial ) Swing 組件一樣,這種 Jtree 通過查詢她的數據模型獲得數據。這是一個 Jtree :


如上面的圖片所顯示, Jtree 垂直顯示它的數據。樹中顯示的每一行包含一項數據,稱之爲節點( node )。每顆樹有一個根節點( root node ),其他所有節點是它的子孫。默認情況下,樹只顯示根節點,但是你可以設置改變默認顯示方式。一個節點可以擁有孩子也可以不擁有任何子孫。我們稱那些可以擁有孩子(不管當前是否有孩子)的節點爲“分支節點”( branch nodes ),而不能擁有孩子的節點爲“葉子節點”( leaf nodes )。

分支節點可以有任意多個孩子。通常,用戶可以通過點擊實現展開或者摺疊分支節點,使得他們的孩子可見或者不可見。默認情況下,除了根節點以外的所有分支節點默認呈現摺疊狀態。程序中,通過監聽 tree expansion或者 tree-will-expand 事件可以檢測分支節點的展開狀態。監聽事件在下面兩節內容中描述 How to Write a Tree Expansion Listener and How to Write a Tree-Will-Expand Listener .

       在樹中,一個節點可以通過 TreePath (一個囊括該節點和他所有祖先節點的路徑對象)或者他的摺疊行來識別。

       展開節點( expanded node )就是一個非葉子節點,當他的所有祖先都展開時,他將顯示他的孩子。

       摺疊節點( collapsed node )是隱藏了孩子們得的節點。

       隱藏節點( hidden node )就是摺疊節點下的一個孩子

(1)創建一棵 Tree

       這裏是一個應用程序的截圖,上半部分展示了一個滾動面板( scroll pane )中的樹( Jtree )。



 接下來的代碼是從

http://download.oracle.com/javase/tutorial/uiswing/examples/components/TreeDemoProject/src/components/TreeDemo.java獲得,創建了一個JTree 對象,並將之放到一個scroll pane 上

//Where instance variables are declared:
private JTree tree;
...
public TreeDemo() {
    ...
    DefaultMutableTreeNode top =
        new DefaultMutableTreeNode("The Java Series");
    createNodes(top);
    tree = new JTree(top);
    ...
    JScrollPane treeView = new JScrollPane(tree);
    ...
}

  這段代碼創建了一個DefaultMutableTreeNode 實例作爲根節點。接着創建樹中剩下的其他節點。創建完節點後,通過指定剛纔創建的根節點爲JTree 構造函數的參數,創建一棵樹。最後,將樹放到滾動面板中,這是一個通常的策略,因爲需要顯示完一個樹,而展開樹需要另外比較大的空間。

    以下代碼創建根節點以下的節點

private void createNodes(DefaultMutableTreeNode top) {
    DefaultMutableTreeNode category = null;
    DefaultMutableTreeNode book = null;
    
    category = new DefaultMutableTreeNode("Books for Java Programmers");
    top.add(category);
    
    //original Tutorial
    book = new DefaultMutableTreeNode(new BookInfo
        ("The Java Tutorial: A Short Course on the Basics",
        "tutorial.html"));
    category.add(book);
    
    //Tutorial Continued
    book = new DefaultMutableTreeNode(new BookInfo
        ("The Java Tutorial Continued: The Rest of the JDK",
        "tutorialcont.html"));
    category.add(book);
    
    //JFC Swing Tutorial
    book = new DefaultMutableTreeNode(new BookInfo
        ("The JFC Swing Tutorial: A Guide to Constructing GUIs",
        "swingtutorial.html"));
    category.add(book);

    //...add more books for programmers...

    category = new DefaultMutableTreeNode("Books for Java Implementers");
    top.add(category);

    //VM
    book = new DefaultMutableTreeNode(new BookInfo
        ("The Java Virtual Machine Specification",
         "vm.html"));
    category.add(book);

    //Language Spec
    book = new DefaultMutableTreeNode(new BookInfo
        ("The Java Language Specification",
         "jls.html"));
    category.add(book);
}

DefaultMutableTreeNode 構造函數的參數是一個用戶自定義的類對象,它包含或指向了關聯樹節點的數據。這個用戶對象可以是一個字符串,或者是一個自定義的類。如果它實現了一個自定義對象,你應該要重新實現覆蓋他的 toString 方法,這樣他才能返回對應字符串作爲節點顯示的字符串。 Jtree 默認情況下,每個節點都是用toString 的返回值作爲顯示。所以,讓 toString 返回一些有意義的值是很重要的。有時候,覆蓋 toString 方法是不可行的;在某些場景你可以通過重寫 Jtree 的 convertValueToText 方法,映射模型對象到一個可顯示的字符串。

       例如,前面 demo 中的 BookInfo 類是一個自定義類,它包含了兩個字段:書名和描述該書本的 HTML 文件的URL 路徑。 toString 方法也重新實現,返回書名。從而,每個節點關聯了一個 BookInfo 對象,並且顯示書名。

       總之,你可以調用 Jtree 的構造函數創建一棵樹,指定一個實現了 TreeNode 的類作爲參數。你應該儘量把這棵樹放到一個滾動面板中( scroll pane ),這樣樹就不會佔用太大的空間。對於樹節點相應用戶點擊而展開和摺疊的功能,你不需要做任何事情。但是,你一定要添加一些代碼使得樹在用戶點擊選擇一個節點時能夠作出反應,例如:

(2)對節點的選擇作出響應

對於樹節點的選擇做出響應是簡單的。你可以實現一個樹節點選擇監聽器,並且註冊在這棵樹上。接下來的代碼顯示了 TreeDemo.java 中有關選擇的代碼:

//Where the tree is initialized:
  	 tree.getSelectionModel().setSelectionMode
            (TreeSelectionModel.SINGLE_TREE_SELECTION);
	
	//Listen for when the selection changes.
    tree.addTreeSelectionListener(this);
	...
	public void valueChanged(TreeSelectionEvent e) {
	//Returns the last path element of the selection.
	//This method is useful only when the selection model allows a single selection.
	    DefaultMutableTreeNode node = (DefaultMutableTreeNode)
	                       tree.getLastSelectedPathComponent();

 	   if (node == null)
 	   //Nothing is selected.	
 	   return;
	
    Object nodeInfo = node.getUserObject();
    if (node.isLeaf()) {
        BookInfo book = (BookInfo)nodeInfo;
        displayURL(book.bookURL);
    } else {
        displayURL(helpURL); 
    }
}

上面的代碼執行了一下任務:

       1 .獲得樹的默認 TreeSelectionModel (節點選擇模式),然後設置它,使得在某一時刻只有一個節點被選中。

       2 .註冊了一個事件處理器。事件處理器是一個實現了 TreeSelectionListener 接口的對象。

       3. 在事件處理器中,通過調用 Tree 的 getLastSelectedPathComponent 方法獲得選中的節點。

       4 .使用 getUserObject 方法獲得節點關聯的數據。(節點 node 是一個非平凡組件,要通過它關聯的數據模型獲得真正的數據)

 

這裏給出一些樹節點的圖片,分別通過 Java 、 Windows 和 MacOS 樣式繪得。

(依次爲 java look 、 windows look 和 MacOS look )

像之前圖片顯示一樣,一棵樹按照慣例,對於每個基點顯示了一個圖標和一些文字。像我們簡短的展示一樣,你可以指定這些樣式。

       一棵樹通常表現一些外觀和樣式特效,通過不同的繪製圖形指示節點間的關係。你可以在限制範圍內自定義這些圖形。首先,你可以使用 tree.setRootVisible(true) 設置顯示根節點或者 tree.setRootVisible(false) 隱藏根節點。其次,你可以使用 tree.setShowsRootHandles(true) 請求設置樹的頂層節點具有句柄( +- 圖標,點擊句柄使其展開摺疊)。如果頂層節點是根節點的話,需要保證它是可視的,如果是頂層節點則每個孩子都顯示句柄。

       如果你使用 Java 樣式,你可以自定是否在節點間顯示行線來表現他們的關係。默認情況下, Java 樣式使用“角線”(類似“ L ”)。通過設置 Jtree.lineStyle 的客戶端屬性,你可以指定一種不同的標準。例如,通過以下代碼,這隻 JAVA 樣式僅使用水平線隔開一組節點:

tree.putClientProperty(“Jtree.lineStyle”,  “Horizontal”);

指定 JAVA 樣式在節點間不顯示任何行線,則使用以下代碼:

tree.putClientProperty(“Jtree.lineStyle”,  “None”); 

(3)自定義樹的外觀表現

       接下來的一些截圖顯示了設置不同的 Jtree.lineStyle 屬性(使用 JAVA 樣式)

       

不管你使用那種樣式( java 、 windows 、 mac ) , 默認情況下,節點顯示的圖標決定於節點是否爲葉子節點和是否可展開。例如,在 windwos 樣式中,每個葉子節點的默認圖標是一個點;在 JAVA 樣式中,葉子節點默認圖標是一個類似白紙的符號。在所有樣式中,分支節點被一個文件夾符號所標識。不同樣式對於可展開分支和對應的可摺疊分支,可能有不同的圖標。

       你可以很容易的改變葉子節點、可展開分支節點和可摺疊分支節點的默認圖標。如果要這樣做的話,首先,你要創建一個 DefaultTreeCellRenderer 實例。你總是可以創建自己的 TreeCellRender ,讓你喜歡的任何組件重複利用。接着,通過調用以下一個或多個方法去指定圖標: setLeafIcon (對於葉子節點), setOpenIcon (對於可展開分支節點), setClosedIcon (對於可摺疊節點)。如果你想要這棵樹中各種節點都不顯示圖標,你就要指定圖標爲 null 。

一定你創建了這些圖標,使用樹的 setCellRender 方法去指定這個 DefaultTreeCellRender 來繪製它的節點。這裏有一個來自 TreeIconDemo 的例子

ImageIcon leafIcon = createImageIcon("images/middle.gif");
if (leafIcon != null) {
    DefaultTreeCellRenderer renderer = 
	new DefaultTreeCellRenderer();
    renderer.setLeafIcon(leafIcon);
    tree.setCellRenderer(renderer);
}

這是一個截圖:


    如果你想更精巧的控制節點圖標,或者你想提供一些工具,你可以創建 DefaultTreeCellRender 的子類,然後覆蓋他的getTreeCellRendererComponent 方法。因爲DefaultTreeCellRenderer 是Jlabel 的一個子類,你可以使用任何Jlabel 的方法,例如setIcon 。

       下面代碼來自 TreeIconDemo2.java ,創建了一個單元繪製器( cell renderer ),它根據節點的文本數據是否包含單詞“ Tutorial ”來改變了葉子節點的圖標。這個 renderer 同樣可以指定提示文本( tool-tip ) --- 鼠標移到上面,出現提示。

//...where the tree is initialized:
    //Enable tool tips.
    ToolTipManager.sharedInstance().registerComponent(tree);
    
    ImageIcon tutorialIcon = createImageIcon("images/middle.gif");
    if (tutorialIcon != null) {
        tree.setCellRenderer(new MyRenderer(tutorialIcon));
    }
...
class MyRenderer extends DefaultTreeCellRenderer {
    Icon tutorialIcon;

    public MyRenderer(Icon icon) {
        tutorialIcon = icon;
    }

    public Component getTreeCellRendererComponent(
                        JTree tree,
                        Object value,
                        boolean sel,
                        boolean expanded,
                        boolean leaf,
                        int row,
                        boolean hasFocus) {

        super.getTreeCellRendererComponent(
                        tree, value, sel,
                        expanded, leaf, row,
                        hasFocus);
        if (leaf && isTutorialBook(value)) {
            setIcon(tutorialIcon);
            setToolTipText("This book is in the Tutorial series.");
        } else {
            setToolTipText(null); //no tool tip
        } 

        return this;
    }

    protected boolean isTutorialBook(Object value) {
        DefaultMutableTreeNode node =
                (DefaultMutableTreeNode)value;
        BookInfo nodeInfo =
                (BookInfo)(node.getUserObject());
        String title = nodeInfo.bookName;
        if (title.indexOf("Tutorial") >= 0) {
            return true;
        }

        return false;
    }
}

下面是結果的截圖:


你可能會疑惑單元繪製器( cell renderer )是如何工作的。當一個 tree 在話每個節點的時候,不管是 Jtree 或是他的樣式表現都包含了繪製節點的代碼。 Tree 可以使用 cell renderer 的繪圖代碼代替前者去繪製節點。例如,畫一個包含字符串“ The Java Programming Language ”的葉子節點, tree 會要求 cell renderer 返回一個組件,該組件能夠繪製一個包含該字符串的葉子節點。如果這個 cell renderer 是一個 DefaultTreeCellRender ,它就返回一個 label (DefaultTreeCellRender 繼承於 Jlabel ),它繪製默認的葉子節點圖標,緊隨一段字符串。

         一個 cell renderer 僅繪製而不處理事件。如果你想要對一顆 tree 增加事件處理器,你需要在樹上註冊監聽器,如果事件緊發生在某個節點被選擇時,可以選擇註冊在 tree 的 cell editor 上。有關 cell editors 的資料可以參考Concepts: Editors and Renderers . 這節將討論 table cell editors 和 renderers ,他們類似於 tree cell editors 和renderers 。

(4)動態地改變一棵 Tree

接下來的圖片展示了一個叫 DynamicTreeDemo 的應用程序,它允許你從一顆可視 tree 中增加或者移除節點。你也可以編輯每個節點的文本。


這裏給出了樹初始化的代碼:

rootNode = new DefaultMutableTreeNode("Root Node");
treeModel = new DefaultTreeModel(rootNode);
treeModel.addTreeModelListener(new MyTreeModelListener());

tree = new JTree(treeModel);
tree.setEditable(true);
tree.getSelectionModel().setSelectionMode
     (TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setShowsRootHandles(true); 

      通過明確的創建 tree 的模型( model ),這段代碼保證 tree 的 model 是 DefaultTreeModel 的實例。這樣,我們知道所有 tree model 支持的方法。例如,我們可以調用 model 的 insertNodeInto 方法,幾時這個方法不是TreeModel 接口要求的。

       爲使得樹中節點的文本值可編輯,我們調用對 tree 調用 setEditable(true) 。當用戶完成一個節點的編輯時,這個 model 產生一個 tree model 事件,它會告訴所有監聽者(包括 Jtree ):樹節點被改變了。注意:儘管DefaultMutableTreeNode 擁有改變一個節點內容的方法,但是改變還是需要通過 DefaultTreeModel 上面的方法。否則, tree model 事件就不能產生,事件的監聽者(例如 tree )就不能知道這些更新。

       爲了通知“節點改變”,我們可以實現一個 TreeModelListener 。這裏有一個關於 tree model 監聽器的例子,當用戶爲一個樹節點輸入一個新名字時,事件會被檢測到。

class MyTreeModelListener implements TreeModelListener {
    public void treeNodesChanged(TreeModelEvent e) {
        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode)
                  (e.getTreePath().getLastPathComponent());

         /*

         * If the event lists children, then the changed
         * node is the child of the node we have already
         * gotten.  Otherwise, the changed node and the
         * specified node are the same.
         */
        try {
            int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode)
                   (node.getChildAt(index));
        } catch (NullPointerException exc) {}

         System.out.println("The user has finished editing the node.");
        System.out.println("New value: " + node.getUserObject());
    }
    public void treeNodesInserted(TreeModelEvent e) {
    }
    public void treeNodesRemoved(TreeModelEvent e) {
    }
    public void treeStructureChanged(TreeModelEvent e) {
    }
} 

這裏是一些增加按鈕事件處理器(用於增加節點)的代碼:

treePanel.addObject("New Node " + newNodeSuffix++);
...
public DefaultMutableTreeNode addObject(Object child) {
    DefaultMutableTreeNode parentNode = null;
    TreePath parentPath = tree.getSelectionPath();

    if (parentPath == null) {
        //There is no selection. Default to the root node.
        parentNode = rootNode;
    } else {
        parentNode = (DefaultMutableTreeNode)
                     (parentPath.getLastPathComponent());
    }

    return addObject(parentNode, child, true);
}
...
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
                                        Object child,
                                        boolean shouldBeVisible) {
    DefaultMutableTreeNode childNode =
            new DefaultMutableTreeNode(child);
    ...
    treeModel.insertNodeInto(childNode, parent,
                             parent.getChildCount());

    //Make sure the user can see the lovely new node.
    if (shouldBeVisible) {
        tree.scrollPathToVisible(new TreePath(childNode.getPath()));
    }
    return childNode;
}

用 tree model 做爲 JTree 的構造函數的參數,節點的文本改變監聽器是註冊在 model 上,而節點增刪是通過ActionListener 監聽按鈕事件

       這段代碼創建一個節點,插入 tree model 中。如果可以的話,講請求該節點的上層節點展開, tree 滾動,這樣新節點就可視了。這段代碼使用 DefaultTreeModel 類提供的 insertNodeInto 方法向 tree model 插入新節點。

(5)創建一個數據模型

       如果 DefaultTreeModel 不能符合你的需求,則需要你自定義一個 data model 。你的 data model 必須實現TreeModel 接口。 TreeModel 指定 獲取樹中特定節點、獲取特定節的孩子數量、確定一個節點是否爲葉子、通知model 樹的改變 和 增加刪除 tree model 監聽器的方法。

       有趣的是, TreeModel 接口接受各種對象作爲樹節點。這就不需要通過 TreeNode 對象來表現節點,節點甚至不需要實現 TreeNode 接口。因此,如果 TreeNode 接口不適合你的 tree model, 大可自由的設計自己的節點表現形式。例如,如果一個事前存在的 階級數據結構( hierarchical data structure ),你就不需要複製或者強制把他放進 TreeNode 模子中。你只需實現你的 tree model ,這樣你就可以使用已經存在的數據結構。

         下面圖片展示了一個叫 GenealogyExample( 家譜例子 ) ,他展示了某一個人的子孫和祖先。


在 GenealogyModel.java中,你可以找到這個自定義的 tree model 的實現。因爲這個 model 通過一個 DefaultTreeModel 的子類實現,他必須實現 TreeModel 接口。這就需要實現獲得節點信息的一系列方法,例如,哪個是根節點、某個節點的子孫是哪些節點。在 GenealogyModel 的例子中,每個節點表現爲一個 Person 類型的對象,這是一個未實現 TreeNode 接口的自定義類。

         一個 tree model 一定要實現一些方法,用於增刪 tree model listeners (監聽器),當樹的數據結構或者數據被改變時,必須把 TreeModelEvents ( tree model 事件)響應到這些監聽器。例如,當用戶指示 GenealogyExample從“顯示子孫”改變爲“顯示祖先”時, tree model 實現這些改變,然後產生一個事件並通知它的監聽器。

(這裏涉及的四個 java 文件都挺值得讀,裏面的編程思想跟技巧很值得學習)

(6)“懶加載”孩子                                 

       懶加載( lazy loading )是一種應用程序特徵:當一個類實例的實際加載和實例化延遲到這個實例使用前才進行。

       通過懶加載我們得到任何東西了嗎?當然,這將肯定增加了應用程序的性能。通過懶加載,你能夠在使用一個類前,利用內存資源加載和實例化它。這樣避免了應用程序的初始化時佔用更多的類加載跟實例化時間,加快了應用程序的初始化加載時間。

       有一種辦法可以懶加載一棵樹的孩子:利用 TreeWillExpandListener 接口。例如,你可以聲明和加載根節點,祖父雙親和雙親的顯示包含在以下代碼中:(樹的上層爲祖先)

       我們聲明瞭根節點 root ,祖父雙親和雙親如下所示:

class DemoArea extends JScrollPane

                   implements TreeWillExpandListener {
         .......
         .......

         private TreeNode createNodes() {
            DefaultMutableTreeNode root;
            DefaultMutableTreeNode grandparent;
            DefaultMutableTreeNode parent;

            root = new DefaultMutableTreeNode("San Francisco");
            grandparent = new DefaultMutableTreeNode("Potrero Hill");
            root.add(grandparent);

            parent = new DefaultMutableTreeNode("Restaurants");
            grandparent.add(parent);
          
            dummyParent = parent;
             return root;

        } 

你還可以像下面代碼一樣,把之前聲明的節點加載到樹 tree 上(這裏只是顯示而已)

TreeNode rootNode = createNodes();
tree = new JTree(rootNode);
 tree.addTreeExpansionListener(this);
tree.addTreeWillExpandListener(this);
	.......
  	.......
 setViewportView(tree);

 現在,你可以你可以懶加載孩子了,無論雙親節點 Restaurant 是否可視。如上所述,我們在一個方法中聲明兩個孩子:

private void LoadLazyChildren(){
	    DefaultMutableTreeNode child;
            child = new DefaultMutableTreeNode("Thai Barbeque");
            dummyParent.add(child);
            child = new DefaultMutableTreeNode("Goat Hill Pizza");
	    dummyParent.add(child);
	    textArea.append(" Thai Barbeque and Goat Hill Pizza are loaded lazily");
	}

	.......
	.......

public void treeWillExpand(TreeExpansionEvent e) 
                    throws ExpandVetoException {
            saySomething("You are about to expand node ", e);
            int n = JOptionPane.showOptionDialog(
                this, willExpandText, willExpandTitle,
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                willExpandOptions,
                willExpandOptions[1]);
           
	LoadLazyChildren();
	}

(7)如何寫 Tree Expansion Listener (監聽器)

       有時候,我們使用一棵 tree ,當分支變成展開或者摺疊狀態的時候,或許需要作出某些反映。例如,你或許需要加載或者保存數據。

       有兩種監聽器可以負責響應展開或摺疊事件: tree expansion listeners (我理解爲:已展開事件監聽器)和tree-will-expand listeners. (將來可以展開事件監聽器)這節討論 tree expansion listeners 。

       一個 tree expansion 監聽器偵測在展開或者摺疊已經發生。一般來說,你應該實現一個 tree expansion 監聽器,除非你需要阻止展開或摺疊。

       這個例子演示了一個簡單的 tree expansion 監聽器。窗口底部的文字區域展示關於每次 tree expansion 事件發生的消息。這是一個簡單易懂的演示。


       下面的代碼展示了程序如何處理 expansion 事件

來自 TreeExpandEventDemo.java

private void LoadLazyChildren(){
	    DefaultMutableTreeNode child;
            child = new DefaultMutableTreeNode("Thai Barbeque");
            dummyParent.add(child);
            child = new DefaultMutableTreeNode("Goat Hill Pizza");
	    dummyParent.add(child);
	    textArea.append(" Thai Barbeque and Goat Hill Pizza are loaded lazily");
	}

	.......
	.......

public void treeWillExpand(TreeExpansionEvent e) 
                    throws ExpandVetoException {
            saySomething("You are about to expand node ", e);
            int n = JOptionPane.showOptionDialog(
                this, willExpandText, willExpandTitle,
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                willExpandOptions,
                willExpandOptions[1]);
           
	LoadLazyChildren();
	}

(8)如何寫 Tree-Will-Expand Listener

       Tree-will-expand listener 擋住了節點的展開或摺疊(就是監聽器偵聽在展開摺疊正在發生之前)。如果僅僅要在展開和摺疊發生後通知監聽器,那你就應該使用 expansion listener 代替它。

       這個 demo 增加了 tree-will-expand 監聽器。在你每次要展開一個節點前,監聽器會提示你給出確認信息,確認是否展開。

       TreeExpandEventDemo2.java



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章