Eclipse RCP开发桌面程序

所谓RCP,就是Rich Client Platform的缩写,即富客户平台,是Eclipse进化的产物(自3.0版以后出现),是Eclipse组织向用户提供的强大的开放性开发平台,能 够使用户方便地创建自己的基于Eclipse的应用程序,并且这些应用程序能够得到Eclipse的底层支持。更重要的是,我们可以利用Java创建象 Eclipse这么漂亮的桌面程序。

  我相信,在未来的几年里,RCP一定会变得非常流行。使用RCP,我们可以开发界面象 Eclipse这样漂亮的桌面程序,比如医院管理系统啊、CAD软件等等。遗憾的是,目前在国内基本上找不到关于RCP的中文资料,我们只能通过自己的探 索来为我们的程序添加我们想要的功能。

  下面让我们一步一步来建立一个Eclipse RCP程序,下面的内容可以说在Google上一搜一大把,有些人会觉得乏味,但是没关系,这只是一个快速的起步。

  选择“新建--项目”,选择“插件项目”:
rcp00.JPG

rcp01.JPG

点下一步,输入项目名称,选择Eclipse版本,我这里选择的是3.2:
rcp02.JPG

  点下一步,插件标识和插件名称可以更改,其他的内容都可以保持默认,一定要记得选中富客户机应用程序支持:
rcp03.JPG

  点下一步,选中一个模板,这里选一个最简单的,到时候看源代码的时候便于理解:
rcp04.JPG

  点下一步,改一下应用程序标题:
rcp05.JPG

  点完成,我们可以在项目上面点右键,选择按Eclipse程序运行,就可以看到效果了:
rcp16.JPG

rcp17.JPG

  在这个程序中,窗口上显示的是一个透视图,透视图中含有一个编辑器区域,以后,我们可以逐步为这个程序添加菜单、工具条和为这个透视图添加视图、编辑器等等。

  现在,这个程序只能在Eclipse环境下运行,而RCP的目标是创建可以独立运行的应用程序,我们的事情还没完呢。下一步,在项目上点右键,创建产品配置文件:
rcp06.JPG

  输入产品配置文件名:

rcp07.JPG

  生成的产品配置文件在编辑器中打开,应该是这个样子的:
rcp09.JPG

  刚开始,上面的几个文本框都是空的,点新建按钮之后,弹出如下的对话框,输入产品名称后,点完成就行了。

rcp08.JPG

  点击配置文件中的“启动程序”,我们可以试着启动我们的RCP程序。结果呢,会出错。原因很简单,因为我们没有为我们的程序选中它依赖的插件。

  选中配置文件的“配置”选项卡,添加以下几个依赖项,记住,一定要把我们自己,也就是com.blogjava.youxia.rcp_start加进依赖项,否则会出错。最开始的时候,就是这么一点小问题,让我浪费了几天时间。
rcp10.JPG

  再点击添加必须的插件,自动添加其它的依赖项。

  再下一步,设置项目的构建路径,如下图:
rcp11.JPG

  下一步,导出我们的程序:
rcp12.JPG

rcp13.JPG

  点下一步,输入我们程序导出的目录,如下图:
rcp14.JPG

  点完成按钮之后,我们的程序就导出到我们的指定的目录中了,打开这个目录,可以看到一个类似eclipse的程序图标,双击运行,效果如下图:rcp15.JPG

  最后,需要说明两点:第一,如果希望生成的程序有自己的图标,可以在产品配置文件中的最后两个配置文件中设置;第二,生成的程序应该是没有菜单栏的,因为我的Eclipse安装了MyEclipse,所以导出的程序就多了两个菜单。

  好了,快速起步就到这里了,以后再仔细研究生成的代码和为我们的程序添加功能。
=========================================================================
使用Eclipse RCP进行桌面程序开发(一):快速起步中,我们通过Eclipse的插件开发向导,逐步建立了一个RCP应用程序,但是,这个程序没有任何功能,难以激起我们学习的兴趣。在这一节,我们将一起探索怎样在程序中添加菜单和工具条。先看一下成果:

图一、图二:带有菜单和工具条的RCP程序
rcp18.JPG

rcp19.JPG

图三:工具栏上的按钮的提示文本
rcp20.JPG

图四:点击菜单项或者工具栏按钮后,弹出一个简单的对话框。
rcp21.JPG

这里需要说明一点,为什么要在讲菜单和工具栏的时候一起讲对话框,这是因为对话框是我们所能想到的最简单最直接的用户交互方式,在对话框上可以添加 各种各样的控件来实现复杂的功能,为了让我们点击菜单项的时候能够看到效果,这里就用了一个简单的对话框。当然,当我们以后接触到视图、编辑器和透视图这 样的概念之后,我们能使用的用户交互方式就不仅仅只是对话框了。

打开我们上一节使用向导建立的工程,可以发现工程下面自动生成了如下文件:
Application.java
ApplicationWorkbenchAdvisor.java
ApplicationWorkbenchWindowAdvisor.java
ApplicationActionBarAdvisor.java
Perspective.java
plugin.xml
这里的Application.java是我们整个程序的入口点,我们的程序运行的时候,会先执行Application的run方法,run方法的代码如下:

1 public Objectrun(Objectargs) throws Exception {
2Displaydisplay=PlatformUI.createDisplay();
3try{
4intreturnCode=PlatformUI.createAndRunWorkbench(display,newApplicationWorkbenchAdvisor());
5if(returnCode==PlatformUI.RETURN_RESTART){
6returnIPlatformRunnable.EXIT_RESTART;
7}

8returnIPlatformRunnable.EXIT_OK;
9}
finally{
10display.dispose();
11}

12}

在第4行我们可以看出,该入口函数将创建用户界面的工作交给了ApplicationWorkbenchAdvisor类。接着,我们打开ApplicationWorkbenchAdvisor.java,代码如下:

1 public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {
2
3privatestaticfinalStringPERSPECTIVE_ID="cn.blogjava.youxia.rcp_start.perspective";
4
5publicWorkbenchWindowAdvisorcreateWorkbenchWindowAdvisor(IWorkbenchWindowConfigurerconfigurer){
6returnnewApplicationWorkbenchWindowAdvisor(configurer);
7}

8
9publicStringgetInitialWindowPerspectiveId(){
10returnPERSPECTIVE_ID;
11}

12}

可以看出,这个类的工作就是为我们的程序指定默认的透视图,然后把创建窗口的工作交给了 ApplicationWorkbenchWindowAdvisor类。接着,我们打开 ApplicationWorkbenchWindowAdvisor.java文件,看到代码如下:

1 public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {
2
3publicApplicationWorkbenchWindowAdvisor(IWorkbenchWindowConfigurerconfigurer){
4super(configurer);
5}

6
7publicActionBarAdvisorcreateActionBarAdvisor(IActionBarConfigurerconfigurer){
8returnnewApplicationActionBarAdvisor(configurer);
9}

10
11publicvoidpreWindowOpen(){
12IWorkbenchWindowConfigurerconfigurer=getWindowConfigurer();
13configurer.setInitialSize(newPoint(600,450));
14configurer.setShowCoolBar(true);
15configurer.setShowStatusLine(false);
16configurer.setTitle("第一个RCP程序");
17
18}

19
20}

这个类的功能很强大,我们可以重载它的preWindowCreate、postWindowCreate、preWindowOpen、 postWindowOpen等方法,以便修改我们窗口的外观。在这里可以看出,我们重载了preWindowOpen方法来设置窗口的大小和让工具栏可 见。很显然,这个类的另外一个功能,就是把创建菜单和工具栏的任务交给了ApplicationActionBarAdvisor类。

