关于我写了三万字博客后悔了好久这件事之第二个三万字GUI(swing)

2023-11-16

目录

简介:

使用Swing的优势:

Swing的特征:

Swing基本组件的用法

Swing组件层次

AWT组件的Swing实现

简单了解swing(JFrame)

弹窗

标签:

面板

按钮

3.6 列表

3.7 文本框

JTree、TreeModel实现树

创建树

拖动、编辑树结点

监听结点事件

使用DefaultTreeCellRenderer改变结点外观

扩展DefaultTreeCellRenderer改变结点外观

实现TreeCellRenderer接口改变结点外观


简介:

己经介绍过AWT和Swing 的关系 , 因此不难知道 : 实际使用 Java 开发图形界面程序时 ,很少使用 AWT 组件,绝大部分时候都是用 Swing 组件开发的 。 Swing是由100%纯 Java实现的,不再依赖于本地平台的 GUI, 因此可以在所有平台上都保持相同的界面外观。独立于本地平台的Swing组件被称为轻量级组件;而依赖于本地平台的 AWT 组件被称为重量级组件。 ​ 由于 Swing 的所有组件完全采用 Java 实现,不再调用本地平台的 GUI,所以导致 Swing 图形界面的显示速度要比 AWT 图形界面的显示速度慢一些,但相对于快速发展的硬件设施而言,这种微小的速度差别无妨大碍。

使用Swing的优势:

1. Swing 组件不再依赖于本地平台的 GUI,无须采用各种平台的 GUI 交集 ,因此 Swing 提供了大量图形界面组件 , 远远超出了 AWT 所提供的图形界面组件集。 ​ 2. Swing 组件不再依赖于本地平台 GUI ,因此不会产生与平台 相关的 bug 。

3. Swing 组件在各种平台上运行时可以保证具有相同的图形界面外观。

Swing 提供的这些优势,让 Java 图形界面程序真正实现了 " Write Once, Run Anywhere" 的 目标。

Swing的特征:

1. Swing 组件采用 MVC(Model-View-Controller, 即模型一视图一控制器)设计模式:
​
        模型(Model): 用于维护组件的各种状态;
​
    视图(View): 是组件的可视化表现;
    
    控制器(Controller):用于控制对于各种事件、组件做出响应 。
    
    当模型发生改变时,它会通知所有依赖它的视图,视图会根据模型数据来更新自己。Swing使用UI代理来包装视图和控制器, 还有一个模型对象来维护该组件的状态。例如,按钮JButton有一个维护其状态信息的模型ButtonModel对象 。 Swing组件的模型是自动设置的,因此一般都使用JButton,而无须关心ButtonModel对象。
​
2. Swing在不同的平台上表现一致,并且有能力提供本地平台不支持的显示外观 。由于 Swing采用 MVC 模式来维护各组件,所以 当组件的外观被改变时,对组件的状态信息(由模型维护)没有任何影响 。因 此,Swing可以使用插拔式外观感觉 (Pluggable Look And Feel, PLAF)来控制组件外观,使得 Swing图形界面在同一个平台上运行时能拥有不同的外观,用户可以选择自己喜欢的外观 。相比之下,在 AWT 图形界面中,由于控制组件外观的对等类与具体平台相关 ,因此 AWT 组件总是具有与本地平台相同的外观 。   
 

Swing基本组件的用法

Swing组件层次

Swing组件继承体系图:

大部分Swing 组件都是 JComponent抽象类的直接或间接子类(并不是全部的 Swing 组件),JComponent 类定义了所有子类组件的通用方法 ,JComponent 类是 AWT 里 java.awt. Container 类的子类 ,这也是 AWT 和 Swing 的联系之一。 绝大部分 Swing 组件类继承了 Container类,所以Swing 组件都可作为 容器使用 ( JFrame继承了Frame 类)。

Swing组件和AWT组件的对应关系:

大部分情况下,只需要在AWT组件的名称前面加个J,就可以得到其对应的Swing组件名称,但有几个例外:

1. JComboBox: 对应于 AWT 里的 Choice 组件,但比 Choice 组件功能更丰富 。 ​ 2. JFileChooser: 对应于 AWT 里的 FileDialog 组件 。 ​ 3. JScrollBar: 对应于 AWT 里的 Scrollbar 组件,注意两个组件类名中 b 字母的大小写差别。 ​ 4. JCheckBox : 对应于 AWT 里的 Checkbox 组件, 注意两个组件类名中 b 字母的大小 写差别 。 ​ 5. JCheckBoxMenultem: 对应于 AWT 里的 CheckboxMenuItem 组件,注意两个组件类名中 b字母的大小写差别。

Swing组件按照功能来分类:

1. 顶层容器: JFrame、JApplet、JDialog 和 JWindow 。 ​ 2. 中间容器: JPanel 、 JScrollPane 、 JSplitPane 、 JToolBar 等 。 ​ 3. 特殊容器:在用户界面上具有特殊作用的中间容器,如 JIntemalFrame 、 JRootPane 、 JLayeredPane和 JDestopPane 等 。 ​ 4. 基本组件 : 实现人机交互的组件,如 JButton、 JComboBox 、 JList、 JMenu、 JSlider 等 。 ​ 5. 不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLabel 、 JProgressBar 和 JToolTip等。 ​ 6. 可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如 JTable 、 JTextArea 和JTextField 等 。 ​ 7. 特殊对话框组件:可以直接产生特殊对话框的组件 , 如 JColorChooser 和 JFileChooser 等。

AWT组件的Swing实现

Swing 为除 Canvas 之外的所有 AWT 组件提供了相应的实现,Swing 组件比 AWT 组件的功能更加强大。相对于 AWT 组件, Swing 组件具有如下 4 个额外的功能 :

  1. 可以为 Swing 组件设置提示信息。使用 setToolTipText()方法,为组件设置对用户有帮助的提示信息 。

  2. 很多 Swing 组件如按钮、标签、菜单项等,除使用文字外,还可以使用图标修饰自己。为了允许在 Swing 组件中使用图标, Swing为Icon 接口提供了 一个实现类: Imagelcon ,该实现类代表一个图像图标。

  3. 支持插拔式的外观风格。每个 JComponent 对象都有一个相应的 ComponentUI 对象,为它完成所有的绘画、事件处理、决定尺寸大小等工作。 ComponentUI 对象依赖当前使用的 PLAF , 使用 UIManager.setLookAndFeel()方法可以改变图形界面的外观风格 。

  4. 支持设置边框。Swing 组件可以设置一个或多个边框。 Swing 中提供了各式各样的边框供用户边 用,也能建立组合边框或自己设计边框。 一种空白边框可以用于增大组件,同时协助布局管理器对容器中的组件进行合理的布局。

