Eclipse中的TreeViewer类和ListViewer类

TreeViewer和TableViewer在使用上还是有很多相似之处.TreeViewer中冶有TableViewer中的过滤器和排序器.具体使用看TableViewer中的使用.

和Table有JFace的扩展TableViewer一样,Tree也有一个JFace中的扩展,那就是TreeViewer.因为TreeViewer和TableViewer继承自同一个父类StructuredViewer所以两者有很多方法的使用是一样的.例如:都使用setInput方法输入数据,都有内容器,标签器,以及排序器,过滤器等.

建立一个树节点的接口类:

树节点由两个基本特征,名称和子节点.这里把这两个特征抽象出来写成一个接口,然后将要做树节点的实体类实现此接口.

主义这个接口不是必须的.仅仅是为了今后操作方便,以及规范化涉及才建立的.

但是每个实体对应的都是树上的一个节点.这里定义一个树中节点的通用接口.

然后每个实体类都实现这个接口.

接着建立几个实体类:

ITreeEntry.java

 1 /**
 2  * 树上的每个一个节点都对应的是一个实体,这个实体是树上的一个节点.
 3  * 首先在定义实体类(People City Country)之前要先定义这个接口ITreeEntry
 4  * 树的节点接口
 5  * @author kongxiaohan
 6  */
 7 public interface ITreeEntry {
 8     /*
 9      * 设置dedao 树节点的名称    
10      * 只声明抽象方法,不声明字段名
11      */
12     public String getName();
13     public void setName(String name);
14     
15     
16     /*
17      * 设置与得到子节点集合.
18      */
19     public void setChildren(List<ITreeEntry> children);
20     public List<ITreeEntry> getChildren();
21 }

City.java

 1 /**
 2  * City城市实体类
 3  * @author kongxiaohan
 4  *
 5  */
 6 public class City implements ITreeEntry{
 7     private Long id; // 唯一识别码,在数据库里常为自动递增的ID列
 8     private String name;// 城市名
 9     
10     private List<ITreeEntry> peoples;//City实体的子节点  城市中的人,装在一个List集合中
11     
12     public City(String name) {
13         super();
14         this.name = name;
15     }
16     
17     public Long getId() {
18         return id;
19     }
20     public void setId(Long id) {
21         this.id = id;
22     }
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
29     
30     //这个地方是设置子节点....当前是City实体,其子节点是People实体
31     @Override
32     public void setChildren(List<ITreeEntry> children) {
33         //这个地方我犯了一错误,以前写习惯了,this.name = name
34         //所以一开始这个地方我写的是this.peoples = peoples 
35         //在Country实体类中也是这个错误,所以运行出来的程序只有一列国家名,国家下面的城市都没有了....
36         this.peoples = children;
37     }
38 
39     @Override
40     public List<ITreeEntry> getChildren() {
41         return peoples;
42     }
43 }

Country.java

 1 /**
 2  * Country国家实体类
 3  * @author kongxiaohan
 4  */
 5 public class Country implements ITreeEntry {
 6     private Long id; // 唯一识别码,在数据库里常为自动递增的ID列
 7     private String name; // 国家名
 8     
 9     private List<ITreeEntry> cities; //Country实体的子节点是城市 City 此国家所包含的的城市的集合,集合元素为City对象
10     
11     public Country() {
12     }
13     
14     public Country(String name) {
15         super();
16         this.name = name;
17     }
18     
19     public Long getId() {
20         return id;
21     }
22     public void setId(Long id) {
23         this.id = id;
24     }
25     public String getName() {
26         return name;
27     }
28     public void setName(String name) {
29         this.name = name;
30     }
31     
32     @Override
33     public void setChildren(List<ITreeEntry> children) {
34         this.cities = children;
35     }
36 
37     @Override
38     public List<ITreeEntry> getChildren() {
39         return cities;
40     }
41 }

