使用Eclipse 创建视图并实现视图间消息传递

在本文中,我们会介绍以两种不同的方式实现Eclipse RCP View之间的交互,并结合代码详细领略一下Eclipse是如何巧妙处理View之间交互的。
首先介绍一下我们要达到的效果,如下图所示:

可以看到,用户点击左边视图上的人员列表,该人员的详细信息会显示在右侧的view视图中,这一步骤看起来简单,但是要做的工作却不少,下面我们就着手把前期获得的RCP程序做成这个样子,让View之间传递信息或者更复杂一些的对象。

从Eclipse目前提供的手段看来,我们可以使用下列方法实现视图的交互:
(1)选择提供器 - 选择监听器(selection provider-selection listener)模式,从而让视图对其他视图中的选择做出反应
(2)使用属性改变监听模式,即Property Changed Listener模式,用于视图中没有可供点击的UI模块的情况下。这类监听模式允许视图将属性改变事件

以下将详细介绍这两种模式的概念、适用场景,并结合我们的Hello RCP分别实例说明这两种视图交互模式的实现。
(一)选择提供器-选择监听器(selection provider-selection listener)模式:
这是一种传统的事件传递模式,一般来说,只要视图中的UI控件实现了ISelectionProvider接口,即具备提供选择事件的能力,就可以使用这个模式将自身发生的选择事件提供给其他实现了ISelectionListener的UI控件。例如我们在Hello RCP中实现的Navigation视图,里面采用了ListViewer这一控件,而Jface中所有的Viewer对象都实现了 ISelectionProvider接口,那么当我们点击navigation视图list列表中的人员名单时,ListViewer列表就可以向其他能够监听该选择事件的对象提供该选择事件,如果我们在事件中包含一些字符串信息,那么其他对象得到这个信息之后,就可以进行相应显示了。
"Eclipse提供了所谓的 Site ,以及 ISelectionService 机制,来处理试图之间的简单的交互。简单的说, ViewSite 提供了一个交互的中心点,其它 View 向 ViewSite 提供选择事件,或者向其注册监听器,而事件的触发与转发则由 ViewSite() 来完成。"
以上只是一些概念介绍,对于我们来说,只需按照下列步骤进行,就可以将一个视图变成一个提供Selection 事件的Selection Provider:
(1)首先将视图注册成为Selection Provider,即实现ISelectionProvider接口


public class NavigationView extends ViewPart implements ISelectionProvider 以下是ISelectionProvider接口的定义:
1 public interface ISelectionProvider {
2
3 public void addSelectionChangedListener(ISelectionChangedListener listener);
4
5 public ISelection getSelection();
6
7 public void removeSelectionChangedListener(
8
9 ISelectionChangedListener listener);
10
11 public void setSelection(ISelection selection);
12
13 }
14 (2)有了以上步骤还不够,还需要将视图中具体的Viewer上想要发生的事件,注册到这个Provider上。
viewer.addSelectionChangedListener( new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection2 = event.getSelection();
setSelection(selection2);
}
} ); 这段代码就是把ListViewer上的Selection Change Event注册到了Selection Provider上,由后者负责向外传播
(3)一个Selection Provider想要其他部件监听的话,还要向Site中控台进行注册,要不然就相当于开会时找不到话筒讲话,大家听不见

1 this .getSite().setSelectionProvider( this );
注意this对象指的是Viwe视图,它具有指向Site的引用,通过getSite()方法获得引用
(4)最后,还要把讲给听众听,没有听众也是白讲,这里是在setSelection()方法里迭代每一个注册了ISelectionListener的控件,找到它们,把事件传递给这些听众即可:

public void setSelection(ISelection selection) {
this .selection = selection;
SelectionChangedEvent event2 = new SelectionChangedEvent(viewer, selection);
for (Iterator i = myListeners.iterator(); i.hasNext();) {
ISelectionChangedListener object = (ISelectionChangedListener) i.next();
object.selectionChanged(event2);
}

}

至此,我们已经实现了一个Selection Provider了,对于事件监听的另一端,即Selection Listener,则更为简单一些。只需要实现ISelectionListener接口,并注册在 Site中:

site.getPage().addSelectionListener( this );
然后实现 public void selectionChanged(IWorkbenchPart part, ISelection selection) {}方法即可。这样,当 SelectionProvider中的选择发生改变时,这个视图中的 selectionChanged()方法就会被调用。


即如下方式:

1 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
2 IStructuredSelection structuredSelection = (IStructuredSelection)selection;
3 Object obj = structuredSelection.getFirstElement();
4 Person tempPerson = (Person)obj;
5 if (tempPerson != null )
6 text.setText(tempPerson.getName());
7 text_1.setText(tempPerson.getSex());
8 }

根据以上介绍的Selection Provider-Selection Listener模式,我们重新改造了一下navigation view视图和detail view视图:

1 package hellorcp;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5
6 import org.eclipse.jface.action.IToolBarManager;
7 import org.eclipse.jface.viewers.ISelection;
8 import org.eclipse.jface.viewers.ISelectionChangedListener;
9 import org.eclipse.jface.viewers.ISelectionProvider;
10 import org.eclipse.jface.viewers.IStructuredContentProvider;
11 import org.eclipse.jface.viewers.ListViewer;
12 import org.eclipse.jface.viewers.SelectionChangedEvent;
13 import org.eclipse.swt.SWT;
14 import org.eclipse.swt.widgets.Composite;
15 import org.eclipse.ui.part.ViewPart;
16 import hellorcp.NavigationViewLabelProvider;
17
18 public class NavigationViewPart extends ViewPart implements ISelectionProvider {
19
20 private ISelection selection;
21 ArrayList myListeners = new ArrayList();
22 ListViewer listViewer ;
23
24 public NavigationViewPart() {
25 // TODO Auto-generated constructor stub
26 }
27
28 @Override
29 public void createPartControl(Composite parent) {
30 listViewer = new ListViewer(parent, SWT.BORDER);
31 listViewer.setLabelProvider( new NavigationViewLabelProvider());
32 listViewer.setContentProvider( new NavigationListViewContentProvider());
33 listViewer.setInput( new PersonModel());
34 initializeToolBar();
35 listViewer.addSelectionChangedListener( new ISelectionChangedListener() {
36 public void selectionChanged(SelectionChangedEvent event) {
37 ISelection selection2 = event.getSelection();
38 setSelection(selection2);
39 }
40 } );
41 this .getSite().setSelectionProvider( this );
42 // TODO Auto-generated method stub
43
44 }
45
46 public ISelection getSelection() {
47 // TODO Auto-generated method stub
48 return null ;
49 }
50
51 public void addSelectionChangedListener(ISelectionChangedListener listener) {
52 if ( ! myListeners.contains(listener))
53 myListeners.add(listener);
54 }
55
56 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
57 myListeners.remove(listener);
58 }
59
60 @Override
61 public void setFocus() {
62 // TODO Auto-generated method stub
63
64 }
65 private void initializeToolBar() {
66 IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager();
67 }
68 public void setSelection(ISelection selection) {
69 this .selection = selection;
70 SelectionChangedEvent event2 = new SelectionChangedEvent(listViewer, selection);
71 for (Iterator i = myListeners.iterator(); i.hasNext();) {
72 ISelectionChangedListener object = (ISelectionChangedListener) i.next();
73 object.selectionChanged(event2);
74 }
75
76 }
77
78 }
79 1 package hellorcp;
2
3 import java.util.ArrayList;
4
5 import org.eclipse.jface.viewers.ISelection;
6 import org.eclipse.jface.viewers.IStructuredSelection;
7 import org.eclipse.jface.viewers.TableViewer;
8 import org.eclipse.swt.SWT;
9 import org.eclipse.swt.widgets.Composite;
10 import org.eclipse.swt.widgets.Group;
11 import org.eclipse.swt.widgets.Label;
12 import org.eclipse.swt.widgets.Table;
13 import org.eclipse.swt.widgets.Text;
14 import org.eclipse.ui.ISelectionListener;
15 import org.eclipse.ui.IWorkbenchPart;
16 import org.eclipse.ui.part.ViewPart;
17
18 public class DetailView extends ViewPart implements ISelectionListener {
19
20 private Table table;
21 private Text text_1;
22 private Text text;
23 @SuppressWarnings( " unchecked " )
24 ArrayList myListeners;
25 public DetailView() {
26 super ();
27 // TODO Auto-generated constructor stub
28 }
29
30 @Override
31 public void createPartControl(Composite parent) {
32 final Composite composite = new Composite(parent, SWT.NONE);
33
34 final Group group = new Group(composite, SWT.NONE);
35 group.setBounds( 0 , 0 , 494 , 62 );
36
37 final Label label = new Label(group, SWT.NONE);
38 label.setText( " 姓名 " );
39 label.setBounds( 10 , 32 , 24 , 12 );
40
41 text = new Text(group, SWT.BORDER);
42 text.setBounds( 40 , 29 , 80 , 15 );
43
44 final Label label_1 = new Label(group, SWT.NONE);
45 label_1.setText( " 性别 " );
46 label_1.setBounds( 172 , 32 , 30 , 12 );
47
48 text_1 = new Text(group, SWT.BORDER);
49 text_1.setBounds( 208 , 29 , 80 , 15 );
50
51 final TableViewer tableViewer = new TableViewer(composite, SWT.BORDER);
52 table = tableViewer.getTable();
53 table.setLinesVisible( true );
54 table.setHeaderVisible( true );
55 table.setBounds( 0 , 68 , 494 , 270 );
56 initializeToolBar();
57 this .getSite().getPage().addSelectionListener( this );
58 }
59
60 @Override
61 public void setFocus() {
62 // TODO Auto-generated method stub
63
64 }
65 private void initializeToolBar() {
66 }
67
68 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
69 IStructuredSelection structuredSelection = (IStructuredSelection)selection;
70 Object obj = structuredSelection.getFirstElement();
71 Person tempPerson = (Person)obj;
72 if (tempPerson != null )
73 text.setText(tempPerson.getName());
74 text_1.setText(tempPerson.getSex());
75
76 }
77
78 }
79