每个 Swing 组件都有一个对应的UI 类,例如 JButton组件就有一个对应的 ButtonUI 类来作为UI代理 。每个 Swing组件的UI代理的类名总是将该 Swing 组件类名的 J 去掉,然后在后面添加 UI 后缀 。 UI代理类通常是一个抽象基类 , 不同的 PLAF 会有不同的UI代理实现类 。 Swing 类库中包含了几套UI代理,分别放在不同的包下, 每套UI代理都几乎包含了所有 Swing组件的 ComponentUI实现,每套这样的实现都被称为一种PLAF 实现 。以 JButton 为例,其 UI 代理的继承层次下图:

简单了解swing(JFrame)

package 数字图像化处理Swing;
​
import javax.swing.*;
import java.awt.*;
​
public class Text01 {
​
    public static void main(String[] args) {
    new Jframe().init();
    }
}
​
class Jframe extends JFrame {
    public void init() {
        setBounds(100, 100, 500, 500);
        JLabel jl=new JLabel("awt和swing在大多数方法声明上面差不多,加了一个J");
        jl.setHorizontalAlignment(SwingConstants.CENTER);//居中显示
        add(jl);
        //获得一个容器,容器里面的颜色才是真正的颜色
        Container con=this.getContentPane();
        con.setBackground(Color.yellow);
​
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    }
}
​
​

弹窗

JDialog,用来被弹出,默认就有关闭事件(原理就是单击事件之后出现另外一个窗口)

package 数字图像化处理Swing;
​
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
​
public class Text02 {
    public static void main(String[] args) {
new Mydailog();
    }
}
class Mydailog extends JFrame{
    public Mydailog(){
        setBounds(100,100,500,550);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        Container con=getContentPane();
        con.setBackground(Color.blue);
        //绝对布局
        con.setLayout(null);
        JButton jb=new JButton("点击开始游戏");
        jb.setBounds(150,150,200,80);
        con.add(jb);
        jb.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new Mytan();
            }
        });
    }
}
class Mytan extends JDialog{
   public Mytan(){
       this.setBounds(300,300,400,400);
       this.setVisible(true);
       Container con1=this.getContentPane();
       con1.setLayout(new FlowLayout());
       con1.add(new JLabel("恭喜你你通关了"));
   }
}

标签:

JLabel

JLabel jLabel = new JLabel("欢迎来到java-JFrame窗口",SwingConstants.CENTER);
//或者是jLabel.setHorizontalAlignment(SwingConstants.CENTER);//也是设置标签居中

图像标签ICON

import javax.swing.*;
import java.awt.*;
/**
 * Swing组件:标签(JLabel)设置图标标签
 */
public class JLabelIcon extends JFrame implements Icon{
    private int width;
    private int height;
    //无参构造
    public JLabelIcon(){
    }
    public JLabelIcon(int width, int height) throws HeadlessException {
        this.width = width;
        this.height = height;
    }
    public static void main(String[] args) {
        new JLabelIcon(15,15).init();
    }
    public void init(){
        JLabelIcon jLabelIcon = new JLabelIcon(10, 10);
        JLabel label = new JLabel("标签图标", jLabelIcon, SwingConstants.CENTER);//第二个参数为Icon实现类
        Container container = this.getContentPane();
        container.add(label);
        this.setBounds(10,10,400,800);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        //画笔  画图标  创建对象后,默认自动画一次
        g.fillOval(x,y,width,height);
    }
    @Override
    public int getIconWidth() {
        return this.width;
    }
    @Override
    public int getIconHeight() {
        return this.height;
    }
}

将图片添加到标签内ImageIcon:

import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
 * 将图片添加到标签内
 * 添加一个图片到标签中,需要ImageIcon类
 */
public class ImageIcon_ extends JFrame {
    public ImageIcon_(){
        //获取图片的地址
        JLabel label = new JLabel("ImageIcon",SwingConstants.CENTER);//设置标签的上下左右位置,所以是SwingConstants
        URL url = ImageIcon_.class.getResource("Mid-AutumnFestival.jpg");//找到ImageIcon_类的同级目录下的1.jpg文件
        ImageIcon imageIcon = new ImageIcon(url);
        label.setIcon(imageIcon);
        setVisible(true);
        setBounds(100,100,400,400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container container = getContentPane();
        container.add(label);
    }
    public static void main(String[] args) {
        new ImageIcon_();
    }
}

面板

JPanel

import javax.swing.*;
import java.awt.*;
/**
 * JPanel面板
 */
public class JPanel_ extends JFrame {
    public JPanel_() {
        Container container = this.getContentPane();
        container.setLayout(new GridLayout(2,1,10,10));
        JPanel panel1 = new JPanel(new GridLayout(1, 3));
        JPanel panel2 = new JPanel(new GridLayout(1, 2));
        JPanel panel3 = new JPanel(new GridLayout(2, 1));
        JPanel panel4 = new JPanel(new GridLayout(3,2));
        panel1.add(new JButton("1"));
        panel1.add(new JButton("1"));
        panel1.add(new JButton("1"));
        container.add(panel1);
        panel2.add(new JButton("2"));
        panel2.add(new JButton("2"));
        container.add(panel2);
        panel3.add(new JButton("3"));
        panel3.add(new JButton("3"));
        container.add(panel3);
        panel4.add(new JButton("4"));
        panel4.add(new JButton("4"));
        panel4.add(new JButton("4"));
        panel4.add(new JButton("4"));
        panel4.add(new JButton("4"));
        panel4.add(new JButton("4"));
        container.add(panel4);
        this.setVisible(true);
        this.setSize(800,800);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new JPanel_();
    }
}

滑动面板JScrollPane

import javax.swing.*;
import java.awt.*;
/**
 * JScrollPane:滑动面板 有滚动条
 */
public class JScrollPane_ extends JFrame {
    public JScrollPane_() throws HeadlessException {
        Container container = this.getContentPane();
        //设置一个文本域  文本域可以换行,而文本框不行
        JTextArea textArea = new JTextArea(20,30);//设置起始行列数,行列可变化
        textArea.setText("滑动面板的文本域,有换行功能");
        //JScrollPane面板
        JScrollPane scrollPane = new JScrollPane(textArea);
        container.add(scrollPane);
        setVisible(true);
        setBounds(100,100,200,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new JScrollPane_();
    }
}

练习

实现如图效果,并且给每个图片添加单击事件,并且在单击按钮事件中加入滑条

package 数字图像化处理Swing;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.net.URL;

import static javax.swing.SwingConstants.*;

public class Text03 {
    public static void main(String[] args) {
    new My03();
    }

}
class My03 extends JFrame{
    public My03(){
        setVisible(true);
        Container con=getContentPane();
        JPanel panel1 = new JPanel(new GridLayout(1, 3));
        con.setLayout(new GridLayout(2,1,10,10));
        JButton jb1=new JButton("单击");
        JButton jb2=new JButton("不要点击");
        JButton jb3=new JButton("不要点击");
        JButton jb4=new JButton("不要点击");
        jb1.setBounds(200,200,100,100);
        con.add(jb1);
        con.setVisible(true);
       setBounds(1001,100,1000,1000);
        panel1.add(jb2);
        panel1.add(jb3);
        panel1.add(jb4);
        add(panel1);
       setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jb2.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new My0302();
            }
        });
        jb3.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new My0302();
            }
        });
        jb4.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new My0302();
            }
        });
       jb1.addActionListener(new AbstractAction() {
           @Override
           public void actionPerformed(ActionEvent e) {
               new My0301();
           }
       });
    }
}
class My0302 extends JFrame{
    public My0302(){
        setVisible(true);
        Container con=getContentPane();
        setBounds(100,100,500,500);
        JLabel jl=new JLabel("说了不要点击还点");
        jl.setHorizontalAlignment(CENTER);
        con.add(jl);
        con.setBackground(Color.yellow);
    }

}
class My0301 extends JFrame{
    public My0301(){
        JLabel jl=new JLabel("ImageIcon", CENTER);
        Container con=getContentPane();
        URL ur=Text03.class.getResource("小球结束.png");
        ImageIcon im=new ImageIcon(ur);
        jl.setIcon(im);
        setVisible(true);
        setBounds(100,100,500,500);
        con.add(jl);
        JScrollPane scrollPane = new JScrollPane(jl);
        con.add(scrollPane);
        setDefaultCloseOperation(CENTER);
            }
}