People.java

 1 public class People implements ITreeEntry {
 2     private Long id; // 唯一识别码,在数据库里常为自动递增的ID列
 3     private String name; // 姓名
 4     private boolean sex; // 性别 true男,flase女
 5     private int age; // 年龄
 6     private Date createDate; // 记录的建立日期,是java.util.Date,而不是java.sql.Date
 7 
 8     public People(String name) {
 9         super();
10         this.name = name;
11     }
12     public Long getId() {
13         return id;
14     }
15     public void setId(Long id) {
16         this.id = id;
17     }
18     public String getName() {
19         return name;
20     }
21     public void setName(String name) {
22         this.name = name;
23     }
24     public boolean isSex() {
25         return sex;
26     }
27     public void setSex(boolean sex) {
28         this.sex = sex;
29     }
30     public int getAge() {
31         return age;
32     }
33     public void setAge(int age) {
34         this.age = age;
35     }
36     public Date getCreateDate() {
37         return createDate;
38     }
39     public void setCreateDate(Date createDate) {
40         this.createDate = createDate;
41     }
42     
43     
44     //因为人是这个树的最小子节点,所以这个地方空实现接口中的这两个方法就可以了
45     @Override
46     public void setChildren(List<ITreeEntry> children) {
47     }
48     @Override
49     public List<ITreeEntry> getChildren() {
50         return null;
51     }
52 }

制造各个实体类的工具类

DataFactory.java

 1 /**
 2  * 此类负责生成TreeViewer的方法setInput所需要的参数.
 3  * @author kongxiaohan
 4  * 
 5  * 这个地方我没有用书中用的局部代码块,在组织代码的时候确实犯了一些小错误,
 6  * 确实体会到了局部代码块的意义,代码读起来容易,而且节省了内存.
 7  */
 8 public class DataFactory {
 9     /**
10      * 在这个方法中定义生成的数据
11      * 要有人People的数据对象
12      * 要有国家Country的数据对象
13      * 要有城市City的数据对象
14      * @return
15      */
16     public static Object createTreeData(){
17         //生成人People的数据对象
18         People people1 = new People("张A");
19         People people2 = new People("张B");
20         People people3 = new People("张C");
21         People people4 = new People("张D");
22         People people5 = new People("张E");
23         People people6 = new People("张F");
24         People people7 = new People("张G");
25         People people8 = new People("张H");
26         People people9 = new People("张I");
27         
28         //生成城市City的数据对象
29         City city1 = new City("北京");
30         City city2 = new City("广州");
31         City city3 = new City("东京");
32         City city4 = new City("芝加哥");
33         City city5 = new City("洛杉矶");
34         
35         //生成国家Country的的数据对象
36         Country country1 = new Country("中国");
37         Country country2 = new Country("日本");
38         Country country3 = new Country("美国");
39         
40         /**
41          * 将这些封装的对象建立关系.
42          */
43         //1.人和城市的关系 
44         //张A 张B 张C 在帝都
45         ArrayList<ITreeEntry> list1 = new ArrayList<ITreeEntry>();
46         list1.add(people1);
47         list1.add(people2);
48         list1.add(people3);
49         city1.setChildren(list1);
50         
51         //张D 张E 张F在广州 
52         ArrayList<ITreeEntry> list2 = new ArrayList<ITreeEntry>();
53         list2.add(people4);
54         list2.add(people5);
55         list2.add(people6);
56         city2.setChildren(list2);        
57         
58         //张G 在东京
59         ArrayList<ITreeEntry> list3 = new ArrayList<ITreeEntry>();
60         list3.add(people7);
61         city3.setChildren(list3);        
62 
63         //张I 在芝加哥
64         ArrayList<ITreeEntry> list4 = new ArrayList<ITreeEntry>();
65         list4.add(people8);
66         city4.setChildren(list4);        
67 
68         //张H 在洛杉矶
69         ArrayList<ITreeEntry> list5 = new ArrayList<ITreeEntry>();
70         list5.add(people9);
71         city5.setChildren(list5);        
72         
73         //2.城市和国家的关系
74         //北京 上海  广州是中国的
75         ArrayList<ITreeEntry> list6 = new ArrayList<ITreeEntry>();
76         list6.add(city1);
77         list6.add(city2);
78         country1.setChildren(list6);
79         
80         //东京是日本的
81         ArrayList<ITreeEntry> list7 = new ArrayList<ITreeEntry>();
82         list7.add(city3);
83         country2.setChildren(list7);
84         
85         //芝加哥和洛杉矶是美国的
86         ArrayList<ITreeEntry> list8 = new ArrayList<ITreeEntry>();
87         list8.add(city4);
88         list8.add(city5);
89         country3.setChildren(list8);
90         
91         //3.将国家置于一个对象之下,这个对象可以是一个List也可以是个数组
92         //国家是这个树上的最上层的节点.国家和国家之间是并列的关系,把这几个国家放到一个List集合对象中.
93         ArrayList<ITreeEntry> list9 = new ArrayList<ITreeEntry>();
94         list9.add(country1);
95         list9.add(country2);
96         list9.add(country3);
97         return list9;
98     }
99 }