到这里,谜底已经揭晓,要创建我们自己的菜单和工具条,就一定是在ApplicationActionBarAdvisor.java中做文章了。不错,打开这个文件,我们可以看到这个类有两个重要的方法:
protected void makeActions(IWorkbenchWindow window);
protected void fillMenuBar(IMenuManager menuBar);
我 们可以在makeActions方法中创建我们的Action,什么是Action呢?Action是jface中的一个概念,在jface中通过 org.eclipse.jface.action中的Action和ActionContributionItem类实现了视图和处理代码的分离,这样 无论何时用户触发了一个控件的事件,都会激活一个相应的Action类实例来进行时间处理。毫无疑问,我们的菜单项是一个Action类的子类了。

下面请看ApplicationActionBarAdvisor.java的源代码:

1 package cn.blogjava.youxia.rcp_start;
2
3 import org.eclipse.jface.action.IMenuManager;
4 import org.eclipse.jface.action.MenuManager;
5 import org.eclipse.ui.IWorkbenchWindow;
6 import org.eclipse.ui.application.ActionBarAdvisor;
7 import org.eclipse.ui.application.IActionBarConfigurer;
8 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
9 import cn.blogjava.youxia.actions.Action1;
10
11 public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
12
13privateIWorkbenchActionaction1;
14
15publicApplicationActionBarAdvisor(IActionBarConfigurerconfigurer){
16super(configurer);
17}

18
19protectedvoidmakeActions(IWorkbenchWindowwindow){
20action1=newAction1(window);
21action1.setText("第一个菜单项");
22action1.setId("cn.blogjava.youxia.actions.action1");
23register(action1);
24}

25
26protectedvoidfillMenuBar(IMenuManagermenuBar){
27MenuManagernewMenu=newMenuManager("第一个菜单","cn.blogjava.youxia.firstmenu");
28menuBar.add(newMenu);
29newMenu.add(action1);
30}

31
32}

可以看出,我们通过创建cn.blogjava.youxia.actions.Action1类的实例来创建一个菜单项,然后把它加入到菜单 newMenu中,然后再把newMenu加入menuBar中,整个过程很容易理解。那么register(action1)是做什么的呢?这是为了把 我们的Action的实例注册到工作台中,这样当我们的工作台销毁的时候,我们的Action也可以被销毁。

下面请看Action1类的源代码:

1 package cn.blogjava.youxia.actions;
2
3 import org.eclipse.jface.action.Action;
4 import org.eclipse.ui.IWorkbenchWindow;
5 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
6 import cn.blogjava.youxia.rcp_start.FirstDialog;
7
8
9 public class Action1 extends Action implements IWorkbenchAction {
10
11privateIWorkbenchWindowworkbenchWindow;
12
13publicAction1(IWorkbenchWindowwindow){
14if(window==null){
15thrownewIllegalArgumentException();
16}

17
18this.workbenchWindow=window;
19}

20
21publicvoidrun(){
22//makesureactionisnotdisposed
23if(workbenchWindow!=null){
24//在这里添加功能
25FirstDialogdg=newFirstDialog(workbenchWindow.getShell());
26dg.open();
27
28}

29}

30
31publicvoiddispose(){
32workbenchWindow=null;
33
34}

35
36}

在构造函数中保存我们工作台窗口的引用,在run方法中执行功能,是不是很简单?在这里,我们用到了一个对话框类 cn.blogjava.youxia.rcp_start.FirstDialog,这个类从 org.eclipse.swt.widgets.Dialog类继承,熟悉swt的朋友一定不会陌生。我建议大家可以使用Designer插件,这个插 件对swt/jface提供非常好的可视化支持,在这个对话框中,我们只简单的添加了两个按钮。

FirstDialog.java源文件如下:

1 package cn.blogjava.youxia.rcp_start;
2
3 import org.eclipse.swt.SWT;
4 import org.eclipse.swt.events.SelectionAdapter;
5 import org.eclipse.swt.events.SelectionEvent;
6 import org.eclipse.swt.widgets.Button;
7 import org.eclipse.swt.widgets.Dialog;
8 import org.eclipse.swt.widgets.Display;
9 import org.eclipse.swt.widgets.Shell;
10
11
12 public class FirstDialog extends Dialog {
13
14protectedShellshell;
15
16privateintresult;
17
18publicFirstDialog(Shellparent,intstyle){
19super(parent,style);
20}

21
22publicFirstDialog(Shellparent){
23this(parent,SWT.NONE);
24}

25
26publicintopen(){
27createContents();
28shell.open();
29shell.layout();
30Displaydisplay=getParent().getDisplay();
31while(!shell.isDisposed()){
32if(!display.readAndDispatch())
33display.sleep();
34}

35returnresult;
36}

37
38protectedvoidcreateContents(){
39shell=newShell(getParent(),SWT.DIALOG_TRIM|SWT.APPLICATION_MODAL);
40shell.setSize(150,70);
41shell.setText("第一个对话框");
42
43finalButtonokButton=newButton(shell,SWT.NONE);
44okButton.addSelectionListener(newSelectionAdapter(){
45publicvoidwidgetSelected(SelectionEvente){
46result=1;
47shell.dispose();
48}

49}
);
50okButton.setText("OK");
51okButton.setBounds(10,10,48,22);
52
53finalButtoncancelButton=newButton(shell,SWT.NONE);
54cancelButton.addSelectionListener(newSelectionAdapter(){
55publicvoidwidgetSelected(SelectionEvente){
56result=2;
57shell.dispose();
58}

59}
);
60cancelButton.setText("Cancel");
61cancelButton.setBounds(89,10,48,22);
62}

63
64}

65


上面所讲的,只是添加菜单和工具栏的第一种方法,这种方法把构建菜单的工作以静态代码的方式加入到了ApplicationActionBarAdvisor类中,如果需要修改用户界面,则需要修改代码并重新编译。

添加菜单项的第二种方法就要简单得多,而且修改起来也方便,还可以对菜单项实现更加灵活的控制,但是,需要对Eclipse的插件基础有比较好的了解。那这第二种方法就是通过扩展actionSets扩展点来添加菜单。

对扩展点的扩展,可以通过编辑plugin.xml文件了实现,比如我们添加的第二个菜单项,其配置文件如下:

1 < extension
2 id ="cn.blogjava.youxia.actionset"
3 name ="我的菜单扩展"
4 point ="org.eclipse.ui.actionSets" >
5 < actionSet
6 description ="第一个扩展"
7 id ="RCP_Start.actionSet1"
8 label ="RCP_Start.actionSet1"
9 visible ="true" >
10 < action
11 class ="cn.blogjava.youxia.actions.Action2"
12 icon ="icons/alt_window_16.gif"
13 id ="RCP_Start.action2"
14 label ="第二个菜单项"
15 menubarPath ="cn.blogjava.youxia.firstmenu/additions"
16 style ="push"
17 toolbarPath ="additions"
18 tooltip ="第二个菜单项的按钮" />
19 </ actionSet >
20 </ extension >

其实Eclipse为我们提供了很好的可视化plugin.xml的编辑器,如下图,我们可以对菜单的外观进行和行为进行灵活的控制:

rcp22.JPG

从配置文件中我们可以看到,我们为这第二个菜单项指定的Action是cn.blogjava.youxia.actions.Action2类, 这个类我们必须实现org.eclipse.ui.IWorkbenchWindowActionDelegate接口,这个接口中比 org.eclipse.jface.actions.Action中多定义了一个方法public void selectionChanged(IAction action, ISelection selection),这个方法是必须的,以便工作台窗口在用户选定哪一项资源的时候通知我们的Action类的实例。其代码如下:

1 package cn.blogjava.youxia.actions;
2
3 import org.eclipse.jface.action.IAction;
4 import org.eclipse.jface.viewers.ISelection;
5 import org.eclipse.ui.IWorkbenchWindow;
6 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
7 import cn.blogjava.youxia.rcp_start.FirstDialog;
8
9 public class Action2 implements IWorkbenchWindowActionDelegate {
10
11privateIWorkbenchWindowwindow;
12
13publicvoiddispose(){
14//TODO
15
16}

17
18publicvoidinit(IWorkbenchWindowwindow){
19//TODO
20this.window=window;
21
22}

23
24publicvoidrun(IActionaction){
25//TODO
26FirstDialogdg=newFirstDialog(window.getShell());
27dg.open();
28
29}

30
31publicvoidselectionChanged(IActionaction,ISelectionselection){
32//TODO
33
34}

35
36}


总结:通过向工作台中添加菜单和工具栏,并使用对话框作为与用户交互的基础,我们已经基本上可以构建功能比较复杂的程序了。但这仅仅只是RCP编程的开端。下一节,我们将一起探索Eclipse的透视图和视图。
===============================================================
Eclipse RCP开发中,和用户进行交互最多的界面,应该是视图了,而透视图就是将已有的视图、菜单、工具栏、编辑器等等进行组合和布局。看完这一节,我们就可以建立如下图这样的程序界面了。

rcp25.JPG

首 先我们来介绍一下视图,建立一个视图其实非常简单,只要从org.eclipse.ui.part.ViewPart继承一个类,然后在 plugin.xml中进行视图的配置。其中,向视图中添加控件的操作,我们即可以手工编写,也可以使用Designer插件,我这里推荐大家使用 Designer插件,该插件对RCP提供功能非常强大的支持,如果使用Designer插件开发视图,则plugin.xml文件也不需要我们手动修改 了。

比如我们上图中的第一个视图,就是从ViewPart继承一个类,然后在上面加入了几个swt的控件,做得非常得简单,而它的配置文件如下:

1 < extension
2 point ="org.eclipse.ui.views" >
3 < view
4 class ="cn.blogjava.youxia.views.FirstView"
5 id ="cn.blogjava.youxia.views.FirstView"
6 name ="第一个View" />
7 </ extension >


可以看到,实现这个视图的class为cn.blogjava.youxia.views.FirstView,那么我们看看FirstView.java吧:

1 package cn.blogjava.youxia.views;
2
3 import org.eclipse.jface.action.IMenuManager;
4 import org.eclipse.jface.action.IToolBarManager;
5 import org.eclipse.jface.viewers.TableViewer;
6 import org.eclipse.swt.SWT;
7 import org.eclipse.swt.widgets.Composite;
8 import org.eclipse.swt.widgets.Label;
9 import org.eclipse.swt.widgets.Table;
10 import org.eclipse.swt.widgets.Text;
11 import org.eclipse.ui.part.ViewPart;
12
13 public class FirstView extends ViewPart {
14
15privateTabletable;
16privateTexttext_1;
17privateTexttext;
18publicstaticfinalStringID="cn.blogjava.youxia.views.FirstView";//$NON-NLS-1$
19
20/**
21*Createcontentsoftheviewpart
22*@paramparent
23*/

24@Override
25publicvoidcreatePartControl(Compositeparent){
26Compositecontainer=newComposite(parent,SWT.NONE);
27
28finalLabellabel=newLabel(container,SWT.NONE);
29label.setText("姓名:");
30label.setBounds(56,41,36,12);
31
32text=newText(container,SWT.BORDER);
33text.setBounds(98,38,80,15);
34
35finalLabellabel_1=newLabel(container,SWT.NONE);
36label_1.setText("性别:");
37label_1.setBounds(212,41,30,12);
38
39text_1=newText(container,SWT.BORDER);
40text_1.setBounds(252,38,80,15);
41
42finalTableViewertableViewer=newTableViewer(container,SWT.BORDER);
43//tableViewer.setInput(newObject());
44table=tableViewer.getTable();
45table.setBounds(56,75,374,143);
46table.setItemCount(10);
47table.setLinesVisible(true);
48//
49createActions();
50initializeToolBar();
51initializeMenu();
52}

53
54/**
55*Createtheactions
56*/

57privatevoidcreateActions(){
58//Createtheactions
59}

60
61/**
62*Initializethetoolbar
63*/

64privatevoidinitializeToolBar(){
65IToolBarManagertoolbarManager=getViewSite().getActionBars()
66.getToolBarManager();
67}

68
69/**
70*Initializethemenu
71*/

72privatevoidinitializeMenu(){
73IMenuManagermenuManager=getViewSite().getActionBars()
74.getMenuManager();
75}

76
77@Override
78publicvoidsetFocus(){
79//Setthefocus
80}

81
82}


其中,添加控件的代码由Disgner插件自动生成。这个时候,如果我们运行程序的话,我们的视图还不会被显示出来。为了让我们的视图可以显示,我们还需要修改Perspective.java文件,代码如下:

1 packagecn.blogjava.youxia.rcp_start ;
2
3 importorg.eclipse.ui.IPageLayout ;
4 importorg.eclipse.ui.IPerspectiveFactory ;
5
6 publicclassPerspectiveimplementsIPerspectiveFactory{
7
8 publicvoidcreateInitialLayout(IPageLayoutlayout){
9 StringeditorArea = layout.getEditorArea() ;
10 layout.addView( " cn.blogjava.youxia.views.FirstView " , IPageLayout.RIGHT , 0 .2f , editorArea) ;
11 }
12 }

运行程序,得到如下效果:

rcp23.JPG

我们可以发现,上面这个视图的标签不是我们通常看到的波浪形,我们可以通过配置文件的方式来更改产品的样式。
首先,在plugin.xml中对org.eclipse.core.runtime.products扩展点的属性进行更改,如下:

1 < extension
2 id = " product "
3 point = " org.eclipse.core.runtime.products " >
4 < product
5 application = " cn.blogjava.youxia.rcp_start.application "
6 name = " 第一个RCP程序 " >
7 < property
8 name = " preferenceCustomization "
9 value = " plugin_customization.ini " />
10 </ product >
11 </ extension >


可见,我们为我们的产品添加了一个prefereneCustomization属性,该属性的值为plugin_customization.ini文件,在该文件中,我们可以配置我们的样式。在这里,它的内容如下:

1 org.eclipse.ui/SHOW_TRADITIONAL_STYLE_TABS = false
2 org.eclipse.ui/DOCK_PERSPECTIVE_BAR = topRight


事实上,在这个文件中可以定义的参数有上百个,大家可以查看Eclipse的文档。
这个时候,效果应该是这样的了:
rcp24.JPG

好 了,我们现在对以上的代码做一个总结。我不是写教科书,在Blog中也没有写得那么详细的条件。我们这里主要关注在哪个地方对代码进行扩展,可以达到我们 想要的效果。比如,我们要创建视图,就是需要扩展org.eclipse.ui.part.ViewPart类,然后向其中添加控件,再然后配置 plugin.xml文件,最后修改透视图的代码,以便它能够显示出来。

在ViewPart类中,我们添加控件的操作主要是在public void createPartControl(Composite parent)这个方法中进行,而方法最后会调用以下三个方法:
createActions();
initializeToolBar();
initializeMenu();
从这三个方法的方法名我们不难看出,它们的功能是创建视图特有的菜单栏和工具栏的,结合上一小节的内容,我们应该很快就可以探索到怎么给视图添加漂亮的工具栏了,这里我不再罗嗦。