按钮

图标按钮:把一个图标变为一个按钮图标

import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
 * 设置一个图标按钮  加了一个按钮提示方法
 * 将一个图片变为图标
 */
public class JButtonIcon extends JFrame {
    public JButtonIcon() throws HeadlessException {
        Container container = getContentPane();
        //将一个图片变成图标
        URL url = JButtonIcon.class.getResource("Mid-AutumnFestival.jpg");
        ImageIcon imageIcon = new ImageIcon(url);
        //把这个图标放在按钮上
        JButton button = new JButton();
        button.setIcon(imageIcon);
        button.setToolTipText("图片按钮");//设置提示按钮,鼠标放到按钮上面会弹出一些提示
        container.add(button);
        setSize(500,300);
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new JButtonIcon();
    }
}
  • 单选按钮

    将按钮添加进来,不是将组添加进来!! 要设置按钮的位置,避免出现覆盖现象,没有设置组的话,就是多选

    import javax.swing.*;
    import java.awt.*;
    import java.net.URL;
    /**
     * 单选按钮  JRadioButton
     * 需要用到一个组,ButtonGroup
     */
    public class JButton_1 extends JFrame {
        public void init(){
            Container container = getContentPane();
            //单选框
            JRadioButton radioButton1 = new JRadioButton("JRdioButton1");
            JRadioButton radioButton2 = new JRadioButton("JRdioButton2");
            JRadioButton radioButton3 = new JRadioButton("JRdioButton3");
            //由于是单选框,需要准备一个组,让三个按钮在这组内实现单选功能
            ButtonGroup group = new ButtonGroup();
            group.add(radioButton1);
            group.add(radioButton2);
            group.add(radioButton3);
            //将按钮添加进来,不是将组添加进来!! 要设置按钮的位置,避免出现覆盖现象
            container.add(radioButton1,BorderLayout.NORTH);
            container.add(radioButton2,BorderLayout.CENTER);
            container.add(radioButton3,BorderLayout.SOUTH);
            setSize(500,300);
            setVisible(true);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        public static void main(String[] args) {
            new JButton_1().init();
        }
    }

  • 复选按钮

    import javax.swing.*;
    import java.awt.*;
    /**
     * 多选框 JCheckBox
     */
    public class JButton_2 extends JFrame {
        public void init(){
            Container container = getContentPane();
            //多选框
            JCheckBox checkBox1 = new JCheckBox("checkBox1");
            JCheckBox checkBox2 = new JCheckBox("checkBox2");
            JCheckBox checkBox3 = new JCheckBox("checkBox3");
            container.add(checkBox1,BorderLayout.NORTH);
            container.add(checkBox2,BorderLayout.CENTER);
            container.add(checkBox3,BorderLayout.SOUTH);
            setSize(500,300);
            setVisible(true);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        public static void main(String[] args) {
            new JButton_2().init();
        }
    }

3.6 列表

  • 下拉框

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    /**
     * 下拉框  JComboBox类
     * System.out.println(comboBox.getSelectedIndex());//返回当前项的位置
     * System.out.println(comboBox.getSelectedItem());//返回当前项的内容
     */
    public class Combobox_1 extends JFrame {
        public Combobox_1() {
            Container container = this.getContentPane();
            JComboBox comboBox = new JComboBox();
            comboBox.addItem("正在热映");
            comboBox.addItem(null);
            comboBox.addItem("即将上映");
            comboBox.addItem("下架");
            container.add(comboBox,BorderLayout.NORTH);
            comboBox.addActionListener(new MyActionListener());
            this.setVisible(true);
            this.setBounds(100,100,400,400);
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        public static void main(String[] args) {
            new Combobox_1();
        }
    }
    class MyActionListener implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            JComboBox comboBox=(JComboBox)e.getSource();
            System.out.println(comboBox.getSelectedIndex());//返回当前项的位置
            System.out.println(comboBox.getSelectedItem());//返回当前项的内容
        }
    }

  • 列表框

    import javax.swing.*;
    import java.awt.*;
    import java.util.Vector;
    /**
     * 列表框  JList
     * 每一项都是可点击的
     */
    public class Combobox_2 extends JFrame {
        public Combobox_2() {
            Container container = this.getContentPane();
            //生成列表的内容
            //String[] contents={"Item1","Item2","Item3"};//静态变量
            Vector vector = new Vector();//动态变量 动态集合
            JList list = new JList(vector);//列表中需要放入内容
            container.add(list);
            vector.add("正在热映");
            vector.add(null);//与下拉框不同,动态集合自动排除掉为空的内容,所以这里显示的就两个项
            vector.add("已下架");
            this.setVisible(true);
            this.setBounds(100,100,400,400);
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        public static void main(String[] args) {
            new Combobox_2();
        }
    }

  • 应用场景

    • 选择地区,或者一些单个选项 (选项只有两个的话建议使用单选框,三个以上建议使用下拉框)

    • 列表框,展示项信息,一般都是动态扩容

3.7 文本框

  • 文本框:

import javax.swing.*;
import java.awt.*;
/**
 * 文本框 JTextField
 * 不能换行
 */
public class JTextField_ extends JFrame {
    public JTextField_() throws HeadlessException {
        Container container = this.getContentPane();
        //设置文本框 不能换行
        JTextField textField1 = new JTextField("hello");//设置初始内容
        JTextField textField2 = new JTextField("world",20);//设置起始字符数,行列可变化
        container.add(textField1,BorderLayout.NORTH);
        container.add(textField2,BorderLayout.SOUTH);
        setVisible(true);
        setBounds(100,100,200,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new JTextField_();
    }
}
  • 密码框

    import javax.swing.*;
    import java.awt.*;
    /**
     * 密码框 JPasswordField 在文本框中用一个字符隐藏内容
     * 是JTextField的子类  默认是一个小黑点隐藏
     */
    public class JPasswordField_ extends JFrame {
        public JPasswordField_() throws HeadlessException {
            Container container = this.getContentPane();
            //设置文本框 不能换行
            JPasswordField passwordField = new JPasswordField("hello");//设置初始内容
            passwordField.setEchoChar('*');//设置用一个字符代替输入字符,实现隐藏作用
            System.out.println(passwordField.getPassword());//得到文本框的内容
            container.add(passwordField,BorderLayout.NORTH);
            setVisible(true);
            setBounds(100,100,200,300);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        public static void main(String[] args) {
            new JPasswordField_();
        }
    }

  • 文本域

import javax.swing.*;
import java.awt.*;
/**
 * 文本域
 */
public class JTextArea_ extends JFrame {
    public JTextArea_() throws HeadlessException {
        Container container = this.getContentPane();
        //设置一个文本域  文本域可以换行,而文本框不行
        JTextArea textArea = new JTextArea(20,30);//设置起始行列数,行列可变化
        textArea.setText("滑动面板的文本域,有换行功能");
        //JScrollPane面板
        JScrollPane scrollPane = new JScrollPane(textArea);
        container.add(scrollPane);
        setVisible(true);
        setBounds(100,100,200,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new JTextArea_();
    }
}

JTree、TreeModel实现树

树也是图形用户界面中使用非常广泛的 GUI 组件,例如使用 Windows 资源管理器时,将看到如下图所示的目录树:

如上图所示的树,代表计算机世界里的树,它从自然界实际的树抽象而来 。 计算机世界里的树是由一系列具有严格父子关系的节点组成的,每个节点既可以是其上一级节点的子节点,也可以是其下一级节点的父节点,因此同一个节点既可以是父节点,也可以是子节点(类似于一个人,他既是他儿子的父亲,又是他父亲的儿子)。

按照结点是否包含子结点,可以把结点分为下面两类:

普通结点:包含子结点的结点;

叶子结点:没有子结点的结点;

按照结点是否具有唯一的父结点,可以把结点分为下面两类:

根结点:没有父结点的结点,计算机中,一棵树只能有一个根结点

普通结点:具有唯一父结点的结点

使用 Swing 里的 Jtree 、 TreeModel 及其相关的辅助类可以很轻松地开发出计算机世界里的树。

创建树

Swing 使用 JTree 对 象来代表一棵树,JTree 树中结点可以使用 TreePath 来标识,该对象封装了当前结点及其所有的父结点。

当一个结点具有子结点时,该结点有两种状态:

展开状态:当父结点处于展开状态时,其子结点是可见的;

折叠状态: 当父结点处于折叠状态时,其子结点都是不可见的 。

如果某个结点是可见的,则该结点的父结点(包括直接的、间接的父结点)都必须处于展开状态,只要有任意一个父结点处于折叠状态,该结点就是不可见的 。

JTree常用构造方法:

JTree(TreeModel newModel):使用指定 的数据模型创建 JTree 对象,它默认显示根结点。
JTree(TreeNode root): 使用 root 作为根节 点创建 JTree 对象,它默认显示根结点 。
JTree(TreeNode root, boolean asksAllowsChildren): 使用root作为根结点创建JTree对象,它默认显示根结点。 asksAllowsChildren 参数控制怎样的结点才算叶子结点,如果该参数为 true ,则只有当程序使用 setAllowsChildren(false)显式设置某个结点不允许添加子结点时(以后也不会拥有子结点) ,该结点才会被 JTree 当成叶子结点:如果该参数为 false ,则只要某个结点当时没有子结点(不管以后是否拥有子结点) ,该结点都会被 JTree 当成叶子结点。

TreeNode继承体系及使用:

在构建目录树时,可以先创建很多DefaultMutableTreeNode对象,并调用他们的add方法构建好子父级结构,最后根据根结点构建一个JTree即可。

案例:

使用JTree和TreeNode完成下图效果:

演示代码:

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class SimpleJTree {
    JFrame jf = new JFrame("简单树");

    JTree tree;
    DefaultMutableTreeNode root;
    DefaultMutableTreeNode guangdong;
    DefaultMutableTreeNode guangxi;
    DefaultMutableTreeNode foshan;
    DefaultMutableTreeNode shantou;
    DefaultMutableTreeNode guilin;
    DefaultMutableTreeNode nanning;

    public void init(){
        //依次创建所有结点
        root = new DefaultMutableTreeNode("中国");
        guangdong = new DefaultMutableTreeNode("广东");
        guangxi = new DefaultMutableTreeNode("广西");
        foshan = new DefaultMutableTreeNode("佛山");
        shantou = new DefaultMutableTreeNode("汕头");
        guilin = new DefaultMutableTreeNode("桂林");
        nanning = new DefaultMutableTreeNode("南宁");


        //通过add()方法建立父子层级关系
        guangdong.add(foshan);
        guangdong.add(shantou);
        guangxi.add(guilin);
        guangxi.add(nanning);
        root.add(guangdong);
        root.add(guangxi);

        //依据根结点,创建JTree
        tree = new JTree(root);

        jf.add(new JScrollPane(tree));
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setVisible(true);

    }


    public static void main(String[] args) {
        new SimpleJTree().init();
    }
}

JTree的其他外观设置方法:

tree.putClientProperty( "JTree.lineStyle", "None"):设置结点之间没有连接线
tree.putClientProperty("JTree.lineStyle" , "Horizontal"):设置结点之间只有水平分割线
tree.setShowsRootHandles(true):设置根结点有"展开、折叠"图标
tree.setRootVisible(false):隐藏根结点

DefaultMutableTreeNode其他成员方法:

Enumeration breadthFirstEnumerationO/preorderEnumeration(): 按广度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象 。
Enumeration depthFirstEnumerationO/postorderEnumeration(): 按深度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象 。
DefaultMutableTreeNode getNextSibling(): 返回此结点的下一个兄弟结点 。
TreeNode getParent(): 返回此结点的父结点 。 如果此结点没有父结点,则返回null 。
TreeNode[] getPath(): 返回从根结点到达此结点的所有结点组成的数组。
DefaultMutableTreeNode getPreviousSibling(): 返回此结点的上一个兄弟结点。
TreeNode getRoot(): 返回包含此结点的树的根结点 。
TreeNode getSharedAncestor(DefaultMutableTreeNode aNode): 返回此结点和aNode最近的共同祖先 。
int getSiblingCount(): 返回此结点的兄弟结点数 。
boolean isLeaf(): 返回该结点是否是叶子结点 。
boolean isNodeAncestor(TreeNode anotherNode): 判断anotherNode是否是当前结点的祖先结点(包括父结点) 。
boolean isNodeChild(TreeNode aNode): 如果aNode是此结点的子结点,则返回true。
boolean isNodeDescendant(DefaultMutableTreeNode anotherNode): 如果 anotherNode 是此结点的后代,包括是此结点本身、此结点的子结点或此结点的子结点的后代,都将返回true 。
boolean isNodeRelated(DefaultMutableTreeNode aNode) : 当aNode和当前结点位于同一棵树中时返回 true 。
boolean isNodeSibling(TreeNode anotherNode): 返回anotherNode是否是当前结点的兄弟结点 。
boolean isRoot(): 返回当前结点是否是根结点 。
Enumeration pathFromAncestorEnumeration(TreeNode ancestor): 返回从指定祖先结点到当前结点的所有结点组成的枚举对象 。

拖动、编辑树结点

JTree 生成的树默认是不可编辑的,不可以添加、删除结点,也不可以改变结点数据 :如果想让某个 JTree 对象变成可编辑状态,则可以调用 JTree 的setEditable(boolean b)方法,传入 true 即可把这棵树变成可编辑的树(可以添加、删除结点,也可以改变结点数据) 。

编辑树结点的步骤:

  1. 获取当前被选中的结点:

获取当前被选中的结点,会有两种方式:
	一:
		通过JTree对象的某些方法,例如 TreePath getSelectionPath()等,得到一个TreePath对象,包含了从根结点到当前结点路径上的所有结点;
		调用TreePath对象的 Object getLastPathComponent()方法,得到当前选中结点
	二:
		调用JTree对象的 Object getLastSelectedPathComponent() 方法获取当前被选中的结点
  1. 调用DefaultTreeModel数据模型有关增删改的一系列方法完成编辑,方法执行完后,会自动重绘JTree

案例:

使用JTree以及DefaultTreeModel、DefaultMutableTreeNode、TreePath完成下图效果:

演示代码:

import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class EditTree {

    JFrame jf ;
    JTree tree;

    //JTree关联的数据模型对象
    DefaultTreeModel model;

    //定义几个初始结点
    DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
    DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
    DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
    DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
    DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
    DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
    DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");

    //定义需要被拖动的TreePath
    TreePath movePath;

    //定义按钮,完成操作
    JButton addSiblingBtn = new JButton("添加兄弟结点");
    JButton addChildBtn = new JButton("添加子结点");
    JButton deleteBtn = new JButton("删除结点");
    JButton editBtn = new JButton("编辑当前结点");

    public void init(){

        //通过add()方法建立父子层级关系
        guangdong.add(foshan);
        guangdong.add(shantou);
        guangxi.add(guilin);
        guangxi.add(nanning);
        root.add(guangdong);
        root.add(guangxi);

        jf = new JFrame("可编辑结点的树");
        tree = new JTree(root);

        //获取JTree关联的数据模型TreeModel对象
        model = (DefaultTreeModel) tree.getModel();

        //设置JTree可编辑
        tree.setEditable(true);

        //创建鼠标事件监听器
        MouseListener ml = new MouseAdapter() {

            //按下鼠标时,获得被拖动的结点
            @Override
            public void mousePressed(MouseEvent e) {
                //如果需要唯一确定某个结点,则必须通过TreePath来获取
                TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
                if (tp!=null){
                    movePath = tp;
                }
            }

            //松开树表示,确定即将被拖入到的父结点
            @Override
            public void mouseReleased(MouseEvent e) {

                TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
                if (tp!=null && movePath!=null){
                    //阻止向子结点拖动
                    if (movePath.isDescendant(tp) && movePath!=tp){
                        JOptionPane.showMessageDialog(jf,"目标结点是被移动结点的子结点,无法移动!","非法移动",JOptionPane.WARNING_MESSAGE);
                    }
                    //不是向子结点移动,并且鼠标按下和松开也不是同一个结点

                    if (movePath!=tp){
                        //add方法内部,先将该结点从原父结点删除,然后再把该结点添加到新结点中
                        DefaultMutableTreeNode tartParentNode = (DefaultMutableTreeNode) tp.getLastPathComponent();
                        DefaultMutableTreeNode moveNode = (DefaultMutableTreeNode) movePath.getLastPathComponent();
                        tartParentNode.add(moveNode);

                        movePath=null;
                        tree.updateUI();
                    }
                }
            }
        };

        //为JTree添加鼠标监听器
        tree.addMouseListener(ml);

        JPanel panel = new JPanel();

        addSiblingBtn.addActionListener(e -> {
            //获取选中结点
            DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();

            //如果结点为空,则直接返回
            if (selectedNode==null){
                return;
            }

            //获取该选中结点的父结点
            DefaultMutableTreeNode parent = (DefaultMutableTreeNode) selectedNode.getParent();

            //如果父结点为空,则直接返回
            if (parent==null){
                return;
            }

            //创建一个新结点
            DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新结点");

            //获取选中结点的索引
            int selectedIndex = parent.getIndex(selectedNode);

            //在选中位置插入新结点
            model.insertNodeInto(newNode,parent,selectedIndex);

            //----------显示新结点---------------
            //获取从根结点到新结点的所有结点
            TreeNode[] pathToRoot = model.getPathToRoot(newNode);

            //使用指定的结点数组创建TreePath
            TreePath treePath = new TreePath(pathToRoot);

            //显示指定的treePath
            tree.scrollPathToVisible(treePath);


        });

        panel.add(addSiblingBtn);

        addChildBtn.addActionListener(e -> {
            //获取选中结点
            DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();

            if (selectedNode==null){
                return ;
            }

            //创建新结点
            DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新结点");

            //model.insertNodeInto(newNode,selectedNode,selectedNode.getChildCount());使用TreeModel的方法添加,不需要手动刷新UI
            selectedNode.add(newNode);//使用TreeNode的方法添加,需要手动刷新UI

            //显示新结点

            TreeNode[] pathToRoot = model.getPathToRoot(newNode);
            TreePath treePath = new TreePath(pathToRoot);
            tree.scrollPathToVisible(treePath);

            //手动刷新UI
            tree.updateUI();

        });

        panel.add(addChildBtn);

        deleteBtn.addActionListener(e -> {

            DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();

            if (selectedNode!=null && selectedNode.getParent()!=null){
                model.removeNodeFromParent(selectedNode);
            }

        });

        panel.add(deleteBtn);

        //实现编辑结点的监听器
        editBtn.addActionListener(e -> {

            TreePath selectionPath = tree.getSelectionPath();

            if (selectionPath!=null){
                //编辑选中结点
                tree.startEditingAtPath(selectionPath);
            }

        });

        panel.add(editBtn);

        jf.add(new JScrollPane(tree));
        jf.add(panel, BorderLayout.SOUTH);

        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setVisible(true);

    }

    public static void main(String[] args) {
        new EditTree().init();
    }
}

监听结点事件

修改JTree的选择模式:

JTree 专门提供了 一个 TreeSelectionModel 对象来保存该 JTree 选中状态的信息 。 也就是说,JTree组件背后隐藏了两个 model 对象 , 其中TreeModel 用于保存该 JTree 的所有节点数据 , 而TreeSelectionModel 用于保存该 JTree的所有选中状态的信息 。

程序可以改变 JTree 的选择模式 , 但必须先获取该 JTree 对应的 TreeSelectionMode1 对象 , 再调用该对象的 setSelectionMode(int mode);方法来设置该JTree的选择模式 ,其中model可以有如下3种取值:

  1. TreeSelectionModel.CONTIGUOUS_TREE_SELECTION: 可 以连续选中多个 TreePath 。

  2. TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : 该选项对于选择没有任何限制 。

  3. TreeSelectionModel.SINGLE_TREE_SELECTION: 每次只能选择一个 TreePath 。

为JTree添加监听器:

  1. addTreeExpansionListener(TreeExpansionListener tel) : 添加树节点展开/折叠事件的监听器。

  2. addTreeSelectionListener(TreeSelectionListener tsl) : 添加树节点选择事件的监听器。

案例:

实现下图效果:

演示代码:

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class SelectJTree {
    JFrame jf = new JFrame("监听树的选择事件");

    JTree tree;

    DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
    DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
    DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
    DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
    DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
    DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
    DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");

    JTextArea eventTxt = new JTextArea(5, 20);

    public void init() {

        //通过add()方法建立父子层级关系
        guangdong.add(foshan);
        guangdong.add(shantou);
        guangxi.add(guilin);
        guangxi.add(nanning);
        root.add(guangdong);
        root.add(guangxi);

        tree = new JTree(root);

        //添加监听器
        tree.addTreeSelectionListener(e -> {
            if (e.getOldLeadSelectionPath()!=null){
                eventTxt.append("原选中结点的路径:"+e.getOldLeadSelectionPath().toString()+"\n");
            }

            eventTxt.append("新选中结点的路径:"+e.getNewLeadSelectionPath().toString()+"\n");
        });

        tree.setShowsRootHandles(true);
        tree.setRootVisible(true);

        Box box = Box.createHorizontalBox();
        box.add(new JScrollPane(tree));
        box.add(new JScrollPane(eventTxt));

        jf.add(box);
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);
    }

    public static void main(String[] args) {
        new SelectJTree().init();
    }
}

使用DefaultTreeCellRenderer改变结点外观

JTree默认的外观是比较单一的,它提供了如下几种改变结点外观的方式:

  1. 使用 DefaultTreeCellRenderer 直接改变节点的外观,这种方式可 以 改变整棵树所有节点 的字体、颜色和图标 。

  2. 为 JTree 指定 DefaultTreeCellRenderer 的扩展类对象作为 JTree 的节点绘制器,该绘制器负责为不同节点使用不同的字体、颜色和图标。通常使用这种方式来改变节点的外观 。

  3. 为 JTree 指定一个实现 TreeCellRenderer 接口的节点绘制器,该绘制器可以为不同的节点自由绘制任意内容,这是最复杂但最灵活的节点绘制器 。

第 一种方式最简单 , 但灵活性最差 ,因为它会改变整棵树所有节点的外观 。 在这种情况下 , Jtree的所有节点依然使用相同的图标 ,相当于整体替换了 Jtree 中 节点的所有默认图标 。 用户指定 的节点图标未必就比 JTree 默认的图标美观 。

DefaultTreeCellRenderer 提供了如下几个方法来修改节点的外观:

setBackgroundNonSelectionColor(Color newColor): 设置用于非选定节点的背景颜色 。
setBackgroundSelectionColor(Color newColor): 设置节点在选中状态下的背景颜色 。
setBorderSelectionColor(Color newColor): 设置选中状态下节点的边框颜色 。
setClosedIcon(Icon newIcon): 设置处于折叠状态下非叶子节点的图标 。
setFont(Font font) : 设置节点文本 的 字体。
setLeaflcon(Icon newIcon): 设置叶子节点的图标 。
setOpenlcon(Icon newlcon): 设置处于展开状态下非叶子节 点的图标。
setTextNonSelectionColor(Color newColor): 设置绘制非选中状态下节点文本的颜色 。
setTextSelectionColor(Color newColor): 设置绘制选中状态下节点文本的颜色 。

案例:

使用DefaultTreeCellRenderer完成下图效果:

演示代码:

import cn.itcast.swing.util.ImagePathUtil;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;

public class ChangeAllCellRender {

    JFrame jf = new JFrame("改变所有结点外观");

    JTree tree;

    DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
    DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
    DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
    DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
    DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
    DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
    DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");

    public void init(){
        //通过add()方法建立父子层级关系
        guangdong.add(foshan);
        guangdong.add(shantou);
        guangxi.add(guilin);
        guangxi.add(nanning);
        root.add(guangdong);
        root.add(guangxi);

        tree = new JTree(root);

        //创建一个DefaultTreeCellRenderer对象
        DefaultTreeCellRenderer cellRenderer = new DefaultTreeCellRenderer();
        //设置非选定结点的背景颜色
        cellRenderer.setBackgroundNonSelectionColor(new Color(220,220,220));
        //设置选中结点的背景色
        cellRenderer.setBackgroundSelectionColor(new Color(140,140,140));
        //设置选中状态下结点的边框颜色
        cellRenderer.setBorderSelectionColor(Color.BLACK);
        //设置处于折叠状态下非叶子结点的图标
        cellRenderer.setClosedIcon(new ImageIcon(ImagePathUtil.getRealPath("10\\close.gif")));
        //设置结点文本的字体
        cellRenderer.setFont(new Font("SansSerif",Font.BOLD,16));
        //设置叶子结点图标
        cellRenderer.setLeafIcon(new ImageIcon(ImagePathUtil.getRealPath("10\\leaf.png")));
        //设置处于展开状态下非叶子结点图标跑
        cellRenderer.setOpenIcon(new ImageIcon(ImagePathUtil.getRealPath("10\\open.gif")));
        //设置绘制非选中状态下结点文本颜色
        cellRenderer.setTextNonSelectionColor(new Color(255,0,0));
        //设置选中状态下结点的文本颜色
        cellRenderer.setTextSelectionColor(new Color(0,0,255));

        tree.setCellRenderer(cellRenderer);

        tree.setShowsRootHandles(true);

        tree.setRootVisible(true);

        jf.add(new JScrollPane(tree));
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);

    }


    public static void main(String[] args) {
        new ChangeAllCellRender().init();
    }

}

扩展DefaultTreeCellRenderer改变结点外观

DefaultTreeCellRenderer 实现类实现了TreeCellRenderer接口,该接口里只有 一个用于绘制节点内容的方法: getTreeCellRendererComponent() , 该方法负责绘制 JTree 节点 。学习JList的时候,如果要绘制JList的列表项外观的内容,需要实现ListCellRenderer 接口,通过重写getTreeCellRendererComponent()方法返回一个Component 对象 , 该对象就是 JTree 的节点组件 。两者之间非常类似

DefaultTreeCellRende rer 类继承了JLabel,实现 getTreeCellRendererComponent()方法时返回 this ,即返回一个特殊的 JLabel 对象 。 如果需要根据节点内容来改变节点的外观,则可以再次扩展DefaultTreeCellRenderer 类,并再次重写它提供的 getTreeCellRendererComponent()方法。

案例:

自定义类继承DefaultTreeCellRenderer,重写getTreeCellRendererComponent()方法,实现下图效果:

演示代码:

import cn.itcast.swing.util.ImagePathUtil;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;

public class ExtendsDefaultCellTreeRenderer {

    JFrame jf = new JFrame("根据结点类型定义图标");

    JTree tree;

    //定义几个初始结点
    DefaultMutableTreeNode root = new DefaultMutableTreeNode(new NodeData(DBObjectType.ROOT,"数据库导航"));
    DefaultMutableTreeNode salaryDb = new DefaultMutableTreeNode(new NodeData(DBObjectType.DATABASE,"公司工资数据库"));
    DefaultMutableTreeNode customerDb = new DefaultMutableTreeNode(new NodeData(DBObjectType.DATABASE,"公司客户数据库"));
    DefaultMutableTreeNode employee = new DefaultMutableTreeNode(new NodeData(DBObjectType.TABLE,"员工表"));
    DefaultMutableTreeNode attend = new DefaultMutableTreeNode(new NodeData(DBObjectType.TABLE,"考勤表"));
    DefaultMutableTreeNode concat = new DefaultMutableTreeNode(new NodeData(DBObjectType.TABLE,"联系方式表"));
    DefaultMutableTreeNode id = new DefaultMutableTreeNode(new NodeData(DBObjectType.INDEX,"员工ID"));
    DefaultMutableTreeNode name = new DefaultMutableTreeNode(new NodeData(DBObjectType.COLUMN,"姓名"));
    DefaultMutableTreeNode gender = new DefaultMutableTreeNode(new NodeData(DBObjectType.COLUMN,"性别"));

    public void init(){
        //通过结点的add方法,建立结点的父子关系

        root.add(salaryDb);
        root.add(customerDb);

        salaryDb.add(employee);
        salaryDb.add(attend);

        customerDb.add(concat);

        concat.add(id);
        concat.add(name);
        concat.add(gender);

        tree = new JTree(root);

        tree.setCellRenderer(new MyRenderer());

        tree.setShowsRootHandles(true);
        tree.setRootVisible(true);

        //设置使用windows外观风格
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (Exception e) {
            e.printStackTrace();
        }

        //更新JTree的UI外观
        SwingUtilities.updateComponentTreeUI(tree);

        jf.add(new JScrollPane(tree));
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);

    }

    public static void main(String[] args) {
        new ExtendsDefaultCellTreeRenderer().init();
    }

    class MyRenderer extends DefaultTreeCellRenderer{
        //初始化5个图标
        ImageIcon rootIcon = new ImageIcon(ImagePathUtil.getRealPath("10\\root.gif"));
        ImageIcon databaseIcon = new ImageIcon(ImagePathUtil.getRealPath("10\\database.gif"));
        ImageIcon tableIcon = new ImageIcon(ImagePathUtil.getRealPath("10\\table.gif"));
        ImageIcon columnIcon = new ImageIcon(ImagePathUtil.getRealPath("10\\column.gif"));
        ImageIcon indexIcon = new ImageIcon(ImagePathUtil.getRealPath("10\\index.gif"));

        @Override
        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);
            DefaultMutableTreeNode  node = (DefaultMutableTreeNode) value;

            NodeData data = (NodeData) node.getUserObject();
            //根据结点数据中的nodeType决定结点的图标
            ImageIcon icon = null;
            switch (data.nodeType){
                case DBObjectType.ROOT:
                    icon = rootIcon;
                    break;
                case DBObjectType.DATABASE:
                    icon = databaseIcon;
                    break;
                case DBObjectType.TABLE:
                    icon = tableIcon;
                    break;
                case DBObjectType.COLUMN:
                    icon = columnIcon;
                    break;
                case DBObjectType.INDEX:
                    icon = indexIcon;
                    break;
            }

            //改变图标
            this.setIcon(icon);

            return this;
        }
    }


    //定义一个NodeData类,用于封装结点数据
    class NodeData{
        public int nodeType;
        public String nodeData;

        public NodeData(int nodeType, String nodeData) {
            this.nodeType = nodeType;
            this.nodeData = nodeData;
        }

        @Override
        public String toString() {
            return this.nodeData;
        }
    }

    //定义一个接口,该接口里包含数据库对象类型的常量
    interface  DBObjectType{
        int ROOT=0;
        int DATABASE=1;
        int TABLE=2;
        int COLUMN=3;
        int INDEX=4;
    }

}