内容器:TreeViewerContentProvider.java

标签器还比较简单,在TreeViewer中最主要和最复杂的是内容器,熟悉内容器是掌握TreeViewer的要点.

 1 /**
 2  * "内容器" 由它决定哪些对象记录应该输出在TreeViewer里显示.
 3  * @author kongxiaohan
 4  *
 5  */
 6 public class TreeViewerContentProvider  implements ITreeContentProvider{
 7 
 8     /**
 9      * 这个方法决定树的一级目录显示哪些对象
10      * @param inputElement 是用tv.setInput()方法输入的那个对象
11      * @return Object[]一个数组,数组中一个元素就是一个结点
12      */
13     @Override
14     public Object[] getElements(Object inputElement) {
15         if (inputElement instanceof List) {
16             List list = (List) inputElement;
17             return list.toArray();
18         } else {
19             return new Object[0]; // 生成一个空数组
20         }
21     }
22     
23     
24     /**
25      * 判断某结点是否有子结点。如果有子结点,这时结点前都有一个“+”号图标
26      * 
27      * @param element 需要判断是否有子的结点
28      * @return true有子结点,false无子结点
29      */
30     @Override
31     public boolean hasChildren(Object element) {
32         ITreeEntry entry = (ITreeEntry) element;
33         List<ITreeEntry> list = entry.getChildren();
34         if (list == null || list.isEmpty()) {
35             return false;
36         } else {
37             return true;
38         }
39     }
40     
41     
42     /**
43      * 由这个方法决定父结点应该显示那些子结点。
44      * 
45      * @param parentElement 当前被点击的结点对象
46      * @return 由子结点做为元素的数组
47      */
48     @Override
49     public Object[] getChildren(Object parentElement) {
50         ITreeEntry entry = (ITreeEntry) parentElement;
51         List<ITreeEntry> list = entry.getChildren();
52         if (list == null || list.isEmpty()) {
53             return new Object[0];
54         } else {
55             return list.toArray();
56         }
57     }
58 
59     //>>>>>>>>>>>>>>>>>>>>>>>>>书上说以下三个方法是没有用的,空实现就哦了>>>>>>>>>>>>>>>>>>>>>>
60     @Override
61     public Object getParent(Object element) {
62         return null;
63     }
64 
65     @Override
66     public void dispose() {
67     }
68     
69     @Override
70     public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
71     }
72 }

程序说明:在内容器中最关键的是getElements,hasChildren,getChildren这三个方法.

1.getElements自在显示"第一级"结点的时候才会被执行.

2.hasChildren主要用于判断当前说系那是的节点是否有子节点,如果有子节点则前面显示一个"+"号图标,而有"+"号的结点则可以单击展开其下一级的子节点.

3.当单击右"子"的结点的时候,才会执行getChildren方法.展开其子节点后,又会对子节点执行一遍hasChildren方法,以决定其各子结点前是否显示"+"图标.

下面是"内容器"在启动,单击,关闭窗口时执行的过程.

下面以本例来解释这个图:

1.树界面启动的时候 ,先执行inputChanged()方法,接着执行getElements方法,其inputElement参数就是由setInput传入的对象:包含所有实体对象的List,此List在getElements中被转化成一个数组,数组包含第一级结点的两个元素,中国,美国,日本,他们将首先显示在页面上.接下来执行三次hasChildren方法,判断中国,日本和美国是否有子节点.他们都有子节点.,所以方法返回True,两结点前都显示"+"图标.

2.单击右子节点的中国:先执行一次getChildren方法,方法的parentElement参数就是中国结点对象.方法中把中国的子节点取出转换陈给一个数组返回,此数组包含3个元素"北京,上海,济南".接下来连续执行3次hasChildren方法来判断"北京,上海,济南"是否有子节点,如果有在结点的前面显示一个"+"图标.

