java something

不要以为......很遥远
随笔 - 23, 文章 - 1, 评论 - 2, 引用 - 0
数据加载中……

Jree

树控件很适合用来显示,导航和编辑结构化对象;但是JTree很复杂,swing中有整个一个包都是针对它的(javax.swing.tree),注意树控件是显示的,但是树数据结构是一种集合接口的实现,就如同JList和java.util.List一样,他们应用在不同层,当然你使用Jlist来显示List接口的实现者那是很般配的。
*
**
关于树的术语如根,节点,叶子,深度,路径,平衡性,边,子树;不需要我这里过多的解释,任何一本数据结构的书籍都会介绍他们。我这里主要是讲述树控件。

树遍历这个概念先提一下:遍历即逐个访问,对于树而言主要有三种:前序,后序,中序遍历树的每个节点。遍历一般是用递归算法来实现的,三种遍历法区别于先访问树的那个部分。树遍历也是比较难的一个技术,不好掌握,我在大学时用汇编实现过这三种算法,到现在还记忆犹新(完全自己找到的规律),一下来和朋友们分享一下。
对于一个有两个子节点的树(或多个子节点),请你沿着树的外围画一个轮廓线:

 

———>         >——————
        /             \
      /                 \
     /_____>____\     

       这是大致绕树行走的轮廓线,大家都知道(或许你还不知道)函数的调用时控制流的传递就是这个样子的。(控制流是线程执行方法时的形象表述)比如一下函数: main(){

     f1();
     f2();
}//该函数的控制流向是:先传给main,再由main()传给f1,之后退回到mian(),在传给f2()在由f2退回给main之后结束程序。异步方法调用时才会从这个封闭的轮廓中分出一个分支来。现在来谈你如何设计一个树遍历方法:
我们来看一个函数的几个关键部位,
       func(){
        entry入口处
          
            中间部位           

        return出口处   
      }也许你很迷惑这与树遍历算法有和关系,告诉你吧这三个特殊部位就是你在设计递归时,递归函数应该出现的位置,他们出现在不同的位置就是不同的“序”,伪码如:
先序遍历
traversTree(Node root){
   if(root !=null){
     if(root.isLeaf()){//当是叶子时,
         visit(root);//前序遍历是先遍历页节点
     }   
         Node[] children=root.getChildren();//获取所有子树
         for(Node n:children){
           traversTree(n);//递归遍历所有子树,注意子树可能为空。
          }
   }

}

中序遍历(亦称广度优先遍历,总是先遍历树的根)

traversTree(Node root){
   if(root !=null){
     //树非空
     visit(root); //这是中序遍历 visit出现与递归函数之前。
    
     Node[] children=root.getChildren();//获取所有子树
         for(Node n:children){
           traversTree(n);//递归遍历所有子树,注意子树可能为空。
          }
   }

}

后序遍历(亦称深度优先搜索):
traversTree(Node root){
   if(root !=null){
        
     Node[] children=root.getChildren();//获取所有子树
         for(Node n:children){
           traversTree(n);//递归遍历所有子树,注意子树可能为空。
          }
    
     
     visit(root); //这是后序遍历 visit出现在递归函数之后。
   }

}

以上三个算法,可能有点不正确,我没测试过,时间太久了有点忘了,总之为大家做个参考吧!
因为树结构典型的是应用了组合设计模式,所以只要涉及到树肯定涉及遍历,和递归。所以这里罗嗦一下。   所有的树都是节点                   
**
***
swing中Jtree处理树结构是通过树模型接口,它实现了TreeNode接口(在api文档中竟看不到此信息!),DefaultMutableTreeNode类实现了TreeNode接口,并提供了前,中,后序的树遍历能力。
JTree图形化的显示了树结构的每一个节点,所有的UI控件都有两个目的,显示和输入(输入包括数据的输入如JTextField和命令输入如菜单,按钮等),JTree既可以用于树结构的显示,也可以用于命令的输入,或使得我们编辑树节点。
树节点可以被选中,它由TreeSelectionModel来控制,选择涉及到维护作为TreeNode实例的树节点间的路径轨迹。树控件典型的可以激发两类事件:TreeModelEvent和TreeExpansionEvent,当然其他Awt和Swing事件也可由树控件激发(看其继承层次结构即可知)比如MouseListener可用来截取鼠标事件。JTree扩展了Scrollable接口,可被放在一个滚动面板中。