再 来看Perspective.java,不难发现,所有的透视图类都需要实现IPerspectiveFactory接口,而该接口的 createInitialLayout方法,就是描述工作台窗口中编辑器和视图的布局。默认情况下,透视图中只包含一个编辑器区域,就是我们第一节中看 到的那个效果。在createInitialLayou中,我们可以通过以下几个方法向透视图中添加视图、编辑器和菜单:
addView —— 添加视图
addActionSet —— 添加菜单和工具栏
createFolder —— 创建一个IForderLayou,可以让多个视图重叠在同一个位置

写 到这里,肯定有人会问,如果我要创建一个象Eclipse中的资源视图这样的视图,该怎么做呢?这我们就要感谢 org.eclipse.jface.viewers包了,Viewer,这里翻译为查看器,它和视图是不一样的。JFace查看器是Jface对SWT 部件的封装,它简化了我们对小部件的操作。在使用查看器的时候,它的数据使用单独的模型对象来保存,使用查看器的setInput方法可以为查看器设置模 型,此外,在使用查看器的时候,需要为它提供ContentProvider(内容提供器)和LabelProvider(标签提供器)。

JFace查看器主要分为以下几类:
1. ListViewer: 对应于SWT的列表控件,目的是将列表中的元素映射至SWT列表控件
2. TreeViewer: 对应于SWT的树控件,提供树的展开和折叠等基本操作
3. TableViewer: 对应于SWT的表控件,映射表中的元素
4. TextViewer: 对应于SWT的StyledText控件,创建编辑器的时候,使用这个查看器是最合适不过了。

好了,介绍性的文字就写到这里,我想大家一定已经知道了探索的方向。下面,我们看一个简单的示例,就是这篇文章开头给出的效果图。它是我模仿医院管理系统做的一个简单例子,左边的视图就是使用了一个ListView查看器。这里给出它的关键代码:

1 public void createPartControl(Compositeparent) {
2
3
4viewer=newListViewer(parent,SWT.BORDER);
5viewer.setContentProvider(newPersonContentProvider());
6viewer.setLabelProvider(newPersonLabelProvider());
7viewer.setInput(newPersonModel());
8
9createActions();
10initializeToolBar();
11initializeMenu();
12}


可以看到,这里需要设置内容提供器和标签提供器和模型。下面,我们先创建一个病人类Person.java:

1 package cn.blogjava.youxia.views;
2
3 public class Person {
4
5privateStringname;
6privateStringsex;
7publicStringgetName(){
8returnname;
9}

10publicvoidsetName(Stringname){
11this.name=name;
12}

13publicStringgetSex(){
14returnsex;
15}

16publicvoidsetSex(Stringsex){
17this.sex=sex;
18}

19
20}


下面,创建模型类PersonModel.java,在构造函数中我们向List中填入了几个初始化数据:

1 package cn.blogjava.youxia.views;
2 import java.util.ArrayList;
3
4 public class PersonModel {
5
6privateArrayList<Person>list=newArrayList<Person>();
7
8publicinterfaceListener{
9publicvoidadd(Personp);
10publicvoidremove(Personp);
11}

12
13privateListenerlistener;
14
15publicPersonModel(){
16//向list里面填入几个初始化数据
17Personp1=newPerson();
18p1.setName("病人1");
19p1.setSex("");
20list.add(p1);
21
22Personp2=newPerson();
23p2.setName("病人2");
24p2.setSex("");
25list.add(p2);
26
27}

28
29publicvoidsetListener(Listenerlistener){
30this.listener=listener;
31}

32
33publicvoidadd(Personp){
34list.add(p);
35if(listener!=null){
36listener.add(p);
37}

38}

39
40publicvoidremove(Personp){
41list.remove(p);
42if(listener!=null){
43listener.remove(p);
44}

45}

46
47publicArrayListelements(){
48returnlist;
49}

50}


在这里,我们还定义了一个Listener接口,为什么要有这么一个接口呢?就是为了让我们模型中的数据被改变时,查看器能够相应更改。下 面,我们实现内容提供器,该内容提供器实现了PersonModel中定义的Listener接口,如下 PersonContentProvider.java:

1 package cn.blogjava.youxia.views;
2
3 import org.eclipse.jface.viewers.IStructuredContentProvider;
4 import org.eclipse.jface.viewers.Viewer;
5 import org.eclipse.jface.viewers.ListViewer;
6
7 import cn.blogjava.youxia.views.PersonModel.Listener;
8
9 public class PersonContentProvider implements IStructuredContentProvider,
10 Listener {
11
12PersonModelinput;
13ListViewerviewer;
14
15publicObject[]getElements(ObjectinputElement){
16//TODO自动生成方法存根
17returninput.elements().toArray();
18}

19
20publicvoiddispose(){
21//TODO自动生成方法存根
22if(input!=null){
23input.setListener(null);
24}

25input=null;
26
27}

28
29publicvoidinputChanged(Viewerviewer,ObjectoldInput,ObjectnewInput){
30//TODO自动生成方法存根
31viewer=(ListViewer)viewer;
32input=(PersonModel)newInput;
33input.setListener(this);
34
35}

36
37publicvoidadd(Personp){
38//TODO自动生成方法存根
39viewer.add(p);
40}

41
42publicvoidremove(Personp){
43//TODO自动生成方法存根
44viewer.remove(p);
45}

46
47}


我们知道,列表中的元素都是Person类的对象,怎么让他们显示出来呢,需要实现标签提供器,在标签提供器中,我们可以设置对象显示的图标和文字,如下PersonLabelProvider.java:

1 package cn.blogjava.youxia.views;
2
3 import org.eclipse.jface.viewers.ILabelProvider;
4 import org.eclipse.jface.viewers.ILabelProviderListener;
5 import org.eclipse.swt.graphics.Image;
6
7 public class PersonLabelProvider implements ILabelProvider {
8
9publicImagegetImage(Objectelement){
10returnnull;
11}

12
13publicStringgetText(Objectelement){
14//TODO自动生成方法存根
15return((Person)element).getName();
16}

17
18publicvoidaddListener(ILabelProviderListenerlistener){
19//TODO自动生成方法存根
20
21}

22
23publicvoiddispose(){
24//TODO自动生成方法存根
25
26}

27
28publicbooleanisLabelProperty(Objectelement,Stringproperty){
29//TODO自动生成方法存根
30returnfalse;
31}

32
33publicvoidremoveListener(ILabelProviderListenerlistener){
34//TODO自动生成方法存根
35
36}

37
38}


运行程序,就得到了文章开头的效果,但是不能在右边的视图中显示病人的详细信息。

如果要做到视图的交互,需要添加事件的监听器。使用Java 进行GUI开发的人应该都不会陌生,而我在RCP上,也处于探索阶段,更深一步的内容,让我们自己慢慢研究吧。
================================================================================
没有嵌入Active X控件的视图:
rcp29.JPG

嵌入浏览器控件,并显示www.blogjava.net的主页:
rcp30.JPG

在 Windows系统下,OLE和Active X控件是两个非常吸引人的技术,它们的基础都是COM。OLE的体验,就是平时我们可以把Excel表格嵌入Word文档,或者把PDF嵌入浏览器显示一 样,而Active X控件更是无处不在,做VB开发和网页开发的人都应该很熟悉。使用Windows系统中丰富的Active X控件资源,我们可以实现功能非常强大的程序。