3.单击没有子节点的结点:不会有执行内容器中的任何方法.

4.关闭窗口:会先后执行inputChanged和dispose方法.

 

标签器TreeViewerLableProvider.java

将标签器写成单独的外部类,以便于后面的实例共用,其代码如下,在getText方法中element的类型可以是国家,城市,人,由于他们都是属于一个接口,所以getText的代码简洁不少.getImage()的实现参考TableViewer的标签器.

 1 /**
 2  * "标签器" 控制记录在树中显示的文字和图像等.
 3  * @author kongxiaohan
 4  */
 5 public class TreeViewerLableProvider implements ILabelProvider {
 6 
 7     /**
 8      * 记录显示的文字。不能返回NULL值
 9      */
10     @Override
11     public String getText(Object element) {
12         //得到这个节点对应的名字,首先对element进行强制类型转换
13         ITreeEntry entry = (ITreeEntry) element;
14         return entry.getName();
15     }
16     
17     /**
18      * 记录显示的图像,可以返回NULL值
19      */    
20     @Override
21     public Image getImage(Object element) {
22         return null;
23     }
24     
25 //>>>>>>>>>>>>>>>>>>>>>>书上说一下几个方法没有用,空实现就可以了.>>>>>>>>>>>>>>>>>>>>>>>>>    
26     @Override
27     public void addListener(ILabelProviderListener listener) {
28         // TODO Auto-generated method stub
29         
30     }
31 
32     @Override
33     public void removeListener(ILabelProviderListener listener) {
34         // TODO Auto-generated method stub
35         
36     }
37     @Override
38     public void dispose() {
39         // TODO Auto-generated method stub
40         
41     }
42     @Override
43     public boolean isLabelProperty(Object element, String property) {
44         // TODO Auto-generated method stub
45         return false;
46     }
47 }

给TreeViewer加上右键菜单的方法和TableViewer相似,也要用到Action,ActionGroup,MenuManager类.当然程序要根据树的特点稍作改动.

MyActionGroup内含有各Action类,在实现Action时,对传入的结点对象要记得进行空值判断.因为TreeViewer的大部分方法都不支持空值参数.会导致异常并中断程序.