JTree的构造:可使用默认的构造方法,提供一个TreeNode作为其根节点,提供一个TreeModel包含所有其它的节点,或提供一个一维数组,向量,或对象的哈希表,对于这些集合中的单个元素,如果它又是一个集合,那么他们会被解释显示为子树,该功能由JTree的内部类DynamicUtilTreeNode完成。
***
****
TreeModel接口:
该接口的实现者为JTree提供显示的数据,如果你熟悉MVC模式,你应该明白所有的swing或awt控件中模型的作用就是为相应的控件提供数据。当模型的数据结构有所变化时它会通知视图(这里就是JTree)来更新显示。当然模型也可以添加其他的监听器如Jtree的addTreeModelListener,你可以实现该监听器,来使你自己的类接收模型变化给你的通知。如果你不熟悉MVC模式,请参考POSA卷一或其他资料,顺便提一下在我们学校GUI时都知道有MVC模式的应用,往往不知道那个Controller是什么类,其实就是视图的监听器,比如ActionListener,注意别被众多的监听器弄昏了,一类是模型需要添加的,一类是视图(比如JComponent的子类)需要添加的。控制的流向或数据的流向是相反的,视图需要添加的监听器(我们常常实现他们)才是控制器。

因为模型和视图都能够触发事件,比如视图(JTree等控件)是触发用户输入导致的事件,而模型触发的事件是因为模型中维护的数据有所变动才触发的(比如,树模型中树节点的增删,改,或树路径的变动)。而他们都使用了观察者模式,算了不多说了,到时全弄成模式了,会搞昏大家的。继续....

JTree的setModel和getModel方法是用来更换/设置和获取模型的方法。你可替换现有JTree的模型,或者你想这样用,两个模型,一个用,一个备。如果构造模型复杂耗时的话,先在后台构造好一个在换掉原先的。就如同双缓冲技术的思路那样。
****
*****
杂项:
DefultTreeModel是对TreeModel接口的默认实现类,
TreeNode接口可告诉你改实现者是否为一个叶子,一个父节点等。MutalbeTreeNode接口扩展了TreeNode接口,我们可在该实现者中存放一个我们自己的类实例(setUserObject()/getUserObject);
defaultMutableTreeNode 实现了MutableTreeNode接口,children()方法返回以一维向量形式存放的直接子节点的枚举,也可以使用getChildAt()返回特定索引位置的子节点(注意子节点完全可以是一颗子树)该类提供了前中后序访问树的能力:preorderEnumeration(),,breadthFirstEnumeration(),depthFirstEnumeration()postorderEnumeration()最后两个方法同行为,只不过是不同的称号而已。

TreePath:该类用一系列节点表示一个从树根到一个节点的路径,它是只读的,提供与其他路径比较的能力。
TreeCellRenderrer接口:渲染tree的一个单元的组件,我们自己实现该接口并用jtree的setCellRenderer()方法替换原先的渲染器,可以是树节点在选中,获取焦点,不同的树状态(叶子或父节点,展开,或收缩)等不同的状态下的外观。
DefaultTreeCellRenderer类是TreeCellRenderrer接口的默认实现,它扩展了JLabel,并基于以上描述的树状态来渲染树节点,其提供的属性包括图标,背景色,前景色等,其get和set方法是我们可以访问的,通过这些方法你当然可以换掉树节点的图标了。

