Eclipse RCP開發桌面程序

 

所謂RCP,就是Rich Client Platform的縮寫,即富客戶平臺,是Eclipse進化的產物(自3.0版以後出現),是Eclipse組織向用戶提供的強大的開放性開發平臺,能夠使用戶方便地創建自己的基於Eclipse的應用程序,並且這些應用程序能夠得到Eclipse的底層支持。更重要的是,我們可以利用Java創建象Eclipse這麼漂亮的桌面程序。

  我相信,在未來的幾年裏,RCP一定會變得非常流行。使用RCP,我們可以開發界面象Eclipse這樣漂亮的桌面程序,比如醫院管理系統啊、CAD軟件等等。遺憾的是,目前在國內基本上找不到關於RCP的中文資料,我們只能通過自己的探索來爲我們的程序添加我們想要的功能。

  下面讓我們一步一步來建立一個Eclipse RCP程序,下面的內容可以說在Google上一搜一大把,有些人會覺得乏味,但是沒關係,這只是一個快速的起步。

  選擇“新建--項目”,選擇“插件項目”:
rcp00.JPG

rcp01.JPG

點下一步,輸入項目名稱,選擇Eclipse版本,我這裏選擇的是3.2:
rcp02.JPG

  點下一步,插件標識和插件名稱可以更改,其他的內容都可以保持默認,一定要記得選中富客戶機應用程序支持:
rcp03.JPG

  點下一步,選中一個模板,這裏選一個最簡單的,到時候看源代碼的時候便於理解:
rcp04.JPG

  點下一步,改一下應用程序標題:
rcp05.JPG

  點完成,我們可以在項目上面點右鍵,選擇按Eclipse程序運行,就可以看到效果了:
rcp16.JPG

rcp17.JPG

  在這個程序中,窗口上顯示的是一個透視圖,透視圖中含有一個編輯器區域,以後,我們可以逐步爲這個程序添加菜單、工具條和爲這個透視圖添加視圖、編輯器等等。

  現在,這個程序只能在Eclipse環境下運行,而RCP的目標是創建可以獨立運行的應用程序,我們的事情還沒完呢。下一步,在項目上點右鍵,創建產品配置文件:
rcp06.JPG

  輸入產品配置文件名:

rcp07.JPG

  生成的產品配置文件在編輯器中打開,應該是這個樣子的:
rcp09.JPG

  剛開始,上面的幾個文本框都是空的,點新建按鈕之後,彈出如下的對話框,輸入產品名稱後,點完成就行了。

rcp08.JPG

  點擊配置文件中的“啓動程序”,我們可以試着啓動我們的RCP程序。結果呢,會出錯。原因很簡單,因爲我們沒有爲我們的程序選中它依賴的插件。

   選中配置文件的“配置”選項卡,添加以下幾個依賴項,記住,一定要把我們自己,也就是com.blogjava.youxia.rcp_start加進依賴項,否則會出錯。最開始的時候,就是這麼一點小問題,讓我浪費了幾天時間。
rcp10.JPG

  再點擊添加必須的插件,自動添加其它的依賴項。

  再下一步,設置項目的構建路徑,如下圖:
rcp11.JPG

  下一步,導出我們的程序:
rcp12.JPG

rcp13.JPG

  點下一步,輸入我們程序導出的目錄,如下圖:
rcp14.JPG

  點完成按鈕之後,我們的程序就導出到我們的指定的目錄中了,打開這個目錄,可以看到一個類似eclipse的程序圖標,雙擊運行,效果如下圖:rcp15.JPG

  最後,需要說明兩點:第一,如果希望生成的程序有自己的圖標,可以在產品配置文件中的最後兩個配置文件中設置;第二,生成的程序應該是沒有菜單欄的,因爲我的Eclipse安裝了MyEclipse,所以導出的程序就多了兩個菜單。

  好了,快速起步就到這裏了,以後再仔細研究生成的代碼和爲我們的程序添加功能。
=========================================================================
使用Eclipse RCP進行桌面程序開發(一):快速起步中,我們通過Eclipse的插件開發嚮導,逐步建立了一個RCP應用程序,但是,這個程序沒有任何功能,難以激起我們學習的興趣。在這一節,我們將一起探索怎樣在程序中添加菜單和工具條。先看一下成果:

圖一、圖二:帶有菜單和工具條的RCP程序
rcp18.JPG

