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);
        }
}
相關文章
相關標籤/搜索