在Windows平台下,SWT图形工具包提供了对OLE的支持,Active X控件和OLE文档都可以被很方便地嵌入SWT窗口部件或者JFace部件,在这里,我只讨论将Active X控件插入视图。

在 一个视图中包含一个Active X控件需要两个对象的支持,即一个OleFrame和一个OleClientSite对象。如果需要创建一个OLE应用,需要先后创建他们。创建 OleFrame对象比较简单,OleFrame类定义在org.eclipse.swt.ole.win32中,创建OleFrame对象只需要简单的 new就可以,如下:

1 OleFrameframe = new OleFrame(parent,SWT.NONE);

在这个构造函数中,第一个参数指的是该OleFrame的母窗口部件,即Active X控件将要被嵌入的窗口部件。

在 OleFrame的基础上就可以创建OleClientSite对象,创建该对象需要知道控件的programID,这个ID的信息存放在windows 的注册表中。在我们这篇文章的例子中,我们使用的是一个浏览器控件,那么我们怎么知道浏览器控件的ProgID呢?我使用的是Visual Studio 2003自带的OleView工具,如下图:
rcp26.JPG

可以看到,Microsoft Web 浏览器的ProgID为Shell.Explorer.2,我们可以这样创建OleClientSite对象:

1 OleClientSiteclient = new OleClientSite(frame,SWT.NONE, " Shell.Explorer.2 " );


创建对象后,还需要激活,才能够在RCP程序中对这些OLE对象进行操作。如下:

client.doVerb(OLE.OLEIVERB_SHOW);


然后,我们需要操作这个Active X控件,调用它的方法,或者设置它的属性。比如在此例中,我们需要调用浏览器控件的navigate方法,以便我们的浏览器控件显示www.blogjava.net的主页。对Active X控件的操作通过OleAutomation对象来实现,创建OleAutomation对象的方法如下:

OleAutomationautomation = new OleAutomation(client);

再通过automation.invoke()来调用Active X控件的方法,其中invoke方法有几种重载形式,有只带一个int参数的,也有带int和Variant[]两个参数的,其中的int参数表示要调用 的Active X控件的方法的ID,Variant[]参数就是要传递给Active X控件的方法的参数。

这里我们要说一说Variant类,这个类提供了多个构造函数,可以方便的将int,float,long,double,string等等基本数据类型封装为Variant,比如我们要传递给浏览器控件的navigate方法的地址参数:

Varianturl = new Variant( " http://www.blogjava.net " );


那么我们怎么才能得到Active X控件的方法的ID,还有它需要哪些参数呢?还是要借助前面提到的OleView.exe工具,如下图:
rcp27.JPG

rcp28.JPG

可以看到,Navigate方法的id为0x00000068,转化为十进制就是104,而它需要的参数第一个是一个字符串,其它的都是可选的,因此,我们可以这样调用它的方法:

Varianturl = new Variant( " http://www.blogjava.net/ " );
automation.invoke(
104 , new Variant[] {url} );


下面,贴出本文例子中的视图的代码和菜单Action的代码,在写这篇文章之前,我一直在探索怎样从菜单控制视图,后来发现是这样:
window.getActivePage.getViewReferences();
虽然我不知道Eclipse中Page的概念究竟是什么,但是只要能找到我要操作的视图就可以了。视图的代码如下:

OleView.java

1 package cn.blogjava.youxia.views;
2
3 import org.eclipse.jface.action.IMenuManager;
4 import org.eclipse.jface.action.IToolBarManager;
5 import org.eclipse.swt.SWT;
6 import org.eclipse.swt.widgets.Composite;
7 import org.eclipse.ui.part.ViewPart;
8 import org.eclipse.swt.ole.win32.OleFrame;
9
10 public class OleView extends ViewPart {
11publicOleFrameframe;
12
13publicstaticfinalStringID="cn.blogjava.youxia.views.OleView";//$NON-NLS-1$
14
15/**
16*Createcontentsoftheviewpart
17*@paramparent
18*/

19@Override
20publicvoidcreatePartControl(Compositeparent){
21frame=newOleFrame(parent,SWT.NONE);
22
23//
24createActions();
25initializeToolBar();
26initializeMenu();
27}

28
29/**
30*Createtheactions
31*/

32privatevoidcreateActions(){
33//Createtheactions
34}

35
36/**
37*Initializethetoolbar
38*/

39privatevoidinitializeToolBar(){
40IToolBarManagertoolbarManager=getViewSite().getActionBars()
41.getToolBarManager();
42}

43
44/**
45*Initializethemenu
46*/

47privatevoidinitializeMenu(){
48IMenuManagermenuManager=getViewSite().getActionBars()
49.getMenuManager();
50}

51
52@Override
53publicvoidsetFocus(){
54//Setthefocus
55}

56
57}

58


在这个视图中,我创建了OleFrame对象,并让它是public的,至于OleClientSite和OleAutomation对象,我们在点击菜单项后创建。菜单动作的代码如下:

OpenFileAction.java

1 package cn.blogjava.youxia.actions;
2
3
4 import org.eclipse.jface.action.IAction;
5 import org.eclipse.jface.viewers.ISelection;
6 import org.eclipse.swt.SWT;
7 import org.eclipse.swt.ole.win32.OLE;
8 import org.eclipse.swt.ole.win32.OleClientSite;
9 import org.eclipse.ui.IWorkbenchWindow;
10 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
11 import org.eclipse.ui. * ;
12 import cn.blogjava.youxia.views. * ;
13 import org.eclipse.swt.ole.win32.OleAutomation;
14 import org.eclipse.swt.ole.win32.Variant;
15
16 public class OpenFileAction implements IWorkbenchWindowActionDelegate {
17
18IWorkbenchWindowwindow;
19
20publicvoiddispose(){
21//TODO自动生成方法存根
22
23}

24
25publicvoidinit(IWorkbenchWindowwindow){
26//TODO自动生成方法存根
27this.window=window;
28
29}

30
31publicvoidrun(IActionaction){
32//TODO自动生成方法存根
33
34IViewReference[]vfs=window.getActivePage().getViewReferences();
35IViewPartvw;
36for(inti=0;i<vfs.length;i++){
37vw=vfs[i].getView(false);
38if(vw.getTitle().equals("使用ActiveX控件")){
39OleClientSiteclient=newOleClientSite(((OleView)vw).frame,SWT.NONE,"Shell.Explorer.2");
40client.doVerb(OLE.OLEIVERB_SHOW);
41OleAutomationoa=newOleAutomation(client);
42Variantstr=newVariant("http://www.blogjava.net/");
43oa.invoke(104,newVariant[]{str});
44
45}

46}

47
48}

49
50publicvoidselectionChanged(IActionaction,ISelectionselection){
51//TODO自动生成方法存根
52
53}

54
55}

56


根据前面几节将的内容配置plugin.xml和修改Perspective.java的代码,就可以看到文章开头的效果了。
================================================================================

看完这篇文章,可以实现如下界面:
rcp32.JPG

rcp33.JPG


当 我第一次看到RCP的时候,我就梦想着有一天能够用它开发界面华丽的2D和3D程序,经历过前面的探索,今天终于可以揭开2D绘图的神秘面纱。在包资源管 理器的插件依赖项中,我们一眼就可以看到org.eclipse.swt.graphics包,毫无疑问,和2D绘图有关的类就在这个包中。还有一个 org.eclipse.swt.opengl包也很引人注目,但是里面却只有GLCanvas类和GLData类,怎么也找不到传说中的GL类和GLU 类,也许下一篇文章我会写出关于3D的内容,但也许这个计划会夭折。