将以上文件重新编译,再运行我们的Hello RCP,可以看到,点击Navigation Views时,已经可以将选择的人员信息传递给detal view了,我们的视图真正动了起来:

其实对于本例来说,由于JFace Viewer已经实现了ISelectionProvider 接口,因此还有一种更简便的方法实现上述效果,就是将navigation view中的list viewer直接注册为Selection Provider ,这样就可以省去实现ISelectionProvider接口的代码了:
以下是NavigationViewPart.java的另外一种实现方式:


1 package hellorcp;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5
6 import org.eclipse.jface.action.IToolBarManager;
7 import org.eclipse.jface.viewers.ISelection;
8 import org.eclipse.jface.viewers.ISelectionChangedListener;
9 import org.eclipse.jface.viewers.ISelectionProvider;
10 import org.eclipse.jface.viewers.IStructuredContentProvider;
11 import org.eclipse.jface.viewers.ListViewer;
12 import org.eclipse.jface.viewers.SelectionChangedEvent;
13 import org.eclipse.swt.SWT;
14 import org.eclipse.swt.widgets.Composite;
15 import org.eclipse.ui.part.ViewPart;
16 import hellorcp.NavigationViewLabelProvider;
17
18 public class NavigationViewPart extends ViewPart {
19
20 private ISelection selection;
21 ArrayList myListeners = new ArrayList();
22 ListViewer listViewer ;
23
24 public NavigationViewPart() {
25 // TODO Auto-generated constructor stub
26 }
27
28 @Override
29 public void createPartControl(Composite parent) {
30 listViewer = new ListViewer(parent, SWT.BORDER);
31 listViewer.setLabelProvider( new NavigationViewLabelProvider());
32 listViewer.setContentProvider( new NavigationListViewContentProvider());
33 listViewer.setInput( new PersonModel());
34 this .getSite().setSelectionProvider(listViewer);
35 // TODO Auto-generated method stub
36
37 }
38 @Override
39 public void setFocus() {
40 // TODO Auto-generated method stub
41
42 }
43
44 }
45
对于detail view ,如果将将消费者视图作为监听器注册到特定的视图部分,就不用循环所有的Listener来通知了,这样可以节约更多的系统资源了。