rcp19.JPG

圖三:工具欄上的按鈕的提示文本
rcp20.JPG

圖四:點擊菜單項或者工具欄按鈕後,彈出一個簡單的對話框。
rcp21.JPG

這裏需要說明一點,爲什麼要在講菜單和工具欄的時候一起講對話框,這是因爲對話框是我們所能想到的最簡單最直接的用戶交互方式,在對話框上可以添加各種各樣的控件來實現複雜的功能,爲了讓我們點擊菜單項的時候能夠看到效果,這裏就用了一個簡單的對話框。當然,當我們以後接觸到視圖、編輯器和透視圖這樣的概念之後,我們能使用的用戶交互方式就不僅僅只是對話框了。

打開我們上一節使用嚮導建立的工程,可以發現工程下面自動生成了如下文件:
Application.java
ApplicationWorkbenchAdvisor.java
ApplicationWorkbenchWindowAdvisor.java
ApplicationActionBarAdvisor.java
Perspective.java
plugin.xml
這裏的Application.java是我們整個程序的入口點,我們的程序運行的時候,會先執行Application的run方法,run方法的代碼如下:

 1 public  Object run(Object args)  throws  Exception  {
 2         Display display  =  PlatformUI.createDisplay();
 3          try   {
 4              int  returnCode  =  PlatformUI.createAndRunWorkbench(display,  new  ApplicationWorkbenchAdvisor());
 5              if  (returnCode  ==  PlatformUI.RETURN_RESTART)  {
 6                  return  IPlatformRunnable.EXIT_RESTART;
 7             }
 8              return  IPlatformRunnable.EXIT_OK;
 9         }   finally   {
10             display.dispose();
11         }
12     }

在第4行我們可以看出,該入口函數將創建用戶界面的工作交給了ApplicationWorkbenchAdvisor類。接着,我們打開ApplicationWorkbenchAdvisor.java,代碼如下:

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

可以看出,這個類的工作就是爲我們的程序指定默認的透視圖,然後把創建窗口的工作交給了ApplicationWorkbenchWindowAdvisor類。接着,我們打開ApplicationWorkbenchWindowAdvisor.java文件,看到代碼如下:

 1 public   class  ApplicationWorkbenchWindowAdvisor  extends  WorkbenchWindowAdvisor  {
 2
 3      public  ApplicationWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer)  {
 4          super (configurer);
 5     }
 6
 7      public  ActionBarAdvisor createActionBarAdvisor(IActionBarConfigurer configurer)  {
 8          return   new  ApplicationActionBarAdvisor(configurer);
 9     }
10     
11      public   void  preWindowOpen()  {
12         IWorkbenchWindowConfigurer configurer  =  getWindowConfigurer();
13         configurer.setInitialSize( new  Point( 600 ,  450 ));
14         configurer.setShowCoolBar( true );
15         configurer.setShowStatusLine( false );
16         configurer.setTitle( " 第一個RCP程序 " );
17         
18     }
19        
20 }

這個類的功能很強大,我們可以重載它的preWindowCreate、postWindowCreate、preWindowOpen、postWindowOpen等方法,以便修改我們窗口的外觀。在這裏可以看出,我們重載了preWindowOpen方法來設置窗口的大小和讓工具欄可見。很顯然,這個類的另外一個功能,就是把創建菜單和工具欄的任務交給了ApplicationActionBarAdvisor類。

到這裏,謎底已經揭曉,要創建我們自己的菜單和工具條,就一定是在ApplicationActionBarAdvisor.java中做文章了。不錯,打開這個文件,我們可以看到這個類有兩個重要的方法:
protected void makeActions(IWorkbenchWindow window);
protected void fillMenuBar(IMenuManager menuBar);
我們可以在makeActions方法中創建我們的Action,什麼是Action呢?Action是jface中的一個概念,在jface中通過org.eclipse.jface.action中的Action和ActionContributionItem類實現了視圖和處理代碼的分離,這樣無論何時用戶觸發了一個控件的事件,都會激活一個相應的Action類實例來進行時間處理。毫無疑問,我們的菜單項是一個Action類的子類了。