MyActionGroup.java

  1 public class MyActionGroup extends ActionGroup {
  2     //ActionGroup这是个抽象类,但是其中并没有抽象方法.
  3     private TreeViewer treeViewer;
  4 
  5     //构造方法
  6     public MyActionGroup(TreeViewer treeViewer) {
  7         this.treeViewer = treeViewer;
  8     }
  9     
 10     /**
 11      * 生成菜单Menu,并将两个Action传入
 12      */
 13     public void fillContextMenu(IMenuManager mgr) {
 14         /*
 15          * 加入两个Action对象到菜单管理器
 16          */
 17         MenuManager menuManager = (MenuManager) mgr; // 把接口类转换成其实现类.
 18         //用MenuManager管理Action
 19         menuManager.add(new OpenAction());
 20         menuManager.add(new RefreshAction());
 21         menuManager.add(new ExpandAction());
 22         menuManager.add(new CollapseAction());
 23         menuManager.add(new AddEntryAction());
 24         menuManager.add(new RemoveEntryAction());
 25         menuManager.add(new ModifyEntryAction());
 26         /*
 27          * 把这些功能加入到右键的Menu菜单中.
 28          */
 29         Tree tree = treeViewer.getTree();
 30         Menu menu = menuManager.createContextMenu(tree);
 31         tree.setMenu(menu);
 32     }
 33     
 34     /**
 35      * 打开"菜单"对应的Action类
 36      */
 37     private class OpenAction extends Action {
 38         public OpenAction() {
 39             setText("打开");
 40         }
 41         /**
 42          * 继承自Action的方法,动作代码写此方法中
 43          */
 44         //覆盖Action中的run()方法.
 45         public void run() {
 46             ITreeEntry obj = getSelTreeEntry();//getgetSelTreeEntry()得到当前节点
 47             if (obj != null) {
 48                 //弹出一个这个节点名字的对话框.
 49                 MessageDialog.openInformation(null, null, obj.getName());
 50             }
 51         }
 52     }
 53     
 54     /**
 55      * 刷新对应的Action类.
 56      */
 57     private class RefreshAction extends Action {
 58         public RefreshAction() {
 59             setText("刷新");
 60         }
 61 
 62         //覆盖Action类中的run()方法,里面是调用的refresh()方法.
 63         public void run() {
 64             treeViewer.refresh();// 调用TreeViewer的刷新方法
 65         }
 66     }
 67 
 68     /**
 69      * 展开当前结点的Action类
 70      */
 71     private class ExpandAction extends Action {
 72         public ExpandAction() {
 73             setText("展开");
 74         }
 75         //重写run()方法
 76         public void run() {
 77             ITreeEntry obj = getSelTreeEntry();
 78             if (obj != null) {
 79                 treeViewer.expandToLevel(obj, 1); 
 80                 // 这个方法后面的数字是展开的层级数.这个地方设置成仅仅展开1个层级.
 81             }
 82         }
 83     }
 84 
 85     /**
 86      * 收缩当前结点的Action类
 87      */
 88     private class CollapseAction extends Action {
 89         public CollapseAction() {
 90             setText("收缩");
 91         }
 92         //重写run()方法
 93         public void run() {
 94             ITreeEntry obj = getSelTreeEntry();
 95             if (obj != null) {
 96                 treeViewer.collapseToLevel(obj, -1); // -1为将当前结点的所有子结点收缩
 97             }
 98         }
 99     }
100 
101     /**
102      * 给当前结点增加一个子结点的Action类
103      */
104     private class AddEntryAction extends Action {
105         public AddEntryAction() {
106             setText("增加");
107         }
108         @Override
109         public void run() {
110             ITreeEntry obj = getSelTreeEntry();
111             if (obj == null || obj instanceof People) {
112                 return;
113             }
114             InputDialog dialog = new InputDialog(null, "给当前结点增加一个子结点", "输入名称", "请输入你要增加的节点的名字", null);
115             if (dialog.open() == InputDialog.OK) {// 如果单击OK按钮
116                 String entryName = dialog.getValue(); // 得到Dialog输入值
117                 /* 根据单击结点的不同类型生成相应的子结点 */
118                 ITreeEntry newEntry = null;
119                 if (obj instanceof Country) {
120                     newEntry = new City(entryName);
121                 } else if (obj instanceof City) {
122                     newEntry = new People(entryName);
123                 }
124                 /* 在增加子结点之前将父结点展开 */
125                 if (!treeViewer.getExpandedState(obj)) {
126                     treeViewer.expandToLevel(obj, 1);
127                 }
128                 treeViewer.add(obj, newEntry);// 增加结点
129             }
130         }
131     }
132 
133     /**
134      * 删除结点的Action类
135      */
136     private class RemoveEntryAction extends Action {
137         public RemoveEntryAction() {
138             setText("删除");
139         }
140         @Override
141         public void run() {
142             ITreeEntry obj = getSelTreeEntry();
143             if (obj == null) {
144                 return;
145             }
146             treeViewer.remove(obj);
147         }
148     }
149 
150     /**
151      * 修改结点名称的Action类
152      */
153     private class ModifyEntryAction extends Action {
154         public ModifyEntryAction() {
155             setText("修改");
156         }
157         @Override
158         public void run() {
159             ITreeEntry obj = getSelTreeEntry();
160             if (obj == null) {
161                 return;
162             }
163             InputDialog dialog = new InputDialog(null, "修改结点", "输入新名称", obj.getName(), null);
164             if (dialog.open() == InputDialog.OK) {
165                 String entryName = dialog.getValue();//得到对话框中的值.
166                 obj.setName(entryName);//给这个节点设置成得到的对话框中的名字.
167                 treeViewer.refresh(obj); // 刷新结点
168             }
169         }
170     }
171     /**
172      * 这个方法是自定义的方法,这个方法的作用就是得到当前选择的节点.
173      */
174     private ITreeEntry getSelTreeEntry() {
175         IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
176         ITreeEntry treeEntry = (ITreeEntry) (selection.getFirstElement());
177         return treeEntry;
178     }
179 }