CellEditor接口:定义了控制何时编辑将开始,结束,提取一个新的结果,是否编辑请求改变当前组件的选择,请参考API文档看该接口的方法。该接口在JTree和JTable中都有用到。,该接口也可以添加监听器,当编辑停止或取消时会激发ChangeEvents到其所有的注册处理器哪里。
TreeCellEditor接口扩展了CellEditor接口,jtree的setCellEditor()使得我们可以用任何一个可充当编辑器的组件替换掉原来的那个。DefaultCellEditor实现了该接口,这个编辑器允许使用JTextField,JComboBox或是JCheckBox组件来编辑数据,其保护的内部类EditorDelegate会响应getCellEditorValue()方法把当前值返回。DefaultCellEditor仅基于以上三个J控件作为编辑器,其clickCountToStart方法决定鼠标单击几次会触发编辑。默认对于JTextField是两次,JComboBox和JCheckBox是一次,changeEvents会在stopCellEditing()和cancelCellEditing()时激发。
DefaultTreeCellEditor扩展了DefaultCellEditor类并且是TreeCellEditor的默认实现类,他使用JTextField来编辑节点数据,在键入ENTER键后stopCellEditing()会被调用。对于树节点的编辑我们可添加自己的时间监听器来处理他们。默认时编辑开始于节点被单击三次或两次(时间间隔在内部会用一个定时器来决定),也可以改变他们的数目setClickCountToStart();
JTree的选择是基于行和树路径的,我们可以选择使用那个。

TreeSelectionModel接口用于树选择模型,支持三种选择,SINGLE_TREE_SELECTION,
DISCONTIGUOUS_TREE_SELECTION,CONTIGUOUS_TREE_SELECTION,set/getSelectionMode()可以访选择模型。getSelectionPath『s』()会返回一个当前选中的树路径。DefaultTreeSelectionModel默认实现了该接口,该类提供TreeSelectionlistener通知,当树路径选择发生变化时。

TreeModelListener实现者可以侦听模型变化,TreeSelectionListener用来侦听视图JTree的selection(仅有一个方法valueChanged(TreeSlectcionEvent tsEvt));
TreeExpansionListener用来对树展开收缩进行处理。
TreeW illExpandListener在树“将要”展开和收缩时得到通知,你可截获处理,ExpandVetoException异常如果抛出,那么树不会展开和收缩。

TreeModelEvent,用来通知模型的监听器,JTree的数据部分或全部发生了变化。该事件对象封装了源组件的引用,封装了一个TreePath或一个用来表示路径的数组。
TreeselectionEvent,视图会用其通知所有视图监听器TreeSelectionListeners,选择发生了变化。
TreeExpansionEvent,用来封装相应最近或可能展开或收缩的TreePath,使用getPath()方法访问树路径。
ExpandVetoException异常可由TreeWillExpandListener抛出,来否决树路径的展开和收缩。

JTree提供的现成方便的UI属性:
myJTree.putClientProperty("JTree.lineStyle", "Angled");//更改线型。
如同其他Swing组件,我们也可以改变默认的用于JTree的UI资源(全局性的):
UIManager.put("Tree.hash",
new ColorUIResource(Color.lightGray));//改变渲染节点间edges边的颜色。
UIManager.put("Tree.openIcon", new IconUIResource(
new ImageIcon("myOpenIcon.gif")));//改变一个打开的树节点的图标。同理可用于其它情况:Tree.leafIcon, Tree.expandedIcon,和Tree.closedIcon, Tree.collapsedIcon。

其他控制TreeUI显示的方法:
myTree.setRowHeight()//控制树节点的行高,
JTree的UI委托也提供了更改树外观的方法(相比于UIManager的方法,这里是局部的)。
BasicTreeUI basicTreeUI = (BasicTreeUI) myJTree.getUI();
basicTreeUI.setRightChildIndent(10);
basicTreeUI.setLeftChildIndent(8);


以上简要提及了JTree的方方面面,许多的事件,将听器模型,请仔细分析,一定要分清哪些是针对模型的那些是针对视图的。

*****
******
简单的示例,我这里仅用到了最简单的树构造方法,和一个监听器,在
以后我的自学过程中,我会继续试用其他的JTree知识,我的JTree学习
最终都是想实现那个GUI上的授权控制系统,请参考其他篇章,
至于这里用到的LAndFSysMenu类,在我的其他篇章中有该类的实现。
package jTreeDemo;

import java.awt.Container;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import lookAndFeelSys.*;
import userInterfaces.UIUtil;
import java.awt.*;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.tree.*;