下面請看ApplicationActionBarAdvisor.java的源代碼:

 1 package  cn.blogjava.youxia.rcp_start;
 2
 3 import  org.eclipse.jface.action.IMenuManager;
 4 import  org.eclipse.jface.action.MenuManager;
 5 import  org.eclipse.ui.IWorkbenchWindow;
 6 import  org.eclipse.ui.application.ActionBarAdvisor;
 7 import  org.eclipse.ui.application.IActionBarConfigurer;
 8 import  org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
 9 import  cn.blogjava.youxia.actions.Action1;
10
11 public   class  ApplicationActionBarAdvisor  extends  ActionBarAdvisor  {
12     
13      private  IWorkbenchAction action1;
14
15      public  ApplicationActionBarAdvisor(IActionBarConfigurer configurer)  {
16          super (configurer);
17     }
18
19      protected   void  makeActions(IWorkbenchWindow window)  {
20         action1  =   new  Action1(window);
21         action1.setText( " 第一個菜單項 " );
22         action1.setId( " cn.blogjava.youxia.actions.action1 " );
23         register(action1);
24     }
25
26      protected   void  fillMenuBar(IMenuManager menuBar)  {
27         MenuManager newMenu  =   new  MenuManager( " 第一個菜單 " , " cn.blogjava.youxia.firstmenu " );
28         menuBar.add(newMenu);
29         newMenu.add(action1);
30     }
31    
32 }

可以看出,我們通過創建cn.blogjava.youxia.actions.Action1類的實例來創建一個菜單項,然後把它加入到菜單newMenu中,然後再把newMenu加入menuBar中,整個過程很容易理解。那麼register(action1)是做什麼的呢?這是爲了把我們的Action的實例註冊到工作臺中,這樣當我們的工作臺銷燬的時候,我們的Action也可以被銷燬。

下面請看Action1類的源代碼:

 1 package  cn.blogjava.youxia.actions;
 2
 3 import  org.eclipse.jface.action.Action;
 4 import  org.eclipse.ui.IWorkbenchWindow;
 5 import  org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
 6 import  cn.blogjava.youxia.rcp_start.FirstDialog;
 7
 8
 9 public   class  Action1  extends  Action  implements  IWorkbenchAction  {
10     
11      private  IWorkbenchWindow workbenchWindow;
12
13      public  Action1(IWorkbenchWindow window)  {
14          if  (window  ==   null )  {
15              throw   new  IllegalArgumentException();
16         }
17
18          this .workbenchWindow  =  window;
19     }
20     
21      public   void  run()  {
22          //  make sure action is not disposed
23          if  (workbenchWindow  !=   null )  {
24              // 在這裏添加功能
25             FirstDialog dg  =   new  FirstDialog(workbenchWindow.getShell());
26             dg.open();
27             
28         }
29     }
30
31      public   void  dispose()  {
32         workbenchWindow  =   null ;
33
34     }
35
36 }

在構造函數中保存我們工作臺窗口的引用,在run方法中執行功能,是不是很簡單?在這裏,我們用到了一個對話框類cn.blogjava.youxia.rcp_start.FirstDialog,這個類從org.eclipse.swt.widgets.Dialog類繼承,熟悉swt的朋友一定不會陌生。我建議大家可以使用Designer插件,這個插件對swt/jface提供非常好的可視化支持,在這個對話框中,我們只簡單的添加了兩個按鈕。

FirstDialog.java源文件如下:

 1 package  cn.blogjava.youxia.rcp_start;
 2
 3 import  org.eclipse.swt.SWT;
 4 import  org.eclipse.swt.events.SelectionAdapter;
 5 import  org.eclipse.swt.events.SelectionEvent;
 6 import  org.eclipse.swt.widgets.Button;
 7 import  org.eclipse.swt.widgets.Dialog;
 8 import  org.eclipse.swt.widgets.Display;
 9 import  org.eclipse.swt.widgets.Shell;