TreeViewer.java的实例

 1 public class TreeViewer1 {
 2     public static void main(String[] args) {
 3         TreeViewer1 window = new TreeViewer1();
 4         window.open();
 5     }
 6     
 7     //定义这个open()方法.就是创建一个典型的SWT程序的步骤
 8     public void open(){
 9         //1.Display负责管理一实现循环和控制UI线程和其他线程之间的通信
10         final Display display = new Display();
11         //2.创建一个或者多个Shell(shell是程序的主窗口)
12         final Shell shell = new Shell();
13         //3.设置shell的布局.
14         shell.setSize(200, 300);
15         //设置shell的布局为FillLayout
16         shell.setLayout(new FillLayout());
17         shell.setText("TreeViewer的第一个例子");
18         
19         
20         Composite c = new Composite(shell, SWT.NONE);
21         c.setLayout(new FillLayout());
22         TreeViewer treeViewer = new TreeViewer(c, SWT.BORDER);
23         treeViewer.setContentProvider(new TreeViewerContentProvider());
24         treeViewer.setLabelProvider(new TreeViewerLableProvider());
25         // 和TableViewer一样,数据的入口也是setInput方法
26         Object inputObj = DataFactory.createTreeData();
27         treeViewer.setInput(inputObj);
28         
29         /*
30         //调用自定义的方法创建表格
31         createTableViewer(shell);
32         //4.设定内容器
33         tableviewer.setContentProvider(new TableViewerContentProvider());
34         //5.设定标签器
35         tableviewer.setLabelProvider(new TableViewerLabelProvider());
36         //6.用setInput输入数据(把PeopleFactory产生的List集合传进来)
37         tableviewer.setInput(PeopleFactory.getPeoples());
38         */
39         
40         //7.创建Shell中的组件(这个例子中没有加入组件,只有一个空窗口)
41         shell.open();
42         //8.写一个时间转发循环
43         while(!shell.isDisposed()){//如果主窗口没有关闭,则一直循环
44             //dispose 是"处理,处置,毁掉"的意思
45             if(!display.readAndDispatch()){ 如果display不忙
46                 display.sleep();// display休眠
47             }
48         }
49     }
50 }

TreeViewer1.java的运行结果图

TreeViewer2.java

 1 public class TreeViewer2 {
 2 
 3     public static void main(String[] args) {
 4         TreeViewer2 window = new TreeViewer2();
 5         window.open();
 6     }
 7     
 8     public void open() {
 9         final Display display = new Display();
10         final Shell shell = new Shell();
11         shell.setSize(200, 300);
12 
13         shell.setLayout(new FillLayout());
14         Composite c = new Composite(shell, SWT.NONE);
15         c.setLayout(new FillLayout());
16         TreeViewer treeViewer = new TreeViewer(c, SWT.BORDER);
17         treeViewer.setContentProvider(new TreeViewerContentProvider());
18         treeViewer.setLabelProvider(new TreeViewerLableProvider());
19         Object inputObj = DataFactory.createTreeData();
20         //>>>>>>>>>>>>>相比于TreeViewer1.java增加的>>>>>>>>>>>>>>>>>>>>>>>>>
21         //生成一个ActionGroup对象
22         MyActionGroup actionGroup = new MyActionGroup(treeViewer);
23         // 调用fillContextMenu方法将按钮注入到菜单对象中
24         actionGroup.fillContextMenu(new MenuManager());
25         // --------------加入代码:END--------------------
26         treeViewer.setInput(inputObj);
27         // -----------------------------
28         shell.open();
29         while (!shell.isDisposed()) {
30             if (!display.readAndDispatch()) {
31                 display.sleep();
32             }
33         }
34     }
35 }

TreeViewer2.java的运行结果图:

 上面的这个是<<Eclispe从入门到精通>>中第一版的代码,在第二版中对一些功能进行了一些改进.

该进在于"不同的结点显示不同的菜单".

树的"人"结点是没有子节点的.因此对于"人"这个节点来说,右键菜单中的"展开,收缩,增加"菜单项都没有任何意义,应该隐藏起来,再根据当前节点的类型决定要将哪些Action加入菜单中.按着这个思路将MyActionGroup类的fillContextMenu方法修改如下:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值