public class JTreeTest extends JFrame{

public static void main(String[] args){
new JTreeTest("测试");
}

public JTreeTest(String title){
   super(title);
   biuldFrame();
}

   private void biuldFrame(){
    JMenuBar jmb=new JMenuBar();
   
    JMenu jm=new LAndFSysMenu();
    //JMenu jm=new JMenu("hello");
   
    jmb.add(jm);
    this.setJMenuBar(jmb);
   
    buildFrmContent();   
   
    UIUtil.SetComponentDimension(this,0.5,0.6);
    UIUtil.SetComponentToCenterOfScreen(this);
    this.setVisible(true);
    this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
   }
  
   private void buildFrmContent(){
    Container root_c=this.getContentPane();
    JTabbedPane jtp=new JTabbedPane();
    Container c = new JPanel();
    jtp.addTab("静态树组件练习",c );
    jtp.addTab("事件监听",this.treeDemo2());
     

   root_c.add(jtp);

c.setLayout(new GridLayout(2,4));
JScrollPane jsp_1=new JScrollPane();
JScrollPane jsp_2=new JScrollPane();
JScrollPane jsp_3=new JScrollPane();
JScrollPane jsp_4=new JScrollPane();


/*为JTree准备显示的模型*/
Object[] m1=new String[]{"节点1","节点2","节点3"};
Object[] m2=new String[][]{
    {"1.1","1.2","1.3"},
    {"2.1","2.2","2.3"},
    {"3.1","3.2","3.3"}
};
Vector<Object> m3=new Vector<Object>();
m3.add("1");
m3.add("2");
m3.add(m1);
m3.add(m2);
Hashtable<String,Object> m4=new Hashtable<String,Object>();
m4.put("子一","叶子");
m4.put("子二", m1);
m4.put("子三",m3);


JTree jtr_1=new JTree(m1);
jsp_1.getViewport().add(jtr_1);
JTree jtr_2=new JTree(m2);
jsp_2.getViewport().add(jtr_2);
JTree jtr_3=new JTree(m3);
jsp_3.getViewport().add(jtr_3);
JTree jtr_4=new JTree(m4);
jsp_4.getViewport().add(jtr_4);


c.add(jsp_1);
c.add(jsp_2);
c.add(jsp_3);
c.add(jsp_4);

/*jsp_1.getViewport().add(jtr_1);
c.add(jsp_1);*/
   }

   /*<<     另一组JTree实例:*/
   private JPanel treeDemo2(){
    JPanel rsltPanel=new JPanel();
    rsltPanel.setLayout(new BorderLayout());
   
    JLabel jl_msg=new JLabel("此标签用来显示树选择情况");
    JScrollPane jsp_1=new JScrollPane();
    Object[] m=new String[]{"节点1","节点2","节点3"};
    JTree jtr=new JTree(m);
    jtr.getSelectionModel()
    .addTreeSelectionListener(new MySelectionLstnr(
      jl_msg));
   
    jsp_1.getViewport().add(jtr);
   
    rsltPanel.add(jsp_1,BorderLayout.CENTER);
    rsltPanel.add(jl_msg,BorderLayout.SOUTH);
    return rsltPanel;
   }
   class MySelectionLstnr implements TreeSelectionListener{
    //该内部类实现树监听器,在树被选中后将选中的节点
    //信息打印到一个Label上
    private JLabel jl_msg=null;
   
    public MySelectionLstnr(JLabel msgLabel){
    this.jl_msg=msgLabel;
    }
@Override
public void valueChanged(TreeSelectionEvent e) {
   // 凡是树选择的处理都涉及到树路径的处理:
   TreePath path = e.getPath();
   Object[] nodes = path.getPath();
  
   //当前选中的节点是树路径上最后一个节点
     Object selectedNode=nodes[nodes.length-1 ];
     if(this.jl_msg!=null){
     this.jl_msg.setText("选中的节点上的文本是:"+
         selectedNode.toString());
     }
}


   }
   /*另一组JTree实例:>>*/
}

 

******
参考Java Swing (Manning出版社)swing hack (orelly出版社)。

posted on 2011-03-23 09:09 Jamie 阅读(2206) 评论(0)  编辑  收藏 所属分类: swing