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  Object run(Object args)  throws  Exception  {
 2         Display display  =  PlatformUI.createDisplay();
 3          try   {
 4              int  returnCode  =  PlatformUI.createAndRunWorkbench(display,  new  ApplicationWorkbenchAdvisor());
 5              if  (returnCode  ==  PlatformUI.RETURN_RESTART)  {
 6                  return  IPlatformRunnable.EXIT_RESTART;
 7             }

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

12     }

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

 1 public   class  ApplicationWorkbenchAdvisor  extends  WorkbenchAdvisor  {
 2
 3      private   static   final  String PERSPECTIVE_ID  =   " cn.blogjava.youxia.rcp_start.perspective " ;
 4
 5      public  WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer)  {
 6          return   new  ApplicationWorkbenchWindowAdvisor(configurer);
 7     }

 8
 9      public  String getInitialWindowPerspectiveId()  {
10          return  PERSPECTIVE_ID;
11     }

12 }

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

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

 6
 7      public  ActionBarAdvisor createActionBarAdvisor(IActionBarConfigurer configurer)  {
 8          return   new  ApplicationActionBarAdvisor(configurer);
 9     }

10     
11      public   void  preWindowOpen()  {
12         IWorkbenchWindowConfigurer configurer  =  getWindowConfigurer();
13         configurer.setInitialSize( new  Point( 600 450 ));
14         configurer.setShowCoolBar( true );
15         configurer.setShowStatusLine( false );
16         configurer.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     
13      private  IWorkbenchAction action1;
14
15      public  ApplicationActionBarAdvisor(IActionBarConfigurer configurer)  {
16          super (configurer);
17     }

18
19      protected   void  makeActions(IWorkbenchWindow window)  {
20         action1  =   new  Action1(window);
21         action1.setText( " 第一个菜单项 " );
22         action1.setId( " cn.blogjava.youxia.actions.action1 " );
23         register(action1);
24     }

25
26      protected   void  fillMenuBar(IMenuManager menuBar)  {
27         MenuManager newMenu  =   new  MenuManager( " 第一个菜单 " , " cn.blogjava.youxia.firstmenu " );
28         menuBar.add(newMenu);
29         newMenu.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     
11      private  IWorkbenchWindow workbenchWindow;
12
13      public  Action1(IWorkbenchWindow window)  {
14          if  (window  ==   null {
15              throw   new  IllegalArgumentException();
16         }

17
18          this .workbenchWindow  =  window;
19     }

20     
21      public   void  run()  {
22          //  make sure action is not disposed
23          if  (workbenchWindow  !=   null {
24              // 在这里添加功能
25             FirstDialog dg  =   new  FirstDialog(workbenchWindow.getShell());
26             dg.open();
27             
28         }

29     }

30
31      public   void  dispose()  {
32         workbenchWindow  =   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
14      protected  Shell shell;
15     
16      private   int  result;
17
18      public  FirstDialog(Shell parent,  int  style)  {
19          super (parent, style);
20     }

21
22      public  FirstDialog(Shell parent)  {
23          this (parent, SWT.NONE);
24     }

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

35          return  result;
36     }

37
38      protected   void  createContents()  {
39         shell  =   new  Shell(getParent(), SWT.DIALOG_TRIM  |  SWT.APPLICATION_MODAL);
40         shell.setSize( 150 70 );
41         shell.setText( " 第一个对话框 " );
42
43          final  Button okButton  =   new  Button(shell, SWT.NONE);
44         okButton.addSelectionListener( new  SelectionAdapter()  {
45              public   void  widgetSelected(SelectionEvent e)  {
46                 result  =   1 ;
47                 shell.dispose();
48             }

49         }
);
50         okButton.setText( " OK " );
51         okButton.setBounds( 10 10 48 22 );
52
53          final  Button cancelButton  =   new  Button(shell, SWT.NONE);
54         cancelButton.addSelectionListener( new  SelectionAdapter()  {
55              public   void  widgetSelected(SelectionEvent e)  {
56                 result  =   2 ;
57                 shell.dispose();
58             }

59         }
);
60         cancelButton.setText( " Cancel " );
61         cancelButton.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     
11      private  IWorkbenchWindow window;
12
13      public   void  dispose()  {
14          //  TODO 
15
16     }

17
18      public   void  init(IWorkbenchWindow window)  {
19          //  TODO 
20          this .window  =  window;
21
22     }

23
24      public   void  run(IAction action)  {
25          //  TODO 
26         FirstDialog dg  =   new  FirstDialog(window.getShell());
27         dg.open();
28
29     }

30
31      public   void  selectionChanged(IAction action, ISelection selection)  {
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吧:

 1package cn.blogjava.youxia.views;
 2
 3import org.eclipse.jface.action.IMenuManager;
 4import org.eclipse.jface.action.IToolBarManager;
 5import org.eclipse.jface.viewers.TableViewer;
 6import org.eclipse.swt.SWT;
 7import org.eclipse.swt.widgets.Composite;
 8import org.eclipse.swt.widgets.Label;
 9import org.eclipse.swt.widgets.Table;
10import org.eclipse.swt.widgets.Text;
11import org.eclipse.ui.part.ViewPart;
12
13public class FirstView extends ViewPart {
14
15    private Table table;
16    private Text text_1;
17    private Text text;
18    public static final String ID = "cn.blogjava.youxia.views.FirstView"//$NON-NLS-1$
19
20    /**
21     * Create contents of the view part
22     * @param parent
23     */

24    @Override
25    public void createPartControl(Composite parent) {
26        Composite container = new Composite(parent, SWT.NONE);
27
28        final Label label = new Label(container, SWT.NONE);
29        label.setText("姓名:");
30        label.setBounds(56413612);
31
32        text = new Text(container, SWT.BORDER);
33        text.setBounds(98388015);
34
35        final Label label_1 = new Label(container, SWT.NONE);
36        label_1.setText("性别:");
37        label_1.setBounds(212413012);
38
39        text_1 = new Text(container, SWT.BORDER);
40        text_1.setBounds(252388015);
41
42        final TableViewer tableViewer = new TableViewer(container, SWT.BORDER);
43        //tableViewer.setInput(new Object());
44        table = tableViewer.getTable();
45        table.setBounds(5675374143);
46        table.setItemCount(10);
47        table.setLinesVisible(true);
48        //
49        createActions();
50        initializeToolBar();
51        initializeMenu();
52            }

53
54    /**
55     * Create the actions
56     */

57    private void createActions() {
58        // Create the actions
59    }

60
61    /**
62     * Initialize the toolbar
63     */

64    private void initializeToolBar() {
65        IToolBarManager toolbarManager = getViewSite().getActionBars()
66                .getToolBarManager();
67    }

68
69    /**
70     * Initialize the menu
71     */

72    private void initializeMenu() {
73        IMenuManager menuManager = getViewSite().getActionBars()
74                .getMenuManager();
75    }

76
77    @Override
78    public void setFocus() {
79        // Set the focus
80    }

81
82    }


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

 1package cn.blogjava.youxia.rcp_start;
 2
 3import org.eclipse.ui.IPageLayout;
 4import org.eclipse.ui.IPerspectiveFactory;
 5
 6public class Perspective implements IPerspectiveFactory {
 7
 8    public void createInitialLayout(IPageLayout layout) {
 9        String editorArea = 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文件,在该文件中,我们可以配置我们的样式。在这里,它的内容如下:

1org.eclipse.ui/SHOW_TRADITIONAL_STYLE_TABS=false
2org.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查看器。这里给出它的关键代码:

 1public void createPartControl(Composite parent) {
 2        
 3
 4        viewer = new ListViewer(parent, SWT.BORDER);
 5        viewer.setContentProvider(new PersonContentProvider());
 6        viewer.setLabelProvider(new PersonLabelProvider());
 7        viewer.setInput(new PersonModel());
 8        
 9        createActions();
10        initializeToolBar();
11        initializeMenu();
12    }


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

 1package cn.blogjava.youxia.views;
 2
 3public class Person {
 4    
 5    private String name;
 6    private String sex;
 7    public String getName() {
 8        return name;
 9    }

10    public void setName(String name) {
11        this.name = name;
12    }

13    public String getSex() {
14        return sex;
15    }

16    public void setSex(String sex) {
17        this.sex = sex;
18    }

19
20}


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

 1package cn.blogjava.youxia.views;
 2import java.util.ArrayList;
 3
 4public class PersonModel {
 5    
 6    private ArrayList<Person> list = new ArrayList<Person>();
 7    
 8    public interface Listener{
 9        public void add(Person p);
10        public void remove(Person p);
11    }

12    
13    private Listener listener;
14    
15    public PersonModel(){
16        //向list里面填入几个初始化数据
17        Person p1 = new Person();
18        p1.setName("病人1");
19        p1.setSex("");
20        list.add(p1);
21        
22        Person p2 = new Person();
23        p2.setName("病人2");
24        p2.setSex("");
25        list.add(p2);
26        
27    }

28
29    public void setListener(Listener listener){
30        this.listener = listener;
31    }

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

38    }

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

45    }

46    
47    public ArrayList elements(){
48        return list;
49    }

50}


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

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

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

25        input = null;
26
27    }

28
29    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
30        // TODO 自动生成方法存根
31        viewer = (ListViewer)viewer;
32        input = (PersonModel)newInput;
33        input.setListener(this);
34
35    }

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

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

46
47}


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

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

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

17
18    public void addListener(ILabelProviderListener listener) {
19        // TODO 自动生成方法存根
20
21    }

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

27
28    public boolean isLabelProperty(Object element, String property) {
29        // TODO 自动生成方法存根
30        return false;
31    }

32
33    public void removeListener(ILabelProviderListener listener) {
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就可以,如下:

1OleFrame frame = 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对象:

1OleClientSite client = 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对象的方法如下:

OleAutomation automation = 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方法的地址参数:

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


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

rcp28.JPG

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

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


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

OleView.java

 1package cn.blogjava.youxia.views;
 2
 3import org.eclipse.jface.action.IMenuManager;
 4import org.eclipse.jface.action.IToolBarManager;
 5import org.eclipse.swt.SWT;
 6import org.eclipse.swt.widgets.Composite;
 7import org.eclipse.ui.part.ViewPart;
 8import org.eclipse.swt.ole.win32.OleFrame;
 9
10public class OleView extends ViewPart {
11    public OleFrame frame;
12
13    public static final String ID = "cn.blogjava.youxia.views.OleView"//$NON-NLS-1$
14
15    /**
16     * Create contents of the view part
17     * @param parent
18     */

19    @Override
20    public void createPartControl(Composite parent) {
21        frame = new OleFrame(parent, SWT.NONE);
22        
23        //
24        createActions();
25        initializeToolBar();
26        initializeMenu();
27    }

28
29    /**
30     * Create the actions
31     */

32    private void createActions() {
33        // Create the actions
34    }

35
36    /**
37     * Initialize the toolbar
38     */

39    private void initializeToolBar() {
40        IToolBarManager toolbarManager = getViewSite().getActionBars()
41                .getToolBarManager();
42    }

43
44    /**
45     * Initialize the menu
46     */

47    private void initializeMenu() {
48        IMenuManager menuManager = getViewSite().getActionBars()
49                .getMenuManager();
50    }

51
52    @Override
53    public void setFocus() {
54        // Set the focus
55    }

56
57}

58


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

OpenFileAction.java

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

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

30
31    public void run(IAction action) {
32        // TODO 自动生成方法存根
33        
34        IViewReference[] vfs = window.getActivePage().getViewReferences();
35        IViewPart vw;
36        for(int i=0; i<vfs.length; i++){
37             vw = vfs[i].getView(false);
38             if(vw.getTitle().equals("使用Active X控件")){
39                    OleClientSite client = new OleClientSite(((OleView)vw).frame,SWT.NONE,"Shell.Explorer.2");
40                    client.doVerb(OLE.OLEIVERB_SHOW);
41                    OleAutomation oa = new OleAutomation(client);
42                    Variant str = new Variant("http://www.blogjava.net/");
43                    oa.invoke(104new Variant[]{str});
44        
45             }

46        }

47        
48    }

49
50    public void selectionChanged(IAction action, ISelection selection) {
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
13      public  Canvas canvas;
14     @Override
15      public   void  createPartControl(Composite parent)  {
16          //  TODO 自动生成方法存根
17         canvas  =   new  Canvas(parent,SWT.NONE);
18             }

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

25
26 }

27

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

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

12         }

13     }

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

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

12         }

13
14     }

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

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