this .getSite().getPage().addSelectionListener( " hellorcp.navigationview " ,(ISelectionListener) this );
以下是DetailView 的另外一种实现:

1 package hellorcp;
2
3 import java.util.ArrayList;
4
5 import org.eclipse.jface.viewers.ISelection;
6 import org.eclipse.jface.viewers.IStructuredSelection;
7 import org.eclipse.jface.viewers.TableViewer;
8 import org.eclipse.swt.SWT;
9 import org.eclipse.swt.widgets.Composite;
10 import org.eclipse.swt.widgets.Group;
11 import org.eclipse.swt.widgets.Label;
12 import org.eclipse.swt.widgets.Table;
13 import org.eclipse.swt.widgets.Text;
14 import org.eclipse.ui.ISelectionListener;
15 import org.eclipse.ui.IWorkbenchPart;
16 import org.eclipse.ui.part.ViewPart;
17
18 public class DetailView extends ViewPart implements ISelectionListener {
19
20 private Table table;
21 private Text text_1;
22 private Text text;
23 @SuppressWarnings( " unchecked " )
24 ArrayList myListeners;
25 public DetailView() {
26 super ();
27 // TODO Auto-generated constructor stub
28 }
29
30 @Override
31 public void createPartControl(Composite parent) {
32 final Composite composite = new Composite(parent, SWT.NONE);
33
34 final Group group = new Group(composite, SWT.NONE);
35 group.setBounds( 0 , 0 , 494 , 62 );
36
37 final Label label = new Label(group, SWT.NONE);
38 label.setText( " 姓名 " );
39 label.setBounds( 10 , 32 , 24 , 12 );
40
41 text = new Text(group, SWT.BORDER);
42 text.setBounds( 40 , 29 , 80 , 15 );
43
44 final Label label_1 = new Label(group, SWT.NONE);
45 label_1.setText( " 性别 " );
46 label_1.setBounds( 172 , 32 , 30 , 12 );
47
48 text_1 = new Text(group, SWT.BORDER);
49 text_1.setBounds( 208 , 29 , 80 , 15 );
50
51 final TableViewer tableViewer = new TableViewer(composite, SWT.BORDER);
52 table = tableViewer.getTable();
53 table.setLinesVisible( true );
54 table.setHeaderVisible( true );
55 table.setBounds( 0 , 68 , 494 , 270 );
56 initializeToolBar();
57 this .getSite().getPage().addSelectionListener( " hellorcp.navigationview " ,(ISelectionListener) this );
58
59 }
60
61 @Override
62 public void setFocus() {
63 // TODO Auto-generated method stub
64
65 }
66 private void initializeToolBar() {
67 }
68
69 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
70 IStructuredSelection structuredSelection = (IStructuredSelection)selection;
71 Object obj = structuredSelection.getFirstElement();
72 Person tempPerson = (Person)obj;
73 if (tempPerson != null )
74 text.setText(tempPerson.getName());
75 text_1.setText(tempPerson.getSex());
76
77 }
78
79 }
80
看起来后两种实现是不是比前面的要轻便很多呢?
但是对于我们来讲,视图间以Selection Provider 和Listener模式传递消息还存在很多局限:
1 视图可能希望公布其他信息,而不只是公布可视化选择信息。公布的信息可能是根据选择进行某些后期处理的结果。
2 视图可能希望使用来自另一个插件的信息,而这个插件可能根本没有提供视图(使用包含的 JFace 查看器)。在这种情况下,使用基于 UI 选择的链接是不可能的
对于以上问题,通过自动方式进行就很困难了,需要我们手工解决,而为视图注册属性监听器而实现观测其它视图的属性变化是个不错的替代模式。

(二)属性改变监听器模式:
属性改变监听器模式主要通过使用JFace中一个重要的接口org.eclipse.jface.util.IPropertyChangeListener来实现,通过注册该监听器,以及使用IPropertyChangeEvent,可以达到事件传递的目的。
与Selection provider-listener模式不同的是,属性改变监听器可以定义到插件上,由插件本身提供注册列表,如下所示:

1 ArrayList myListeners = new ArrayList();
2 // A public method that allows listener registration
3 public void addPropertyChangeListener(IPropertyChangeListener listener) {
4 if ( ! myListeners.contains(listener))
5 myListeners.add(listener);
6 }
7 // A public method that allows listener registration
8 public void removePropertyChangeListener(IPropertyChangeListener listener) {
9 myListeners.remove(listener);
10 }

当插件想要把产生的事件通知到各个监听器的时候,就对这个注册列表中每个Listener元素进行迭代,逐一通知,这样每个Property Change Event就传播到了各个Listener,由后者进行处理了,这个调用模式如下所示:

1 public void initAndInvoke(ArrayList listeners, Object obj) {
2 // Post Invocation, inform listeners
3 for (Iterator < IPropertyChangeListener > iter = myListeners.iterator(); iter.hasNext();) {
4 IPropertyChangeListener element = (IPropertyChangeListener) iter.next();
5 element.propertyChange( new PropertyChangeEvent( this , " HelloRcpEvent " , null , obj));
6
7 }
8 }

总而言之,要想实现这个模式,我们必须自己新定义一个注册并调用Property Change Event的类,我们把它叫做:PersonPlugin.java

那么接下来对于我们的navigation view中的list来说,由于没有采用provider模式,那么就必须自己定义它的SelectionChangeListener了,我们可以在这个ListViewer的添加该Listener中实现我们的目的:

1 package hellorcp;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5
6 import org.eclipse.jface.action.IToolBarManager;
7 import org.eclipse.jface.util.IPropertyChangeListener;
8 import org.eclipse.jface.viewers.ISelection;
9 import org.eclipse.jface.viewers.ISelectionChangedListener;
10 import org.eclipse.jface.viewers.ISelectionProvider;
11 import org.eclipse.jface.viewers.IStructuredContentProvider;
12 import org.eclipse.jface.viewers.IStructuredSelection;
13 import org.eclipse.jface.viewers.ListViewer;
14 import org.eclipse.jface.viewers.SelectionChangedEvent;
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.widgets.Composite;
17 import org.eclipse.ui.part.ViewPart;
18
19 import hellorcp.Person;
20 import hellorcp.PersonPlugin;
21 import hellorcp.NavigationViewLabelProvider;
22
23 public class NavigationViewPart extends ViewPart {
24
25 private ISelection selection;
26 ArrayList myListeners = new ArrayList();
27 ListViewer listViewer ;
28
29 public NavigationViewPart() {
30 // TODO Auto-generated constructor stub
31 }
32
33 @Override
34 public void createPartControl(Composite parent) {
35 listViewer = new ListViewer(parent, SWT.BORDER);
36 listViewer.setLabelProvider( new NavigationViewLabelProvider());
37 listViewer.setContentProvider( new NavigationListViewContentProvider());
38 listViewer.setInput( new PersonModel());
39 // this.getSite().setSelectionProvider(listViewer);
40 listViewer.addSelectionChangedListener( new ISelectionChangedListener() {
41 public void selectionChanged(SelectionChangedEvent event) {
42 ISelection selection2 = event.getSelection();
43 setSelection(selection2);
44 // Post Invocation, inform listeners
45 IStructuredSelection structuredSelection = (IStructuredSelection)selection;
46 Object obj = structuredSelection.getFirstElement();
47 Person tempPerson = (Person)obj;
48 PersonPlugin.getInstance().initAndInvoke(myListeners, tempPerson);
49 }
50 } );
51 // TODO Auto-generated method stub
52
53 }
54
55 public ISelection getSelection() {
56 // TODO Auto-generated method stub
57 return null ;
58 }
59
60 // A public method that allows listener registration
61 public void addPropertyChangeListener(IPropertyChangeListener listener) {
62 if ( ! myListeners.contains(listener))
63 myListeners.add(listener);
64 }
65
66 // A public method that allows listener registration
67 public void removePropertyChangeListener(IPropertyChangeListener listener) {
68 myListeners.remove(listener);
69 }
70
71 @Override
72 public void setFocus() {
73 // TODO Auto-generated method stub
74
75 }
76 public void setSelection(ISelection selection) {
77 this .selection = selection;
78 }
79 }
80