10
11
12 public   class  FirstDialog  extends  Dialog  {
13
14      protected  Shell shell;
15     
16      private   int  result;
17
18      public  FirstDialog(Shell parent,  int  style)  {
19          super (parent, style);
20     }
21
22      public  FirstDialog(Shell parent)  {
23          this (parent, SWT.NONE);
24     }
25
26      public   int  open()  {
27         createContents();
28         shell.open();
29         shell.layout();
30         Display display  =  getParent().getDisplay();
31          while  ( ! shell.isDisposed())  {
32              if  ( ! display.readAndDispatch())
33                 display.sleep();
34         }
35          return  result;
36     }
37
38      protected   void  createContents()  {
39         shell  =   new  Shell(getParent(), SWT.DIALOG_TRIM  |  SWT.APPLICATION_MODAL);
40         shell.setSize( 150 ,  70 );
41         shell.setText( " 第一個對話框 " );
42
43          final  Button okButton  =   new  Button(shell, SWT.NONE);
44         okButton.addSelectionListener( new  SelectionAdapter()  {
45              public   void  widgetSelected(SelectionEvent e)  {
46                 result  =   1 ;
47                 shell.dispose();
48             }
49         } );
50         okButton.setText( " OK " );
51         okButton.setBounds( 10 ,  10 ,  48 ,  22 );
52
53          final  Button cancelButton  =   new  Button(shell, SWT.NONE);
54         cancelButton.addSelectionListener( new  SelectionAdapter()  {
55              public   void  widgetSelected(SelectionEvent e)  {
56                 result  =   2 ;
57                 shell.dispose();
58             }
59         } );
60         cancelButton.setText( " Cancel " );
61         cancelButton.setBounds( 89 ,  10 ,  48 ,  22 );
62     }
63
64 }
65


上面所講的,只是添加菜單和工具欄的第一種方法,這種方法把構建菜單的工作以靜態代碼的方式加入到了ApplicationActionBarAdvisor類中,如果需要修改用戶界面,則需要修改代碼並重新編譯。

添加菜單項的第二種方法就要簡單得多,而且修改起來也方便,還可以對菜單項實現更加靈活的控制,但是,需要對Eclipse的插件基礎有比較好的瞭解。那這第二種方法就是通過擴展actionSets擴展點來添加菜單。

對擴展點的擴展,可以通過編輯plugin.xml文件了實現,比如我們添加的第二個菜單項,其配置文件如下:

 

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

其實Eclipse爲我們提供了很好的可視化plugin.xml的編輯器,如下圖,我們可以對菜單的外觀進行和行爲進行靈活的控制:

rcp22.JPG

從配置文件中我們可以看到,我們爲這第二個菜單項指定的Action是cn.blogjava.youxia.actions.Action2類,這個類我們必須實現org.eclipse.ui.IWorkbenchWindowActionDelegate接口,這個接口中比org.eclipse.jface.actions.Action中多定義了一個方法public void selectionChanged(IAction action, ISelection selection),這個方法是必須的,以便工作臺窗口在用戶選定哪一項資源的時候通知我們的Action類的實例。其代碼如下:

 1 package  cn.blogjava.youxia.actions;
 2
 3 import  org.eclipse.jface.action.IAction;
 4 import  org.eclipse.jface.viewers.ISelection;
 5 import  org.eclipse.ui.IWorkbenchWindow;
 6 import  org.eclipse.ui.IWorkbenchWindowActionDelegate;
 7 import  cn.blogjava.youxia.rcp_start.FirstDialog;
 8
 9 public   class  Action2  implements  IWorkbenchWindowActionDelegate  {
10     
11      private  IWorkbenchWindow window;
12
13      public   void  dispose()  {
14          //  TODO 
15
16     }
17
18      public   void  init(IWorkbenchWindow window)  {
19          //  TODO 
20          this .window  =  window;
21
22     }
23
24      public   void  run(IAction action)  {
25          //  TODO 
26         FirstDialog dg  =   new  FirstDialog(window.getShell());
27         dg.open();
28
29     }
30
31      public   void  selectionChanged(IAction action, ISelection selection)  {
32          //  TODO 
33
34     }
35
36 }


總結:通過向工作臺中添加菜單和工具欄,並使用對話框作爲與用戶交互的基礎,我們已經基本上可以構建功能比較複雜的程序了。但這僅僅只是RCP編程的開端。下一節,我們將一起探索Eclipse的透視圖和視圖。
===============================================================
Eclipse RCP開發中,和用戶進行交互最多的界面,應該是視圖了,而透視圖就是將已有的視圖、菜單、工具欄、編輯器等等進行組合和佈局。看完這一節,我們就可以建立如下圖這樣的程序界面了。

rcp25.JPG

首先我們來介紹一下視圖,建立一個視圖其實非常簡單,只要從org.eclipse.ui.part.ViewPart繼承一個類,然後在plugin.xml中進行視圖的配置。其中,向視圖中添加控件的操作,我們即可以手工編寫,也可以使用Designer插件,我這裏推薦大家使用Designer插件,該插件對RCP提供功能非常強大的支持,如果使用Designer插件開發視圖,則plugin.xml文件也不需要我們手動修改了。