14         }

15
16     }

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

 1 public   void  run(IAction action)  {
 2          //  TODO 自动生成方法存根
 3         IViewReference[] vfs  =  window.getActivePage().getViewReferences();
 4         IViewPart vw;
 5          for ( int  i = 0 ; i < vfs.length; i ++ ) {
 6              vw  =  vfs[i].getView( false );
 7               if (vw.getTitle().equals( " 画图板 " )) {
 8                      GC gc  =   new  GC(((CanvasView)vw).canvas);
 9                      Image img  =   new  Image(window.getShell().getDisplay(), " E:\\img.gif " );
10                      gc.drawImage(img,  280 200 );
11                      gc.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
13      public  Canvas canvas;
14     @Override
15      public   void  createPartControl(Composite parent)  {
16          //  TODO 自动生成方法存根
17         canvas  =   new  Canvas(parent,SWT.NONE);
18         canvas.addPaintListener( new  PaintListener()  {
19              public   void  paintControl(PaintEvent e)  {
20                  // 画椭圆
21                 e.gc.drawOval( 80 50 100 100 );
22                  // 画矩形
23                 e.gc.drawRectangle( 280 50 100 100 );
24                  // 画渐变矩形
25                 e.gc.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_BLUE));
26                  e.gc.fillGradientRectangle( 80 , 200 , 100 , 100 , false );
27                   // 画图形
28                  Image img  =   new  Image(PlatformUI.getWorkbench().getDisplay(), " E:\\img.gif " );
29                  e.gc.drawImage(img,  280 200 );
30
31         }

32         }
);
33     }

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

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

if (!(hRC=wglCreateContext(hDC)))         // 能否取得着色描述表?
    {
        KillGLWindow();              
// 重置显示区
        MessageBox(NULL,"Can't Create A GL Rendering Context.",
           
"ERROR",MB_OK|MB_ICONEXCLAMATION);
        
return FALSE;               // 返回 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(Composite parent) {
        
// TODO 自动生成方法存根
        GLData data = new GLData();
        data.depthSize 
= 1;
        data.doubleBuffer 
= true;
        GLCanvas canvas 
= new GLCanvas(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(Composite parent) {
        
// TODO 自动生成方法存根
        GLData data = new GLData();
        data.depthSize 
= 1;
        data.doubleBuffer 
= true;
        GLCanvas canvas 
= new GLCanvas(parent, SWT.NO_BACKGROUND, data);
        
//将绘图代码转移到定时器中
        Refresher rf = new Refresher(canvas);
        rf.run();
    }


Refresher类的代码如下:

class Refresher implements Runnable {
    
public static final int DELAY = 100;
    
    
private GLCanvas canvas;
    
    
public Refresher(GLCanvas canvas) {
        
this.canvas = canvas;
    }

    
    
public void run() {
        
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(Composite parent) {
        
// TODO 自动生成方法存根
        GLData data = new GLData();
        data.depthSize 
= 1;
        data.doubleBuffer 
= true;
        canvas 
= new GLCanvas(parent, SWT.NO_BACKGROUND, data);
        canvas.addControlListener(
new ControlAdapter() {
            
public void controlResized(ControlEvent e) {
                Rectangle rect 
= canvas.getClientArea();
                GL.glViewport(
00, rect.width, rect.height);
                
                
//选择投影矩阵
                GL.glMatrixMode(GL.GL_PROJECTION);
                
//重置投影矩阵
                GL.glLoadIdentity();
                
//设置窗口比例和透视图
                GLU.gluPerspective(45.0f, (float) rect.width / (float) rect.height, 0.1f100.0f);
                
//选择模型观察矩阵
                GL.glMatrixMode(GL.GL_MODELVIEW);
                
//重置模型观察矩阵
                GL.glLoadIdentity();
                
                
//黑色背景
                GL.glClearColor(0.0f0.0f0.0f0.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(
new DisposeListener() {
            
public void widgetDisposed(DisposeEvent e) {
                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)用来绘制三菱锥矩阵。如下:

package cn.blogjava.youxia.views;

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

public class OpenGLView extends ViewPart {

    GLCanvas canvas;
    @Override
    
public void createPartControl(Composite parent) {
        
// TODO 自动生成方法存根
        GLData data = new GLData();
        data.depthSize 
= 1;
        data.doubleBuffer 
= true;
        canvas 
= new GLCanvas(parent, SWT.NO_BACKGROUND, data);
        canvas.addControlListener(
new ControlAdapter() {
            
public void controlResized(ControlEvent e) {
                Rectangle rect 
= canvas.getClientArea();
                GL.glViewport(
00, rect.width, rect.height);
                
                
//选择投影矩阵
                GL.glMatrixMode(GL.GL_PROJECTION);
                
//重置投影矩阵
                GL.glLoadIdentity();
                
//设置窗口比例和透视图
                GLU.gluPerspective(45.0f, (float) rect.width / (float) rect.height, 0.1f100.0f);
                
//选择模型观察矩阵
                GL.glMatrixMode(GL.GL_MODELVIEW);
                
//重置模型观察矩阵
                GL.glLoadIdentity();
                
                
//黑色背景
                GL.glClearColor(0.0f0.0f0.0f0.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(
new DisposeListener() {
            
public void widgetDisposed(DisposeEvent e) {
                dispose();
            }

        }
);
        
/*
        
        
*/

        
        
        Refresher rf 
= new Refresher(canvas);
        rf.run();
    }


    @Override
    
public void setFocus() {
        
// TODO 自动生成方法存根

    }


}


class Refresher implements Runnable {
    
public static final int DELAY = 100;
    
    
private GLCanvas canvas;
    
private float rotate = 0.0f;
    
    
public Refresher(GLCanvas canvas) {
        
this.canvas = canvas;
    }

    
    
public void run() {
        
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(
04.5f-11);
            
//围绕y轴转起来
            rotate += 0.5;
            GL.glRotatef(rotate, 
01.0f0);
            
//调用递归函数,绘制三菱锥矩阵
            drawPyramid(0,0,0,4);
            canvas.swapBuffers();
            
this.canvas.getDisplay().timerExec(DELAY, this);
        }

    }

        
        
public void drawPyramid(float x, float y, float z, int n){
            
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);
        }

}
posted on 2007-08-02 11:25  kenty  阅读(30420)  评论(12编辑  收藏  举报