我刚开始发现org.eclipse.swt.graphics包的时候,要使用包里面的类却不是那么容易。比如,从名称上可以看出Image类是 处理图像的,但是它的构造函数无一例外都需要一个Device参数,于是,我迷惑了,Device,我该如何取得?再比如,GC类里面含有各种绘图的方 法,但是GC的构造函数需要Drawable参数,那Drawable我又该如何获得呢?

于是,我在网上搜索关于SWT 2D方面的内容,终于,让我看到了别人这样构造Image和GC:
Image img = new Image(display,"pic.gif");
GC gc = new GC(Image);
你能看出什么?为什么display是Device的子类?为什么Image是Drawabe的子类?最简单的办法,使用Eclipse的类层次结构视图查看:

rcp31.JPG

高,实在是高,在这里我不得不佩服SWT的设计者,在一开始,他们就把所有的控件都设计为可绘制的,而且使用Device来抽象绘图的设备。从图中 可以看出,所有的控件都实现Drawable接口,Image也实现Drawable接口,而Device的子类Display和Printer刚好分别 代表了屏幕和打印机。所有的谜团都在这里解决了,我们可以使用任何控件作为GC构造函数的参数来构造GC,然后绘图,而所有需要Device参数的地方, 我们可以根据我们需要输出的设备是显示器还是打印机而分别选择Display或Printer。

在org.eclipse.swt.widgets包中,有一个Canvas类,不难看出,如果我们要绘图,这个控件是最佳选择了。在下面的代码中,我们可以通过选择不同的菜单,分别绘制椭圆,矩形,填充渐变色的矩形和一个图像,运行效果就是文章开头的图片。

视图CanvasView.java

1 package cn.blogjava.youxia.views;
2
3 import org.eclipse.swt.widgets.Composite;
4 import org.eclipse.ui.part.ViewPart;
5 import org.eclipse.swt.widgets.Canvas;
6 import org.eclipse.swt.SWT;
7 import org.eclipse.swt.events. * ;
8 import org.eclipse.swt.graphics.Image;
9 import org.eclipse.ui.PlatformUI;
10
11 public class CanvasView extends ViewPart {
12
13publicCanvascanvas;
14@Override
15publicvoidcreatePartControl(Compositeparent){
16//TODO自动生成方法存根
17canvas=newCanvas(parent,SWT.NONE);
18}

19
20@Override
21publicvoidsetFocus(){
22//TODO自动生成方法存根
23
24}

25
26}

27

菜单项绘制椭圆DrawOvalAction.java的关键部分:

1 public void run(IActionaction) {
2//TODO自动生成方法存根
3IViewReference[]vfs=window.getActivePage().getViewReferences();
4IViewPartvw;
5for(inti=0;i<vfs.length;i++){
6vw=vfs[i].getView(false);
7if(vw.getTitle().equals("画图板")){
8GCgc=newGC(((CanvasView)vw).canvas);
9gc.drawOval(80,50,100,100);
10gc.dispose();
11}

12}

13}

菜单项绘制矩形DrawRectAction.java的关键部分:

1 public void run(IActionaction) {
2//TODO自动生成方法存根
3IViewReference[]vfs=window.getActivePage().getViewReferences();
4IViewPartvw;
5for(inti=0;i<vfs.length;i++){
6vw=vfs[i].getView(false);
7if(vw.getTitle().equals("画图板")){
8GCgc=newGC(((CanvasView)vw).canvas);
9gc.drawRectangle(280,50,100,100);
10gc.dispose();
11}

12}

13
14}

菜单项绘制渐变矩形DrawGradientAction.java的关键部分:

1 public void run(IActionaction) {
2//TODO自动生成方法存根
3IViewReference[]vfs=window.getActivePage().getViewReferences();
4IViewPartvw;
5for(inti=0;i<vfs.length;i++){
6vw=vfs[i].getView(false);
7if(vw.getTitle().equals("画图板")){
8GCgc=newGC(((CanvasView)vw).canvas);
9gc.setBackground(window.getShell().getDisplay().getSystemColor(SWT.COLOR_BLUE));
10gc.fillGradientRectangle(80,200,100,100,false);
11
12gc.dispose();
13}

14}

15
16}

菜单项绘制图像DrawImageAction.java的关键部分:

1 public void run(IActionaction) {
2//TODO自动生成方法存根
3IViewReference[]vfs=window.getActivePage().getViewReferences();
4IViewPartvw;
5for(inti=0;i<vfs.length;i++){
6vw=vfs[i].getView(false);
7if(vw.getTitle().equals("画图板")){
8GCgc=newGC(((CanvasView)vw).canvas);
9Imageimg=newImage(window.getShell().getDisplay(),"E://img.gif");
10gc.drawImage(img,280,200);
11gc.dispose();
12}

13}

14
15}


上面的方法虽然实现了绘图,但是还有一个问题,就是一旦我们的窗口最小化或者被别的窗口遮挡后,图形就会消失。原因其实很简单,一旦我们的 窗口最小化或者被别的窗口遮挡后,控件就需要重画,所以我们画的图形就不见了,如果要让控件重画的时候也能绘制图形,就应该使用 canvas.addPaintListener()为控件添加Paint事件的监听器。示例代码见下。

1 package cn.blogjava.youxia.views;
2
3 import org.eclipse.swt.widgets.Composite;
4 import org.eclipse.ui.part.ViewPart;
5 import org.eclipse.swt.widgets.Canvas;
6 import org.eclipse.swt.SWT;
7 import org.eclipse.swt.events. * ;
8 import org.eclipse.swt.graphics.Image;
9 import org.eclipse.ui.PlatformUI;
10
11 public class CanvasView extends ViewPart {
12
13publicCanvascanvas;
14@Override
15publicvoidcreatePartControl(Compositeparent){
16//TODO自动生成方法存根
17canvas=newCanvas(parent,SWT.NONE);
18canvas.addPaintListener(newPaintListener(){
19publicvoidpaintControl(PaintEvente){
20//画椭圆
21e.gc.drawOval(80,50,100,100);
22//画矩形
23e.gc.drawRectangle(280,50,100,100);
24//画渐变矩形
25e.gc.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_BLUE));
26e.gc.fillGradientRectangle(80,200,100,100,false);
27//画图形
28Imageimg=newImage(PlatformUI.getWorkbench().getDisplay(),"E://img.gif");
29e.gc.drawImage(img,280,200);
30
31}

32}
);
33}

34
35@Override
36publicvoidsetFocus(){
37//TODO自动生成方法存根
38
39}

40
41}

42

GC类的绘图方法很多,而且可以设置不同的前景色,背景色,画笔,画刷等等,还可以裁减图形,这些就靠我们慢慢探索了。
===========================================================================================
看完这一篇,我们应该可以使用OpenGL绘制如下图的场景了。该场景是一个旋转的三菱锥矩阵,下面是旋转到不同方位的截图:
rcp37.JPG

rcp38.JPG

rcp36.JPG

我整整花了一个星期的时间来研究SWT中的OpenGL,遇到的第一个困难是找不到传说中的GL类和GLU类,最后,通过搜索引擎终于找到了,原来使用Eclipse进行OpenGL开发,还需要另外下载OpenGL插件,如下图:
rcp34.JPG

这里有OpenGL的类库,还有一个示例,把类库下载下来,解压,放到Eclipse的Plugin目录下,然后在我们的项目中添加依赖项,就可以看到我们需要使用的类了,如下图:
rcp35.JPG

