zeyuphoenix

          愿我愛的人快樂,愿愛我的人快樂,為了這些,我愿意不快樂.

          JTree--樹(節點渲染和資源管理器加載)(二)

          這次我們用樹實現一個比較綜合的例子,做一個類似Windows的資源管理器,先看Windows,如下圖:

          接著就是我們的實現了,路是一步一步走的,先看最基礎的實現,這個例子是我在網上看到的,雖然簡單,起碼是一種思路:

          效果如下圖:

          先說說這個實現的思路吧,首先創建樹,再創建一個根節點,然后取得盤符,把盤符放置在根節點下面,然后增加樹的監聽,當樹的節點展開時,取得當前節點下的所有文件和文件夾,加入到節點下,刷新樹,展開到指定位置就可以了.

          先是創建根節點的:

          public DefaultMutableTreeNode createRootNode(){ 

                File dir = new File("."); 

                DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(ROOT_NAME); 

                for(int i = 0; i < dir.listRoots().length; i++){ 

                    if(dir.listRoots()[i].isDirectory()){ 

                        String rootPath = dir.listRoots()[i].getPath(); 

                        this.treeNode = new DefaultMutableTreeNode(rootPath); 

                        rootNode.add(this.treeNode); 

                        this.treeNode = null

                    } 

                } 

                  return rootNode; 

              }

          然后是增加監聽,這里我們監聽展開事件:

          this.tree.addTreeExpansionListener(this); 

          然后是處理事件:

              @Override

          publicvoid treeExpanded(TreeExpansionEvent event) { 

          先取得選擇的節點對象:

          this.selectNode

          =(DefaultMutableTreeNode)event.getPath().getLastPathComponent();

          然后取得節點的絕對路徑:

              String path = event.getPath().toString(); 

          然后根據路徑在節點下添加子節點:

              publicvoid addTreeNode(DefaultMutableTreeNode node, File dir) {

                 if (node == null || dir == null) {

                     return;

                 }

                 if (!dir.isDirectory()) {

                     return;

                 }

                 if (!node.isRoot()) {

                     // get all files in node

                     File file[] = dir.listFiles();

                     for (int i = 0; i < file.length; i++) {

                        // hidden is not show

                        if (file[i].isDirectory() && !file[i].isHidden()) {

                            // create node

                            this.treeNode = new DefaultMutableTreeNode(dir.list()[i]);

                            // add to tree

                            ((DefaultTreeModel) this.jt.getModel()).insertNodeInto(

                                   treeNode, node, node.getChildCount());

                            this.treeNode = null;

                        }

                     }

                 }

              }

          同樣的收起事件也要處理:

              @Override

          publicvoid treeCollapsed(TreeExpansionEvent event) { 

          最后把樹放置在JScrollPane上就可以了.

          this.jscroolpane.setViewportView(this.tree); 

          這樣一個簡單的資源管理樹就完成了,下面我們說說它的問題:

               圖片和外觀和Windows相差太大

          這個我們可以通過設置L&F和通過前面寫的Renderer那樣設置新的圖片解決,不是大問題.

               文件夾里文件多時展開會很慢,會導致界面假死

          這個我們可以自己寫一個緩加載的TreeNode,讓它繼承于DefaultMutableTreeNode,在它里面定義加載標示,然后使用SwingWorker或者多線程方式使Tree平穩加載,雖然麻煩,但是也可以解決.

               Tree點擊假死時,用戶會以為出現問題,胡亂點擊會加載多個事件

          這個問題其實是Swing事件機制的問題,其實是沒辦法解決的,因為總會存在耗時的操作的,不等待是不可能的.但我們可以做更好的用戶體驗來避免這個問題,這里我想到的解決辦法是在Tree上繪制一層GlassPane,屏蔽所有事件,提示用戶,等加載完成后,取消GlassPane界面.

               只有我的電腦的基本文件,沒有網上鄰居之類的

          這個問題很難解決,涉及到網上鄰居就存在網絡的問題了,還需要網絡連接和掃描,開始我的思路是使用Apachecommons-client,后來發現有人給出了更好的辦法,使用JavaJFileChooser,Java已經實現了很多我們需要實現的.

               取得的資源管理樹的子目錄是亂序的

          這個很好解決,使我們的TreeNode實現Comparable接口就可以了.

          為了解決這五個問題我們做的改進版:

          首先我們解決問題一,看看我們的代碼:

          節點的圖片的樣式問題我們可以設置Renderer,又因為這些圖片可以在JFileChooserUI中取得,我們先參照JFileChooserUI做一個FileView:

              // ***********************

              // * FileView operations *

              // ***********************

              protectedclass BasicFileView extends FileView {

          復寫它的方法:

                 @Override

                 public String getName(File f) {

                     // Note: Returns display name rather than file name

                     String fileName = null;

                     if (f != null) {

                        fileName = chooser.getFileSystemView().getSystemDisplayName(f);

                     }

                     return fileName;

                 }

          這個是顯示名字.

                 @Override

                 public String getDescription(File f) {

                     return f.getName();

                 }

          這個是描述

                 @Override

                 public String getTypeDescription(File f) {

                     String type = chooser.getFileSystemView().getSystemTypeDescription(

                            f);

                     if (type == null) {

                        if (f.isDirectory()) {

                            type = directoryDescriptionText;

                        } else {

                            type = fileDescriptionText;

                        }

                     }

                     return type;

                 }

          這個是文件類別

                 @Override

                 public Icon getIcon(File f) {

          這個是圖片表示.

          這樣我們構建這個FileView之后我們需要的圖片和名字就都可以取得了.

          然后是我們的Renderer了:

          privateclass FileSystemTreeRenderer extends DefaultTreeCellRenderer {

          復寫它的方法,設置我們從FileView取得圖片和名字:

          @Override

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

               boolean hasFocus) {

               setText(getFileView(chooser).getName(node.getFile()));

               setIcon(getFileView(chooser).getIcon(node.getFile()));

          然后設置到樹上:

               tree.setCellRenderer(new FileSystemTreeRenderer());

          看看效果:

          是不是和Windows的很接近了,設置L&F,如下圖:

          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

          然后解決問題二,我們不能用樹的原始節點了,用我們自己構造的,繼承于它:

          publicabstractclass LazyMutableTreeNode extends DefaultMutableTreeNode {

          增加一個屬性:

              /** is node load. */

              privatebooleanloaded = false;

          提供一個虛方法給子類實現:

              protectedabstractvoid loadChildren();

          然后是我們的實現:

          privateclass FileTreeNode extends LazyMutableTreeNode {

          復寫它的方法,load不允許加載:

                  @Override

                 publicboolean isLeaf() {

                     if (!isLoaded()) {

                        returnfalse;

                     } else {

                        returnsuper.isLeaf();

                     }

                 }

          還有它的現實名字:

                 @Override

                 public String toString() {

                     returnchooser.getFileSystemView().getSystemDisplayName(

                            (File) getUserObject());

                  }

          實現虛方法:

              @Override

                 protectedvoid loadChildren() {

                     FileTreeNode[] nodes = getChildren();

                     for (int i = 0, c = nodes.length; i < c; i++) {

                        add(nodes[i]);

                     }

                 }

          這樣問題二就解決了,同時也可以在這里解決我們的問題五,使我們的TreeNode實現Comparable接口:

              privateclass FileTreeNode extends LazyMutableTreeNode implements

                     Comparable<Object> {

          然后實現方法:

              @Override

                 publicint compareTo(Object o) {

                     if (!(o instanceof FileTreeNode)) {

                        return 1;

                     }

                     return getFile().compareTo(((FileTreeNode) o).getFile());

                 }

          最后在我們使用時:

                 // sort directories, FileTreeNode implements Comparable

                 FileTreeNode[] result = (FileTreeNode[]) nodes

                        .toArray(new FileTreeNode[0]);

                 Arrays.sort(result);

          nodes.add(new FileTreeNode(result[i]));

          這樣我們加入的節點文件夾就都是排序的了.

          然后我們解決問題四,三比較麻煩留在最后:

          構建這個組件時,我們先構建JFileChooser

          JFileChooser chooser = new JFileChooser();

          增加監聽:

              protectedvoid installListeners() {

                 tree.addTreeSelectionListener(new SelectionListener());

                 chooser.getActionMap().put("refreshTree", new UpdateAction());

                  chooser.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(

                        KeyStroke.getKeyStroke("F5"), "refreshTree");

                 chooser.addPropertyChangeListener(new ChangeListener());

              }

          在監聽中展開樹時,使用JFileChooser的方法:

              /**

               * tree node select change.

               */

              privateclass SelectionListener implements TreeSelectionListener {

                 @Override

                 publicvoid valueChanged(TreeSelectionEvent e) {

                     getApproveSelectionAction()

                            .setEnabled(tree.getSelectionCount() > 0);

                     setSelectedFiles();

                     // the current directory is the one currently selected

                     TreePath currentDirectoryPath = tree.getSelectionPath();

                     if (currentDirectoryPath != null) {

                        File currentDirectory = ((FileTreeNode) currentDirectoryPath

                               .getLastPathComponent()).getFile();

                        chooser.setCurrentDirectory(currentDirectory);

                     }

                 }

              }

          這樣我們所有的目錄結構就不需要自己去循環構建了,使用JFileChooser為我們提供好的就可以了,如下圖,網上鄰居也有了,問題四完成了:

          最后我們來解決問題三,為什么會假死,是因為文件夾多或者網速慢導致的,解決辦法當然是多線程,但是多線程在Swing里容易出現線程不安全,因為它不在ADT,這里我們使用SwingWorker,監聽樹的展開事件:

          tree.addTreeExpansionListener(new TreeExpansion());

          處理它:

              privateclass TreeExpansion implements TreeExpansionListener {

                 @Override

                 publicvoid treeCollapsed(TreeExpansionEvent event) {

                 }

                 @Override

                 publicvoid treeExpanded(TreeExpansionEvent event) {

                     // ensure children gets expanded later

                     if (event.getPath() != null) {

                     Object lastElement = event.getPath().getLastPathComponent();

                        if (lastElement instanceof FileTreeNode && useNodeQueue)

                            if (((FileTreeNode) lastElement).isLoaded()) {

          慢主要是在這里的處理,我們把它放在SwingWorker里面:

          new WorkerQueue(node, tree, glassPane).execute();

          然后看這個類:

              privatestaticfinalclass WorkerQueue extends

                     SwingWorker<Void, FileTreeNode> {

          復寫它的方法,處理我們的TreeNode添加事件:

              @Override

              protected Void doInBackground() throws Exception {

                 glassPanel.setVisible(true);

                 for (Enumeration<?> e = node.children(); e.hasMoreElements();) {

                     publish((FileTreeNode) e.nextElement());

                 }

                 returnnull;

              }

              @Override

              protectedvoid process(List<FileTreeNode> chunks) {

                 for (FileTreeNode fileTreeNode : chunks) {

                     fileTreeNode.getChildCount();

                 }

              }

              @Override

              protectedvoid done() {

                 glassPanel.setVisible(false);

                 tree.repaint();

              }

          然后是處理我們在展開節點時屏蔽所有的鼠標點擊并給以用戶提示,這里我們自己繪制一個Component,把它設置為GlassPane,屏蔽所有事件:

          /**

           */

          publicclass GlassPane extends JComponent {

          屏蔽所有事件,只能獲得焦點:

              // blocks all user input

              addMouseListener(new MouseAdapter() {

              });

              addMouseMotionListener(new MouseMotionAdapter() {

              });

              addKeyListener(new KeyAdapter() {

              });

              setFocusTraversalKeysEnabled(false);

              addComponentListener(new ComponentAdapter() {

                 publicvoid componentShown(ComponentEvent evt) {

                     requestFocusInWindow();

                 }

              });

          然后是繪制:

              @Override

              protectedvoid paintComponent(Graphics g) {

          先繪制整體背景:

                 // gets the current clipping area

                 Rectangle clip = g.getClipBounds();

                 // sets a 65% translucent composite

                 AlphaComposite alpha = AlphaComposite.SrcOver.derive(0.65f);

                 Composite composite = g2.getComposite();

                 g2.setComposite(alpha);

                 // fills the background

                 g2.setColor(getBackground());

                 g2.fillRect(clip.x, clip.y, clip.width, clip.height);

                 g2.setComposite(composite);

          然后繪制一張提示圖片,本來想繪制一個滾動的等待圖標,實在是沒心情寫了,隨便Google了張圖片放上去了.

              if (image == null) {

                 try {

                    image = ImageIO.read(getClass().getResource("wait2.jpg"));

                 } catch (IOException ex) {

                     ex.printStackTrace();

                 }

              }

              g.drawImage(image, getWidth() / 2 - 40, getHeight() / 2

                 - 80, 120, 120, null);

          通過設置畫面的GlassPane就可以了

              Component glassPane = new GlassPane();

              frame.getRootPane().setGlassPane(glassPane);

          最終效果如下圖:

          到此為止,關于樹的操作基本就完成了,下面再開個專題講下有CheckBoxJTree,至于JTree的拖拽,因為和其它Component基本是一致的,就留在以后拖拽專題一起寫了,總之,JTree還是不算復雜的一個組件.

          posted on 2010-04-22 22:51 zeyuphoenix 閱讀(5431) 評論(11)  編輯  收藏 所屬分類: JTree的使用

          評論

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2010-04-23 08:57 股海e程

          好文章!  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2010-04-24 09:20 羅萊家紡

          你們呢是明年的南方  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2010-04-24 14:18 fivesmallq

          樓主的文章一直在訂閱拜讀。寫的很好  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2010-06-11 15:27 xdelxia

          老大能給我份源碼么,我看得不是很懂啊,763823729@qq.com這是我的郵箱,謝謝老大,請一定要給我份啊,我最近做的項目遇到了個關于圖片的加載問題,我現在解決不了!  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2011-10-01 09:17 bug

          真的很厲害.但由于本人java水平不高.請問能否給分原代碼讓我參考學習呢?我的郵箱1280074964@qq.com 謝謝~  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2011-10-11 15:42 allenxiaomai

          寫的很好呢。能不能發給我一份源代碼讓我仔細學習一下呢?郵箱:476847992@qq.com,謝謝啦。  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2011-10-14 12:29 1788455@qq.com

          你好 能給我一份Swing的源碼么  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二)[未登錄] 2011-10-22 16:46 wei

          寫得很好啊,可以給我一份源碼嗎?我研究了很久,都不知道怎么寫。。謝謝
          jidi123698745@tom.com  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2012-12-06 18:22 真惡心

          你這樣寫,別人拿去怎么用,下載源碼的地方又不能用?什么意思。如果僅僅是自己看的話,那就隱藏起來吧。  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二) 2015-04-05 16:25 張森洋

          請問參考JFileChooser的UI做的那個FileView類怎么寫?  回復  更多評論   

          # re: JTree--樹(節點渲染和資源管理器加載)(二)[未登錄] 2015-11-12 23:03 ch

          能給份 Jtable 、以及Swing 其他組件的代碼 258440352@qq.com  回復  更多評論   

          導航

          <2010年4月>
          28293031123
          45678910
          11121314151617
          18192021222324
          2526272829301
          2345678

          統計

          常用鏈接

          留言簿(52)

          隨筆分類

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 灵丘县| 浦江县| 瑞丽市| 五莲县| 忻城县| 镇康县| 南宫市| 休宁县| 天峻县| 晴隆县| 集贤县| 攀枝花市| 新巴尔虎左旗| 福鼎市| 昌乐县| 聊城市| 元谋县| 旬阳县| 遵义市| 沂源县| 汉阴县| 申扎县| 大余县| 驻马店市| 仪征市| 海淀区| 剑河县| 阜宁县| 那曲县| 托克托县| 紫阳县| 兰溪市| 宁明县| 唐海县| 万山特区| 文成县| 惠州市| 横峰县| 玛曲县| 日照市| 阳信县|