实现TreeCellRenderer接口改变结点外观

这种改变结点外观的方式是最灵活的,程序实现TreeCellRenderer接口时同样需要实现getTreeCellRendererComponent()方法,该方法可以返回任意类型的组件,该组件将作为JTree的结点。通过这种方式可以最大程度的改变结点的外观。

案例:

自定义类,继承JPanel类,实现TreeCellRenderer接口,完成下图效果:

演示代码:

import cn.itcast.swing.util.ImagePathUtil;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import java.awt.*;

public class CustomerTreeNode {

    JFrame jf = new JFrame("定制树的结点");

    JTree tree;

    //定义几个初始结点

    DefaultMutableTreeNode friends = new DefaultMutableTreeNode("我的好友");
    DefaultMutableTreeNode qingzhao = new DefaultMutableTreeNode("李清照");
    DefaultMutableTreeNode suge = new DefaultMutableTreeNode("苏格拉底");
    DefaultMutableTreeNode libai = new DefaultMutableTreeNode("李白");
    DefaultMutableTreeNode nongyu = new DefaultMutableTreeNode("弄玉");
    DefaultMutableTreeNode hutou = new DefaultMutableTreeNode("虎头");

    public void init() {

        friends.add(qingzhao);
        friends.add(suge);
        friends.add(libai);
        friends.add(nongyu);
        friends.add(hutou);

        tree = new JTree(friends);

        tree.setShowsRootHandles(true);
        tree.setRootVisible(true);


        tree.setCellRenderer(new ImageCellRenderer());



        jf.add(new JScrollPane(tree));
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);

    }

    public static void main(String[] args) {
        new CustomerTreeNode().init();
    }


    class ImageCellRenderer extends JPanel implements TreeCellRenderer {

        private ImageIcon icon;
        private String name;
        //定义绘制单元格时的背景色
        private Color background;
        //定义绘制单元格时的前景色
        private Color foreground;


        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            icon = new ImageIcon(ImagePathUtil.getRealPath("10\\" + value + ".gif"));

            name = value.toString();

            background = hasFocus ? new Color(140, 200, 235) : new Color(255, 255, 255);
            foreground = hasFocus ? new Color(255,255,3) : new Color(0,0,0);
            //返回当前JPanel作为结点
            return this;
        }

        //重写paintComponent改变JPanel的外观

        @Override
        protected void paintComponent(Graphics g) {
            int imageWidth = icon.getImage().getWidth(null);
            int imageHeight = icon.getImage().getHeight(null);
            g.setColor(background);
            g.fillRect(0,0,getWidth(),getHeight());

            g.setColor(foreground);
            //绘制好友图标
            g.drawImage(icon.getImage(),getWidth()/2-imageWidth/2,10,null);

            //绘制好友姓名
            g.setFont(new Font("SansSerif",Font.BOLD,18));

            g.drawString(name,getWidth()/2-name.length()*10,imageHeight+30);

        }


        //设置当前组件结点最佳大小
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(80,80);
        }
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于我写了三万字博客后悔了好久这件事之第二个三万字GUI(swing) 的相关文章