比如我們上圖中的第一個視圖,就是從ViewPart繼承一個類,然後在上面加入了幾個swt的控件,做得非常得簡單,而它的配置文件如下:

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


可以看到,實現這個視圖的class爲cn.blogjava.youxia.views.FirstView,那麼我們看看FirstView.java吧:

 1package cn.blogjava.youxia.views;
 2
 3import org.eclipse.jface.action.IMenuManager;
 4import org.eclipse.jface.action.IToolBarManager;
 5import org.eclipse.jface.viewers.TableViewer;
 6import org.eclipse.swt.SWT;
 7import org.eclipse.swt.widgets.Composite;
 8import org.eclipse.swt.widgets.Label;
 9import org.eclipse.swt.widgets.Table;
10import org.eclipse.swt.widgets.Text;
11import org.eclipse.ui.part.ViewPart;
12
13public class FirstView extends ViewPart {
14
15    private Table table;
16    private Text text_1;
17    private Text text;
18    public static final String ID = "cn.blogjava.youxia.views.FirstView"; //$NON-NLS-1$
19
20    /** *//**
21     * Create contents of the view part
22     * @param parent
23     */
24    @Override
25    public void createPartControl(Composite parent) {
26        Composite container = new Composite(parent, SWT.NONE);
27
28        final Label label = new Label(container, SWT.NONE);
29        label.setText("姓名:");
30        label.setBounds(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文件,代碼如下:

 1package cn.blogjava.youxia.rcp_start;
 2
 3import org.eclipse.ui.IPageLayout;
 4import org.eclipse.ui.IPerspectiveFactory;
 5
 6public class Perspective implements IPerspectiveFactory {
 7
 8    public void createInitialLayout(IPageLayout layout) {
 9        String editorArea = layout.getEditorArea();
10        layout.addView("cn.blogjava.youxia.views.FirstView", IPageLayout.RIGHT, 0.2f, editorArea);
11    }
12}

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

rcp23.JPG

我們可以發現,上面這個視圖的標籤不是我們通常看到的波浪形,我們可以通過配置文件的方式來更改產品的樣式。
首先,在plugin.xml中對org.eclipse.core.runtime.products擴展點的屬性進行更改,如下:

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


可見,我們爲我們的產品添加了一個prefereneCustomization屬性,該屬性的值爲plugin_customization.ini文件,在該文件中,我們可以配置我們的樣式。在這裏,它的內容如下:

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


事實上,在這個文件中可以定義的參數有上百個,大家可以查看Eclipse的文檔。
這個時候,效果應該是這樣的了:
rcp24.JPG

好了,我們現在對以上的代碼做一個總結。我不是寫教科書,在Blog中也沒有寫得那麼詳細的條件。我們這裏主要關注在哪個地方對代碼進行擴展,可以達到我們想要的效果。比如,我們要創建視圖,就是需要擴展org.eclipse.ui.part.ViewPart類,然後向其中添加控件,再然後配置plugin.xml文件,最後修改透視圖的代碼,以便它能夠顯示出來。

在ViewPart類中,我們添加控件的操作主要是在public void createPartControl(Composite parent)這個方法中進行,而方法最後會調用以下三個方法:
createActions();
initializeToolBar();
initializeMenu();
從這三個方法的方法名我們不難看出,它們的功能是創建視圖特有的菜單欄和工具欄的,結合上一小節的內容,我們應該很快就可以探索到怎麼給視圖添加漂亮的工具欄了,這裏我不再羅嗦。

再來看Perspective.java,不難發現,所有的透視圖類都需要實現IPerspectiveFactory接口,而該接口的createInitialLayout方法,就是描述工作臺窗口中編輯器和視圖的佈局。默認情況下,透視圖中只包含一個編輯器區域,就是我們第一節中看到的那個效果。在createInitialLayou中,我們可以通過以下幾個方法向透視圖中添加視圖、編輯器和菜單:
addView   —— 添加視圖
addActionSet —— 添加菜單和工具欄
createFolder —— 創建一個IForderLayou,可以讓多個視圖重疊在同一個位置

寫到這裏,肯定有人會問,如果我要創建一個象Eclipse中的資源視圖這樣的視圖,該怎麼做呢?這我們就要感謝org.eclipse.jface.viewers包了,Viewer,這裏翻譯爲查看器,它和視圖是不一樣的。JFace查看器是Jface對SWT部件的封裝,它簡化了我們對小部件的操作。在使用查看器的時候,它的數據使用單獨的模型對象來保存,使用查看器的setInput方法可以爲查看器設置模型,此外,在使用查看器的時候,需要爲它提供ContentProvider(內容提供器)和LabelProvider(標籤提供器)。

JFace查看器主要分爲以下幾類:
1. ListViewer: 對應於SWT的列表控件,目的是將列表中的元素映射至SWT列表控件
2. TreeViewer: 對應於SWT的樹控件,提供樹的展開和摺疊等基本操作
3. TableViewer: 對應於SWT的表控件,映射表中的元素
4. TextViewer: 對應於SWT的StyledText控件,創建編輯器的時候,使用這個查看器是最合適不過了。

好了,介紹性的文字就寫到這裏,我想大家一定已經知道了探索的方向。下面,我們看一個簡單的示例,就是這篇文章開頭給出的效果圖。它是我模仿醫院管理系統做的一個簡單例子,左邊的視圖就是使用了一個ListView查看器。這裏給出它的關鍵代碼:

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


可以看到,這裏需要設置內容提供器和標籤提供器和模型。下面,我們先創建一個病人類Person.java:

 1package cn.blogjava.youxia.views;
 2
 3public class Person {
 4    
 5    private String name;
 6    private String sex;
 7    public String getName() {
 8        return name;
 9    }
10    public void setName(String name) {
11        this.name = name;
12    }
13    public String getSex() {
14        return sex;
15    }
16    public void setSex(String sex) {
17        this.sex = sex;
18    }
19
20}


下面,創建模型類PersonModel.java,在構造函數中我們向List中填入了幾個初始化數據:

 1package cn.blogjava.youxia.views;
 2import java.util.ArrayList;
 3
 4public class PersonModel {
 5    
 6    private ArrayList<Person> list = new ArrayList<Person>();
 7    
 8    public interface Listener{
 9        public void add(Person p);
10        public void remove(Person p);
11    }
12    
13    private Listener listener;
14    
15    public PersonModel(){
16        //向list裏面填入幾個初始化數據
17        Person p1 = new Person();
18        p1.setName("病人1");
19        p1.setSex("男");
20        list.add(p1);
21        
22        Person p2 = new Person();
23        p2.setName("病人2");
24        p2.setSex("女");
25        list.add(p2);
26        
27    }
28
29    public void setListener(Listener listener){
30        this.listener = listener;
31    }
32    
33    public void add(Person p){
34        list.add(p);
35        if(listener != null){
36            listener.add(p);
37        }
38    }
39    
40    public void remove(Person p){
41        list.remove(p);
42        if(listener != null){
43            listener.remove(p);
44        }
45    }
46    
47    public ArrayList elements(){
48        return list;
49    }
50}


在這裏,我們還定義了一個Listener接口,爲什麼要有這麼一個接口呢?就是爲了讓我們模型中的數據被改變時,查看器能夠相應更改。下面,我們實現內容提供器,該內容提供器實現了PersonModel中定義的Listener接口,如下PersonContentProvider.java:

 1package cn.blogjava.youxia.views;
 2
 3import org.eclipse.jface.viewers.IStructuredContentProvider;
 4import org.eclipse.jface.viewers.Viewer;
 5import org.eclipse.jface.viewers.ListViewer;
 6
 7import cn.blogjava.youxia.views.PersonModel.Listener;
 8
 9public class PersonContentProvider implements IStructuredContentProvider,
10        Listener {
11
12    PersonModel input;
13    ListViewer viewer;
14    
15    public Object[] getElements(Object inputElement) {
16        // TODO 自動生成方法存根
17        return input.elements().toArray();
18    }
19
20    public void dispose() {
21        // TODO 自動生成方法存根
22        if(input != null){
23            input.setListener(null);
24        }
25        input = null;
26
27    }
28
29    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
30        // TODO 自動生成方法存根
31        viewer = (ListViewer)viewer;
32        input = (PersonModel)newInput;
33        input.setListener(this);
34
35    }
36
37    public void add(Person p) {
38        // TODO 自動生成方法存根
39        viewer.add(p);
40    }
41
42    public void remove(Person p) {
43        // TODO 自動生成方法存根
44        viewer.remove(p);
45    }
46
47}


我們知道,列表中的元素都是Person類的對象,怎麼讓他們顯示出來呢,需要實現標籤提供器,在標籤提供器中,我們可以設置對象顯示的圖標和文字,如下PersonLabelProvider.java:

 1package cn.blogjava.youxia.views;
 2
 3import org.eclipse.jface.viewers.ILabelProvider;
 4import org.eclipse.jface.viewers.ILabelProviderListener;
 5import org.eclipse.swt.graphics.Image;
 6
 7public class PersonLabelProvider implements ILabelProvider {
 8
 9    public Image getImage(Object element) {
10        return null;
11    }
12
13    public String getText(Object element) {
14        // TODO 自動生成方法存根
15        return ((Person)element).getName();
16    }
17
18    public void addListener(ILabelProviderListener listener) {
19        // TODO 自動生成方法存根
20
21    }
22
23    public void dispose() {
24        // TODO 自動生成方法存根
25
26    }
27
28    public boolean isLabelProperty(Object element, String property) {
29        // TODO 自動生成方法存根
30        return false;
31    }
32
33    public void removeListener(ILabelProviderListener listener) {
34        // TODO 自動生成方法存根
35
36    }
37
38}


運行程序,就得到了文章開頭的效果,但是不能在右邊的視圖中顯示病人的詳細信息。

如果要做到視圖的交互,需要添加事件的監聽器。使用Java 進行GUI開發的人應該都不會陌生,而我在RCP上,也處於探索階段,更深一步的內容,讓我們自己慢慢研究吧。
================================================================================
沒有嵌入Active X控件的視圖:
rcp29.JPG

嵌入瀏覽器控件,並顯示www.blogjava.net的主頁:
rcp30.JPG

在Windows系統下,OLE和Active X控件是兩個非常吸引人的技術,它們的基礎都是COM。OLE的體驗,就是平時我們可以把Excel表格嵌入Word文檔,或者把PDF嵌入瀏覽器顯示一樣,而Active X控件更是無處不在,做VB開發和網頁開發的人都應該很熟悉。使用Windows系統中豐富的Active X控件資源,我們可以實現功能非常強大的程序。

在Windows平臺下,SWT圖形工具包提供了對OLE的支持,Active X控件和OLE文檔都可以被很方便地嵌入SWT窗口部件或者JFace部件,在這裏,我只討論將Active X控件插入視圖。

在一個視圖中包含一個Active X控件需要兩個對象的支持,即一個OleFrame和一個OleClientSite對象。如果需要創建一個OLE應用,需要先後創建他們。創建OleFrame對象比較簡單,OleFrame類定義在org.eclipse.swt.ole.win32中,創建OleFrame對象只需要簡單的new就可以,如下:

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

在這個構造函數中,第一個參數指的是該OleFrame的母窗口部件,即Active X控件將要被嵌入的窗口部件。

在OleFrame的基礎上就可以創建OleClientSite對象,創建該對象需要知道控件的programID,這個ID的信息存放在windows的註冊表中。在我們這篇文章的例子中,我們使用的是一個瀏覽器控件,那麼我們怎麼知道瀏覽器控件的ProgID呢?我使用的是Visual Studio 2003自帶的OleView工具,如下圖:
rcp26.JPG

可以看到,Microsoft Web 瀏覽器的ProgID爲Shell.Explorer.2,我們可以這樣創建OleClientSite對象:

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


創建對象後,還需要激活,才能夠在RCP程序中對這些OLE對象進行操作。如下:

client.doVerb(OLE.OLEIVERB_SHOW);


然後,我們需要操作這個Active X控件,調用它的方法,或者設置它的屬性。比如在此例中,我們需要調用瀏覽器控件的navigate方法,以便我們的瀏覽器控件顯示www.blogjava.net的主頁。對Active X控件的操作通過OleAutomation對象來實現,創建OleAutomation對象的方法如下:

OleAutomation automation = new OleAutomation(client);

再通過automation.invoke()來調用Active X控件的方法,其中invoke方法有幾種重載形式,有隻帶一個int參數的,也有帶int和Variant[]兩個參數的,其中的int參數表示要調用的Active X控件的方法的ID,Variant[]參數就是要傳遞給Active X控件的方法的參數。

這裏我們要說一說Variant類,這個類提供了多個構造函數,可以方便的將int,float,long,double,string等等基本數據類型封裝爲Variant,比如我們要傳遞給瀏覽器控件的navigate方法的地址參數:

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


那麼我們怎麼才能得到Active X控件的方法的ID,還有它需要哪些參數呢?還是要藉助前面提到的OleView.exe工具,如下圖:
rcp27.JPG

rcp28.JPG

可以看到,Navigate方法的id爲0x00000068,轉化爲十進制就是104,而它需要的參數第一個是一個字符串,其它的都是可選的,因此,我們可以這樣調用它的方法:

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


下面,貼出本文例子中的視圖的代碼和菜單Action的代碼,在寫這篇文章之前,我一直在探索怎樣從菜單控制視圖,後來發現是這樣:
 window.getActivePage.getViewReferences();
雖然我不知道Eclipse中Page的概念究竟是什麼,但是只要能找到我要操作的視圖就可以了。視圖的代碼如下:

OleView.java

 1package cn.blogjava.youxia.views;
 2
 3import org.eclipse.jface.action.IMenuManager;
 4import org.eclipse.jface.action.IToolBarManager;
 5import org.eclipse.swt.SWT;
 6import org.eclipse.swt.widgets.Composite;
 7import org.eclipse.ui.part.ViewPart;
 8import org.eclipse.swt.ole.win32.OleFrame;
 9
10public class OleView extends ViewPart {
11    public OleFrame frame;
12
13    public static final String ID = "cn.blogjava.youxia.views.OleView"; //$NON-NLS-1$
14
15    /** *//**
16     * Create contents of the view part
17     * @param parent
18     */
19    @Override
20    public void createPartControl(Composite parent) {
21        frame = new OleFrame(parent, SWT.NONE);
22        
23        //
24        createActions();
25        initializeToolBar();
26        initializeMenu();
27    }
28
29    /** *//**
30     * Create the actions
31     */
32    private void createActions() {
33        // Create the actions
34    }
35
36    /** *//**
37     * Initialize the toolbar
38     */
39    private void initializeToolBar() {
40        IToolBarManager toolbarManager = getViewSite().getActionBars()
41                .getToolBarManager();
42    }
43
44    /** *//**
45     * Initialize the menu
46     */
47    private void initializeMenu() {
48        IMenuManager menuManager = getViewSite().getActionBars()
49                .getMenuManager();
50    }
51
52    @Override
53    public void setFocus() {
54        // Set the focus
55    }
56
57}
58


在這個視圖中,我創建了OleFrame對象,並讓它是public的,至於OleClientSite和OleAutomation對象,我們在點擊菜單項後創建。菜單動作的代碼如下:

OpenFileAction.java

 1package cn.blogjava.youxia.actions;
 2
 3
 4import org.eclipse.jface.action.IAction;
 5import org.eclipse.jface.viewers.ISelection;
 6import org.eclipse.swt.SWT;
 7import org.eclipse.swt.ole.win32.OLE;
 8import org.eclipse.swt.ole.win32.OleClientSite;
 9import org.eclipse.ui.IWorkbenchWindow;
10import org.eclipse.ui.IWorkbenchWindowActionDelegate;
11import org.eclipse.ui.*;
12import cn.blogjava.youxia.views.*;
13import org.eclipse.swt.ole.win32.OleAutomation;
14import org.eclipse.swt.ole.win32.Variant;
15
16public class OpenFileAction implements IWorkbenchWindowActionDelegate {
17
18    IWorkbenchWindow window;
19    
20    public void dispose() {
21        // TODO 自動生成方法存根
22
23    }
24
25    public void init(IWorkbenchWindow window) {
26        // TODO 自動生成方法存根
27        this.window = window;
28
29    }
30
31    public void run(IAction action) {
32        // TODO 自動生成方法存根
33        
34        IViewReference[] vfs = window.getActivePage().getViewReferences();
35        IViewPart vw;
36        for(int i=0; i<vfs.length; i++){
37             vw = vfs[i].getView(false);
38             if(vw.getTitle().equals("使用Active X控件")){
39                    OleClientSite client = new OleClientSite(((OleView)vw).frame,SWT.NONE,"Shell.Explorer.2");
40                    client.doVerb(OLE.OLEIVERB_SHOW);
41                    OleAutomation oa = new OleAutomation(client);
42                    Variant str = new Variant("http://www.blogjava.net/");
43                    oa.invoke(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);
        }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章