我们需要对OpenGL编程的一些基本概念有点了解,在OpenGL中,3D场景不是直接绘制到操作系统的窗口上的,而是有一个称为着色描述表(Rendering Context)的东西,我们这里简称它为context,OpenGL的绘图命令都是在当前context上进行绘制,然后再把它渲染到操作系统的设备描述表(Device Context)上,这里,我们可以简单的理解成把它渲染到窗口控件上(其实也可以渲染到全屏幕)。

在Windows中使用OpenGL编程比较麻烦,因为我们需要设置一个叫做象素格式的东西,大家只要看看下面的这段C代码,就知道我为什么说它麻烦了:

static PIXELFORMATDESCRIPTORpfd=     //pfd告诉窗口我们所希望的东东
     {
         sizeof(PIXELFORMATDESCRIPTOR),   
//上诉格式描述符的大小
         1,                //版本号
         PFD_DRAW_TO_WINDOW|        //格式必须支持窗口
         PFD_SUPPORT_OPENGL|        //格式必须支持OpenGL
         PFD_DOUBLEBUFFER,         //必须支持双缓冲
         PFD_TYPE_RGBA,           //申请RGBA格式
         bits,               //选定色彩深度
         0,0,0,0,0,0,         //忽略的色彩位
         0,                 //无Alpha缓存
         0,                 //忽略ShiftBit
         0,                 //无聚集缓存
         0,0,0,0,            //忽略聚集位
         16,                //16位Z-缓存(深度缓存)
         0,                 //无模板缓存
         0,                 //无辅助缓存
         PFD_MAIN_PLANE,          //主绘图层
         0,                 //保留
         0,0,0              //忽略层遮罩
    }
;
if ( ! (PixelFormat = ChoosePixelFormat(hDC, & pfd)))    // Windows找到相应的象素格式了吗?
     {
        KillGLWindow();               
//重置显示区
        MessageBox(NULL,"Can'tFindASuitablePixelFormat.",
           
"ERROR",MB_OK|MB_ICONEXCLAMATION);
        
returnFALSE;                //返回FALSE
    }

if ( ! SetPixelFormat(hDC,PixelFormat, & pfd))      // 能够设置象素格式么?
     {
        KillGLWindow();              
//重置显示区
        MessageBox(NULL,"Can'tSetThePixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        
returnFALSE;               //返回FALSE
    }

if ( ! (hRC = wglCreateContext(hDC)))          // 能否取得着色描述表?
     {
        KillGLWindow();              
//重置显示区
        MessageBox(NULL,"Can'tCreateAGLRenderingContext.",
           
"ERROR",MB_OK|MB_ICONEXCLAMATION);
        
returnFALSE;               //返回FALSE
    }


在SWT中,我们开发OpenGL应用就要简单得多,这全部要归功于org.eclipse.swt.opengl包下面的 GLCanvas类和GLData类,使用GLCanvas类可以直接创建一个用于OpenGL渲染的控件,至于设置象素格式这样复杂的问题,它已经帮我 们解决了,不信你看GLCanvas类的构造函数的实现。

GLCanvas类中的几个方法代表了我一开始提到的OpenGL的几个基本概 念,setCurrent()方法就是为了把该控件的context设置为OpenGL的当前着色描述表,然后使用GL和GLU类中的方法在当前 context上进行绘图,绘制完图形以后,再使用GLCanvas类的swapBuffers()方法交换缓冲区,也就是把context中的3D场景 渲染到控件上。

写到这里,大家肯定认为一切问题都应该迎刃而解了,然而,我却碰到了另外一个困难,这个困难就是SWT的OpenGL表现怪异,怎么个怪异呢?请看下面视图类的代码:

public void createPartControl(Compositeparent) {
//TODO自动生成方法存根
GLDatadata=newGLData();
data.depthSize
=1;
data.doubleBuffer
=true;
GLCanvascanvas
=newGLCanvas(parent,SWT.NO_BACKGROUND,data);
//设置该canvas的context为OpenGL的当前context
if(!canvas.isCurrent()){
canvas.setCurrent();
}

//这里可以进行OpenGL绘图

//交换缓存,将图形渲染到控件上
canvas.swapBuffers();
}


按道理,我们应该可以得到一个经典的3D的黑色场景,但是,我得到的却是这样的效果:
rcp39.JPG

相 当的郁闷啊,就是这个问题困扰了我至少一个星期。我把官方网站上的示例看了有看,就是找不到问题的关键所在。直到最后,我用了另外一个线程,每100ms 都调用canvas.swapBuffers()把场景渲染一遍问题才解决。由此可见,之所以回出现上面的问题,主要是因为我们渲染的场景很快会被操作系 统的其他绘图操作所覆盖,只有不断的渲染我们才能看到连续的3D图形。

我是这样实现让3D场景连续渲染的:

public void createPartControl(Compositeparent) {
//TODO自动生成方法存根
GLDatadata=newGLData();
data.depthSize
=1;
data.doubleBuffer
=true;
GLCanvascanvas
=newGLCanvas(parent,SWT.NO_BACKGROUND,data);
//将绘图代码转移到定时器中
Refresherrf=newRefresher(canvas);
rf.run();
}


Refresher类的代码如下:

class Refresher implements Runnable {
publicstaticfinalintDELAY=100;

privateGLCanvascanvas;

publicRefresher(GLCanvascanvas){
this.canvas=canvas;
}


publicvoidrun(){
if(this.canvas!=null&&!this.canvas.isDisposed()){
if(!canvas.isCurrent()){
canvas.setCurrent();
}

//这里添加OpenGL绘图代码
canvas.swapBuffers();
this.canvas.getDisplay().timerExec(DELAY,this);
}

}


}


问题解决,得到的效果图如下:
rcp40.JPG

OK,下面的任务就是完完全全的使用OpenGL的绘图功能了,不管你的OpenGL教材使用的是什么操作系统什么编程语言,你都能很简单的把它的概念拿到这里来使用。

使用OpenGL的第一件事,就是要设置投影矩阵、透视图和观察者矩阵,如果你不知道为什么要这么做,请查看OpenGL的基础教材,在这里,照搬就行了。为了让我们的控件在每次改变大小的时候都能够做这些设置,我们使用事件监听器,如下:

public void createPartControl(Compositeparent) {
//TODO自动生成方法存根
GLDatadata=newGLData();
data.depthSize
=1;
data.doubleBuffer
=true;
canvas
=newGLCanvas(parent,SWT.NO_BACKGROUND,data);
canvas.addControlListener(
newControlAdapter(){
publicvoidcontrolResized(ControlEvente){
Rectanglerect
=canvas.getClientArea();
GL.glViewport(
0,0,rect.width,rect.height);

//选择投影矩阵
GL.glMatrixMode(GL.GL_PROJECTION);
//重置投影矩阵
GL.glLoadIdentity();
//设置窗口比例和透视图
GLU.gluPerspective(45.0f,(float)rect.width/(float)rect.height,0.1f,100.0f);
//选择模型观察矩阵
GL.glMatrixMode(GL.GL_MODELVIEW);
//重置模型观察矩阵
GL.glLoadIdentity();

//黑色背景
GL.glClearColor(0.0f,0.0f,0.0f,0.0f);
//设置深度缓存
GL.glClearDepth(1.0f);
//启动深度测试
GL.glEnable(GL.GL_DEPTH_TEST);
//选择深度测试类型
GL.glDepthFunc(GL.GL_LESS);
//启用阴影平滑
GL.glShadeModel(GL.GL_SMOOTH);
//精细修正透视图
GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT,GL.GL_NICEST);
//清除屏幕和深度缓存
GL.glClear(GL.GL_COLOR_BUFFER_BIT|GL.GL_DEPTH_BUFFER_BIT);
//重置当前的模型观察矩阵
GL.glLoadIdentity();
}

}
);
canvas.addDisposeListener(
newDisposeListener(){
publicvoidwidgetDisposed(DisposeEvente){
dispose();
}

}
);



调用glLoadIdentity()之后,实际上将当前点移到了屏幕中心,X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。OpenGL屏幕中心的坐标值是XY轴上的0.0f点。中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。移入屏幕深处是负值,移出屏幕则是正值。

glTranslatef(x, y, z)是将当前点沿着XYZ轴移动,当我们绘图的时候,不是相对于屏幕中间,而是相对于当前点。

glBegin(GL.GL_TRIANGLES)的意思是开始绘制三角形,glEnd()告诉OpenGL三角形已经创建好了。通常当我们需要画3个顶点时,可以使用GL_TRIANGLES。在绝大多数的显卡上,绘制三角形是相当快速的。如果要画四个顶点,使用GL_QUADS的话会更方便。但据我所知,绝大多数的显卡都使用三角形来为对象着色。最后,如果想要画更多的顶点时,可以使用GL_POLYGON

glVertex(x,y,z)用来设置顶点,如果绘制三角形,这些顶点需要三个一组,如果绘制四边形,则是四个为一组。如果我们要为顶点着色,就需要glColor3f(r,g,b)方法,记住,每次设置以后,这个颜色就是当前颜色,直到再次调用该方法重新设置为止。

最后需要介绍的是glRotatef(Angle,Xvector,Yvector,Zvector)方法,该方法负责让对象围绕指定的轴旋转,Angle参数指转动的角度,注意是浮点数哦。

下面是我的视图类的全部代码,我把3D绘图的任务全部放到了另外一个线程中,并且定义了一个递归方法public void drawPyramid(float x, float y, float z, int n)用来绘制三菱锥矩阵。如下:

packagecn.blogjava.youxia.views;

importorg.eclipse.opengl.GL;
importorg.eclipse.opengl.GLU;
importorg.eclipse.swt.events.ControlAdapter;
importorg.eclipse.swt.events.ControlEvent;
importorg.eclipse.swt.events.DisposeEvent;
importorg.eclipse.swt.events.DisposeListener;
importorg.eclipse.swt.graphics.Rectangle;
importorg.eclipse.swt.opengl.GLData;
importorg.eclipse.swt.widgets.Composite;
importorg.eclipse.ui.part.ViewPart;
importorg.eclipse.swt.opengl.GLCanvas;
importorg.eclipse.swt.SWT;

publicclassOpenGLViewextendsViewPart{

GLCanvascanvas;
@Override
publicvoidcreatePartControl(Compositeparent){
//TODO自动生成方法存根
GLDatadata=newGLData();
data.depthSize
=1;
data.doubleBuffer
=true;
canvas
=newGLCanvas(parent,SWT.NO_BACKGROUND,data);
canvas.addControlListener(
newControlAdapter(){
publicvoidcontrolResized(ControlEvente){
Rectanglerect
=canvas.getClientArea();
GL.glViewport(
0,0,rect.width,rect.height);

//选择投影矩阵
GL.glMatrixMode(GL.GL_PROJECTION);
//重置投影矩阵
GL.glLoadIdentity();
//设置窗口比例和透视图
GLU.gluPerspective(45.0f,(float)rect.width/(float)rect.height,0.1f,100.0f);
//选择模型观察矩阵
GL.glMatrixMode(GL.GL_MODELVIEW);
//重置模型观察矩阵
GL.glLoadIdentity();

//黑色背景
GL.glClearColor(0.0f,0.0f,0.0f,0.0f);
//设置深度缓存
GL.glClearDepth(1.0f);
//启动深度测试
GL.glEnable(GL.GL_DEPTH_TEST);
//选择深度测试类型
GL.glDepthFunc(GL.GL_LESS);
//启用阴影平滑
GL.glShadeModel(GL.GL_SMOOTH);
//精细修正透视图
GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT,GL.GL_NICEST);
//清除屏幕和深度缓存
GL.glClear(GL.GL_COLOR_BUFFER_BIT|GL.GL_DEPTH_BUFFER_BIT);
//重置当前的模型观察矩阵
GL.glLoadIdentity();
}

}
);
canvas.addDisposeListener(
newDisposeListener(){
publicvoidwidgetDisposed(DisposeEvente){
dispose();
}

}
);
/*

*/



Refresherrf
=newRefresher(canvas);
rf.run();
}


@Override
publicvoidsetFocus(){
//TODO自动生成方法存根

}


}


