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应用程序,可是,这个程序没有任何功能,难以激起咱们学习的兴趣。在这一节,咱们将一块儿探索怎样在程序中添加菜单和工具条。先看一下成果:html

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

rcp19.JPGjava

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

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

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

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

 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类。app

到这里,谜底已经揭晓,要建立咱们本身的菜单和工具条,就必定是在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类的子类了。eclipse

下面请看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吧:

 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
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(56, 41, 36, 12);
31
32        text = new Text(container, SWT.BORDER);
33        text.setBounds(98, 38, 80, 15);
34
35        final Label label_1 = new Label(container, SWT.NONE);
36        label_1.setText("性别:");
37        label_1.setBounds(212, 41, 30, 12);
38
39        text_1 = new Text(container, SWT.BORDER);
40        text_1.setBounds(252, 38, 80, 15);
41
42        final TableViewer tableViewer = new TableViewer(container, SWT.BORDER);
43        //tableViewer.setInput(new Object());
44        table = tableViewer.getTable();
45        table.setBounds(56, 75, 374, 143);
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文件,代码以下:

 1 package cn.blogjava.youxia.rcp_start;
 2
 3 import org.eclipse.ui.IPageLayout;
 4 import org.eclipse.ui.IPerspectiveFactory;
 5
 6 public 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文件,在该文件中,咱们能够配置咱们的样式。在这里,它的内容以下:

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(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:

 1 package cn.blogjava.youxia.views;
 2
 3 public 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中填入了几个初始化数据:

 1 package cn.blogjava.youxia.views;
 2 import java.util.ArrayList;
 3
 4 public 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:

 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
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:

 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
 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就能够,以下:

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

1 OleClientSite 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(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  {
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

 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
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(104, new 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,                // 选定色彩深度
         0, 0, 0, 0, 0, 0,          // 忽略的色彩位
         0,                 // 无Alpha缓存
         0,                 // 忽略Shift Bit
         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'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(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(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                dispose();
            }
        });



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

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

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(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(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(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);
        }
    }
        
        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);
        }
}
相关文章
相关标签/搜索