注意我们为listviewer添加了Selection Change Listener 方法,并通知到了PersonPlugin。

接着,在detail view中,我们只用实现IPropertyChangeListener即可,以下是detail view的代码:

1 package hellorcp;
2
3 import java.util.ArrayList;
4
5 import org.eclipse.jface.util.IPropertyChangeListener;
6 import org.eclipse.jface.viewers.ISelection;
7 import org.eclipse.jface.viewers.IStructuredSelection;
8 import org.eclipse.jface.viewers.TableViewer;
9 import org.eclipse.swt.SWT;
10 import org.eclipse.swt.widgets.Composite;
11 import org.eclipse.swt.widgets.Group;
12 import org.eclipse.swt.widgets.Label;
13 import org.eclipse.swt.widgets.Table;
14 import org.eclipse.swt.widgets.Text;
15 import org.eclipse.ui.ISelectionListener;
16 import org.eclipse.ui.IWorkbenchPart;
17 import org.eclipse.ui.part.ViewPart;
18
19 import hellorcp.PersonPlugin;
20
21 import hellorcp.Person;
22
23 public class DetailView extends ViewPart implements IPropertyChangeListener {
24
25 private Table table;
26 private Text text_1;
27 private Text text;
28 @SuppressWarnings( " unchecked " )
29 ArrayList myListeners;
30 public DetailView() {
31 super ();
32 // TODO Auto-generated constructor stub
33 }
34
35 @Override
36 public void createPartControl(Composite parent) {
37 final Composite composite = new Composite(parent, SWT.NONE);
38
39 final Group group = new Group(composite, SWT.NONE);
40 group.setBounds( 0 , 0 , 494 , 62 );
41
42 final Label label = new Label(group, SWT.NONE);
43 label.setText( " 姓名 " );
44 label.setBounds( 10 , 32 , 24 , 12 );
45
46 text = new Text(group, SWT.BORDER);
47 text.setBounds( 40 , 29 , 80 , 15 );
48
49 final Label label_1 = new Label(group, SWT.NONE);
50 label_1.setText( " 性别 " );
51 label_1.setBounds( 172 , 32 , 30 , 12 );
52
53 text_1 = new Text(group, SWT.BORDER);
54 text_1.setBounds( 208 , 29 , 80 , 15 );
55
56 final TableViewer tableViewer = new TableViewer(composite, SWT.BORDER);
57 table = tableViewer.getTable();
58 table.setLinesVisible( true );
59 table.setHeaderVisible( true );
60 table.setBounds( 0 , 68 , 494 , 270 );
61 initializeToolBar();
62 // 注册事件
63 PersonPlugin.getInstance().addPropertyChangeListener( this );
64
65 }
66
67 @Override
68 public void setFocus() {
69 // TODO Auto-generated method stub
70
71 }
72 private void initializeToolBar() {
73 }
74
75 // public void selectionChanged(IWorkbenchPart part, ISelection selection) {
76 // IStructuredSelection structuredSelection = (IStructuredSelection)selection;
77 // Object obj = structuredSelection.getFirstElement();
78 // Person tempPerson = (Person)obj;
79 // if(tempPerson != null)
80 // text.setText(tempPerson.getName());
81 // text_1.setText(tempPerson.getSex());
82 //
83 // }
84 public void propertyChange(org.eclipse.jface.util.PropertyChangeEvent event) {
85 if ( event.getProperty().equals( " HelloRcpEvent " )) {
86 Object val = event.getNewValue();
87 Person tempPerson = (Person)val;
88 if (tempPerson != null ) {
89 text.setText(tempPerson.getName());
90 text_1.setText(tempPerson.getSex());
91 }
92 }
93
94 }
95
96 }
97
采用属性改变监听模式能够更加灵活地在插件之间、插件的各个view之间传递信息,突破了传递信息必须与UI相关的局限,而且还可以异步传递信息,这些信息可以是插件后台JOB定期运行获得的信息,以及定期从数据库中获得的信息等等。不难看到,属性改变监听器扩展了插件之间,视图之间,前台与后台之间的消息传递场景,是我们开发Eclipse RCP应用更好的选择。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值