classRefresherimplementsRunnable{
publicstaticfinalintDELAY=100;

privateGLCanvascanvas;
privatefloatrotate=0.0f;

publicRefresher(GLCanvascanvas){
this.canvas=canvas;
}


publicvoidrun(){
if(this.canvas!=null&&!this.canvas.isDisposed()){
if(!canvas.isCurrent()){
canvas.setCurrent();
}

//这里添加OpenGL绘图代码
GL.glLoadIdentity();
GL.glClear(GL.GL_COLOR_BUFFER_BIT
|GL.GL_DEPTH_BUFFER_BIT);
GL.glTranslatef(
0,4.5f,-11);
//围绕y轴转起来
rotate+=0.5;
GL.glRotatef(rotate,
0,1.0f,0);
//调用递归函数,绘制三菱锥矩阵
drawPyramid(0,0,0,4);
canvas.swapBuffers();
this.canvas.getDisplay().timerExec(DELAY,this);
}

}


publicvoiddrawPyramid(floatx,floaty,floatz,intn){
if(n==0)return;
//画一个三菱锥
GL.glBegin(GL.GL_TRIANGLES);
//画背面
GL.glColor3f(1.0f,0.0f,0.0f);
GL.glVertex3f(x,y,z);
GL.glColor3f(
0.0f,1.0f,0.0f);
GL.glVertex3f(x
+1.0f,y-1.63f,z-0.57f);
GL.glColor3f(
0.0f,0.0f,1.0f);
GL.glVertex3f(x
-1.0f,y-1.63f,z-0.57f);
//画底面
GL.glColor3f(1.0f,0.0f,0.0f);
GL.glVertex3f(x,y
-1.63f,z+1.15f);
GL.glColor3f(
0.0f,1.0f,0.0f);
GL.glVertex3f(x
-1.0f,y-1.63f,z-0.57f);
GL.glColor3f(
0.0f,0.0f,1.0f);
GL.glVertex3f(x
+1.0f,y-1.63f,z-0.57f);
//画左侧面
GL.glColor3f(1.0f,0.0f,0.0f);
GL.glVertex3f(x,y,z);
GL.glColor3f(
0.0f,1.0f,0.0f);
GL.glVertex3f(x
-1.0f,y-1.63f,z-0.57f);
GL.glColor3f(
0.0f,0.0f,1.0f);
GL.glVertex3f(x,y
-1.63f,z+1.15f);
//画右侧面
GL.glColor3f(1.0f,0.0f,0.0f);
GL.glVertex3f(x,y,z);
GL.glColor3f(
0.0f,1.0f,0.0f);
GL.glVertex3f(x,y
-1.63f,z+1.15f);
GL.glColor3f(
0.0f,0.0f,1.0f);
GL.glVertex3f(x
+1.0f,y-1.63f,z-0.57f);
GL.glEnd();
//递归调用,画多个三菱锥
drawPyramid(x,y-1.63f,z+1.15f,n-1);
drawPyramid(x
-1.0f,y-1.63f,z-0.57f,n-1);
drawPyramid(x
+1.0f,y-1.63f,z-0.57f,n-1);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值