随机推荐

  • 11、Seata AT+SpringCloud+MyBatisPlus+Nacos进行分布式事务操作

    具体请看 http blog shangsw com articles 2020 03 28 1585382453622 html
  • Unity通用框架搭建(八)—— 消息事件封装

    在游戏开发中 消息事件通过委托实现 实际开发中 经常会遇到界面之间 玩家之间的相互通信 本文实现的消息事件封装采用的时监听和分发机制 它主要运用在逻辑判断 减少程序之间的耦合度 而且使用起来非常方便 监听服务器消息也可以运用事件监听机制 收
  • 怎么在matlab里分析房价,MATLAB线性回归算法浅析赤(chǐ)峰房价

    每日一句 房子不是你想买想买就能买 基础介绍 线性回归算法是处理回归问题的常用方法 下面就线性回归算法来做一个简单的模型 通过走势模拟房子售价 通过数据样本的基本绘图 可以知道某个现象是不是一个线性的分布过程 并且按一次线性方程分布 那么就
  • 关于Vue

    vue js 与vue runtime xxx js的区别 1 vue js是完整版的Vue 包含 核心功能 模板解析器 2 vue runtihe xxx js是运行版的Vue 只包含 核心功能 没有模板解析器 因为vue runtime
  • 面向对象基本思想概述

    面向对象的基本思想 面向对象是一种新兴的程序设计方法 或者是一种新的程序设计规范 paradigm 其基本思想是使用对象 类 继承 封装 消息等基本概念来进行程序设计 从现实世界中客观存在的事物 即对象 出发来构造软件系统 并且在系统构造中
  • Typora图床配置详细教程

    Typora图床配置详细教程 从读研开始 慢慢有了写博客的习惯 刚开始直接在csdn上写 后来还是觉得不太方便 有时候上网课记笔记还要另外专门打开一个网页来回切换 于是就改用Typora 不得不说真香 不幸的是 Typora后来走上了收费的
  • JavaScript基础标准库总结——(1)

    一标准库对象 一共9个 1 Object 键值对混合对象 2 Array 数组 String 字符串 3 String 字符串 4 NUmber 数字 5 Boolean 布尔值 6 Math 数学 7 Date 日期 8 JSON 9 R
  • TailwindCSS 3.0 (二) 设置背景颜色和背景图片的方法

    设置背景颜色 background color 1 背景颜色 Background Color 基础用法 透明度 自定义主题颜色 其他用法 2 渐变颜色 基础用法 渐变色标 基础用法 其他用法 3 背景图片 Background Image
  • VisualStudio代码格式化方法

    有时我们从其他地方复制过来的代码当粘贴到VS中时 代码缩进的格式已经没有了 这样看代码十分不直观 自己重新排版又太慢 还好VS中已经为我们提供了自动格式化的功能 下面是格式化的几种方法 VS2008即以后的各个版本通用 1 选中需要格式化的
  • java对象复制_Java对象的复制三种方式

    Java对象的复制三种方式 概述 在实际编程过程中 我们常常要遇到这种情况 有一个对象A 在某一时刻A中已经包含了一些有效值 此时可能 会需要一个和A完全相同新对象B 并且此后对B任何改动都不会影响到A中的值 也就是说 A与B是两个独立的对
  • 让你久等了《开源安全运维平台OSSIM疑难解析--入门篇》正式出版

    2019年暑期 众所期待的新书 开源安全运维平台OSSIM疑难解析 入门篇 由人民邮电出版社正式出版发行 此书从立意到付梓 历时超过两年 经过数十次大修 历经曲折与艰辛 希望为大家代奉献一本好书 愿这本书能陪伴OSSIM用户一起进步一起成长
  • 结构化设计和模块设计

    结构化设计 结构化设计包括架构设计 接口设计 数据设计和过程设计等任务 它是一种面向数据流的设计方法 是以结构化分析阶段所产生的成果为基础 进一步自顶而下 逐步求精和模块化的过程 1 概要设计与详细设计的主要任务 概要设计阶段的主要任务是设
  • Java 15中的隐藏类是咋回事?

    Java 从1 1 版就有匿名类 但是 匿名类并不是匿名的 你不需要为它们命名 它们是由 Java 编译器命名的 如果你熟悉命令javap 则可以 反汇编 JAR 文件并查看编译器为匿名类生成的名称 Java 15 引入了没有名称的隐藏类
  • ModuleNotFoundError: No module named ‘rosbag‘

    1 ModuleNotFoundError No module named rosbag File opt ros kinetic lib python2 7 dist packages roslib launcher py line 42
  • 数字图像处理中一元函数f(x)的二阶导数=f(x+1)+f(x-1)-2f(x)的由来

    在 为什么说数字图像的一阶微分为f x 1 f x 介绍了数字图像处理中一元函数的一阶导数的由来 该一阶导数为 在该书中 同时定义了一元函数f x 其二阶导数的基本定义为 关于这个推导其中有点点小技巧 式3 6 1也可以表述为 f x f
  • C++基础——初始化列表

    目录 一 初始化列表 1 列表格式 情况1 成员变量中有const成员 但列表处成员不被初始化时 情况1 成员变量中有const成员 给缺省值时 情况1 成员变量中有const成员 列表处成员不仅初始化 还有缺省值 情况2 成员是自定义类型
  • WinCE 下 RAS 拨号连接的建立和拨号过程

    在 CE 下如果系统没有建立拨号网络 要实现上网功能时 就需要通过代码来实现建立拨号网络 拨号实现上网的过程 以下代码中的参数和拨号所用数值 都是电信 EVDO 的配置 如果换 GSM 或 WCDMA 时 请相应修改 cpp view pl
  • PHP学习笔记 Win平台下PHP开发环境的搭建

    Win平台下PHP开发环境的搭建 本文参考了PHP官方文档 http php net manual zh install windows iis7 php 写成 如果需要了解详细信息 请直接参考PHP官方文档 下载并配置PHP 下载PHP
  • 第十四届蓝桥杯模拟赛(第三期)试题与题解 C++

    目录 一 填空题 一 最小的十六进制 答案 2730 二 Excel的列 答案 BYT 三 相等日期 答案 70910 四 多少种取法 答案 189 五 最大连通分块 答案 148 二 编程题 一 哪一天 二 信号覆盖 三 清理水草 四 最
  • 关于我写了三万字博客后悔了好久这件事之第二个三万字GUI(swing)

    目录 简介 使用Swing的优势 Swing的特征 Swing基本组件的用法 Swing组件层次 AWT组件的Swing实现 简单了解swing JFrame 弹窗 标签 面板 按钮 3 6 列表 3 7 文本框 JTree TreeMod