程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 定制SWT/RCP界面:如何編寫一個漂亮的SWT/RCP界面

定制SWT/RCP界面:如何編寫一個漂亮的SWT/RCP界面

編輯:關於JAVA

簡介:本文介紹如何利用 SWT/RCP 中的功能,編寫漂亮的 SWT/RCP 界面。文章開始介紹了 ECLIPSE 中關於圖形的一些基本知識,通過定義圖形來設置 SWT/RCP 界面的外形。然後介紹如何通過圖片來定義圖形外形,通過圖片來設置界面背景。最後介紹 RCP 中如何繼承相關接口,定義界面外形和背景的。

引言

Java 自從 1995 年發布以來,其圖形界面一直為世人所诟病。無論是早期的 AWT,還是後來的 Swing 應用程序不能像本地應用程序一樣執行,外觀也不一樣,響應的速度也不快。SWT 吸收了 AWT 和 Swing 實現的最好的部分 : 當可以得到本地組件時調用本地實現,當不能得到本地組件時使用 Java 實現。這就同時保證了與本地窗口部件相當的外觀,又提高了響應速度。

目前 SWT 已被廣泛應用於開發 JAVA 富客戶端,但是基於 SWT/RCP 的應用程序界面都是經典的 Eclipse 界面風格 : 藍色的標題欄、灰色的工具欄和狀態欄、四方形的視圖和編輯器、還是四方的控件,這些界面過於樸素,缺乏吸引力。

其實我們可以基於 SWT/RCP,編寫漂亮 GUI 的界面。自定義窗口的形狀,通過圖片背景來美化 SWT/RCP 窗體界面。窗體可以是多邊形,如矩形、圓形、以及這些形狀的疊加。按鈕控件也可以任意多邊形。

下圖是經典的 Eclipse 風格界面和美化後界面的比較:左面是一個經典的 SWT/RCP 窗體界面;右面是美化後的窗體,黑色的外框由一個矩形和一個圓形的疊加而成、圓形的播放按鈕、圓弧形的退出按鈕、不規則的放映視圖等。

圖 1. 經典窗體與美化後窗體的比較

本文首先介紹了 Eclipse 中圖形和界面的一些基本知識, 如何定義多邊形,如何把 SWT 的窗體設置成多邊形,以及如何定制多邊形的 SWT 窗口和控件;然後介紹如通過圖片來獲得多邊形外形,如何使用圖片來設置 SWT 窗體背景。最後通過剖析 Eclipse 的工作台(工作台就是 Workbench)的啟動運行過程,介紹如何編寫 RCP 的定義多邊形窗口,以及窗體上控件的多邊形外形。

SWT 圖形和窗體基礎

在開始介紹之前,我們先熟悉一下如何創建一個圖形的。SWT 是通過 org.eclipse.swt.graphics.Region來定義圖形的。 我們可以通過定義一個整型數組來定義各個點,這些點連接在一起就是一個圖形。坐標的順序是先 X 軸後 Y 軸,逆時針連接的。 例如下面定義的 4 個點數組 int[] rect = {Xa, Ya, Xb, Yb, Xc, Yc, Xd, Yd},A->B->C->D 連接而成就是一個矩形。int[] rect2 = {Xe, Ye, Xf, Yf, Xg, Yg, Xh, Yh},E->F->G->H 連接而成就是另外一個矩形。把這兩個矩形疊加在一起就是一個多邊形的外形。

圖 2. 多邊形

生成兩個矩形的函數如下:

清單 1. 生成兩個矩形的函數

int[] getBackRect1(){
  int [] rect = new int[2*4];
  //A
  rect[0] = 0;
  rect[1] = 0;
  //B
  rect[2] = 0;
  rect[3] = 320;
  //C
  rect[4] = 520;
  rect[5] = 320;
  //D
  rect[6] = 520;
  rect[7] = 0;
  //
  return rect;
  }
  int[] getBackRect2(){
  int [] rect = new int[2*4];
  //E
  rect[0] = 50;
  rect[1] = 320;
  //F
  rect[2] = 50;
  rect[3] = 370;
  //G
  rect[4] = 470;
  rect[5] = 370;
  //H
  rect[6] = 470;
  rect[7] = 320;
  //
  return rect;
  }

Region 類裡面也提供了直接生成矩形的函數 add(Rectangle rect)和 add(int x, int y, int width, int height)。如果要生成不規則的窗體,如圓形,或者其他形狀的窗體,Region 不直接提供函數,只能通過坐標數組來實現。

如圖所示,圓形是通過一組逆時針連接而成的點構成,各個點可以有園半徑,圓心坐標推算而成。A1 的坐標是 (Xo-r, Yo). An 的 X 坐標是 XAn=Xo-r+n,Y 坐標是 YAn=Yo+R。

圖 3. 園

生成圓形坐標數組的函數代碼如下:

清單 2. 生成圓形坐標數組的函數代碼

int[] circle(int r, int offsetX, int offsetY) {
   int[] ring = new int[8 * r + 4];
   // x^2 + y^2 = r^2
   for (int i = 0; i < 2 * r + 1; i++) {
    int x = i - r;
    int y = (int) Math.sqrt(r * r - x * x);
    ring [2 * i] = offsetX + x;
    ring [2 * i + 1] = offsetY + y;
    ring [8 * r - 2 * i - 2] = offsetX + x;
    ring [8 * r - 2 * i - 1] = offsetY - y;
   }
   return ring;
  }

有了這些定義圖形的函數,我們就可以創建一個 Region 對象用於定義窗體的外形。首先我們定義一個缺省的 Region 對象,然後加入定義好的圖形。這些圖形可以疊加在一起形成多邊形外形;也可以剔除一塊圖形。

下面是示例的完整代碼。首先生成一個 Display 對象,然後用 Display 對象創建一個 Shell。注意需要定義成無裝飾風格的窗口。再後就是創建 Region 對象來定義一個圖形形狀,通過 shell.setRegion 來設置窗口外形,最後是顯示 Shell。Region 在使用完後必須要跟 Display 對象一樣被釋放。

清單 3. 示例的完整代碼

public static void main(String[] args) {
   final Display display = new Display();
   final Shell shell = new Shell(display, SWT.NO_TRIM);
   //
   Region region = new Region();
   region.add(getBackRect1());
   region.add(getBackRect2());
   //
   region.subtract(circle(20, 400, 345));
   //
   shell.setRegion(region);
   Rectangle size = region.getBounds();
   shell.setSize(size.width, size.height);
   //
   shell.open();
   while (!shell.isDisposed()) {
     if (!display.readAndDispatch())
       display.sleep();
     }
   region.dispose();
   display.dispose();
  }

通過運行這段代碼,可以得到自己定義的多邊形窗口界面。

圖 4. 多邊形窗口

Control(控件)多邊形外形只有在最近的 Eclipse 版本支持該功能。下面代碼是通過另外一種非常規的手段來實現多邊形小部件的,通過圖片來顯示小部件邊界。程序首先定義一個 BUTTON 按鈕,然後設置按鈕的圖片,讓按鈕大小約大於圖片。為了只讓圖片顯示,而不顯示按鈕,我們需要創建一個 Region,設置相應的偏移,使 Region 剛好覆蓋要顯示的圖片。

清單 4. 實現多邊形小部件

Button startBt = new Button(shell, SWT.PUSH);
  startBtRegion = new Region();
  startBtRegion.add(circle(35, 40, 40));
  ImageData startData = startBtImage.getImageData();
  startBt.setRegion(startBtRegion);
  startBt.setSize(startData.width+10, startData.height+10);
  startBt.setLocation(575, 297);
  startBt.setImage(startBtImage);
  startBt.setToolTipText("Play media"); 

圖 5. 多邊形 Control

圖 1.5 說明了其中原因,如果要創建一個圓形多邊形按鈕,我們首先要定義個圓形部分的 Region,然後設置偏移 X1,Y1,再後設置 button 的 Region 為我們創建的 Region。這樣 Button 顯示給我們的就是一個圓形的 Button。而且只有該圓形區域,按鈕點擊才有響應。

圖 6. 圓形控件示意圖

值得注意的是,如果不用圖片,圓形的 button 沒法顯示相應的邊界,用戶很難分辨圓形按鈕和父窗口,因而用處不大。如果想創建一個像 Button 的多邊形按鈕,我們需要繼承 Button,重載圖形繪制部分,自己繪制 Region 的邊界以顯示一個真正的多邊形按鈕。

多邊形控件只有在 Eclipse3.4 中才開始支持,有對方面感興趣的朋友可以自己實現多邊形控件,然後定義幾個特例,比如說圓形,環形等。如果運氣好的話,說不定這些實現可能被 Eclipse 采納。

通過圖片來定義窗口界面

在介紹通過圖片來獲得圖形外形前,我們先介紹一下圖像方面的一點基礎知識。

在計算機裡,圖像是通過像素來顯示的 , 像素也叫圖像分辨率。正如 WIKI 裡面定義的,像素是圖像顯示的基本單位,是英文單詞 picture 和 element 的組合而成。一幅圖像中的像素可以在任何尺度上看起來都不像分離的點或者方塊;但是在很多情況下,它們采用點或者方塊顯示。每個像素可有各自的顏色值,可采三原色顯示,因而又分成紅、綠、藍三種子像素(RGB 色域),或者青、品紅、黃和黑(CYMK 色域,印刷行業以及打印機中常見)。照片是一個個采樣點的集合,故而單位面積內的像素越多代表分辨率越高,所顯示的圖像就會接近於真實物體。

如下圖所示,圖像被定義成 N * M 個方格,每個方格表示一個像素。每個像素都有自己的顏色值。藍色圖像就是大量藍色小方格組成的。

圖 7. 像素示意圖

在 SWT 中,圖像模型是 ImageData,它用來保存圖像信息,如圖像高度,寬度以及像素相關信息; 它不像 Image,是一個設備無關類。目前 SWT 支持 JPG, PNG, BMP 等圖片格式。在這裡我們遍歷整個圖片,獲取每個像素值,如果像素值不為 0,表示該像素位於圖片內。這些點組成的圖形,就是圖片的圖像。

清單 5. 代碼

ImageData mask = data.getTransparencyMask();
  Rectangle pixel = new Rectangle(0, 0, 1, 1);
  for (int y = 0; y < mask.height; y++) {
  for (int x = 0; x < mask.width; x++) {
   if (mask.getPixel(x, y) != 0) {
    pixel.x = data.x + x;
    pixel.y = data.y + y;
    region.add(pixel);
   }
  }
  }

SWT 中有很多種方法得到 ImageData 實例。可以通過 ImageData 的構造函數初始化一個 ImageData 實例;也可以通過 ImageLoader 來得到 ImageData 實例;還可以通過已有的 Image 對象來獲得。

通過圖片可以獲得如下窗體外形:

圖 8. 圖像外形多邊形窗口

清單 6. 示例全部代碼

private static Region getBackRegionFromImage(Display display, String image){
   ImageLoader loader = new ImageLoader();
   ImageData[] imageData = loader.load(image);
   Region region = new Region(display);
   ImageData data = imageData[0];
   ImageData mask = data.getTransparencyMask();
   Rectangle pixel = new Rectangle(0, 0, 1, 1);
   for (int y = 0; y < mask.height; y++) {
     for (int x = 0; x < mask.width; x++) {
       if (mask.getPixel(x, y) != 0) {
         pixel.x = data.x + x;
         pixel.y = data.y + y;
         region.add(pixel);
       }
     }
   }
   return region;
  }
  public static void main(String[] args) {
   String backImage="icons/back.gif";
   final Display display = new Display();
   final Shell shell = new Shell(display, SWT.NO_TRIM | SWT.ON_TOP);
   //
   Region region = getBackRegionFromImage(display, backImage);
   //
   shell.setRegion(region);
   Rectangle size = region.getBounds();
   shell.setSize(size.width, size.height);
   //
   ImageLoader loader = new ImageLoader();
   ImageData[] imageData = loader.load(backImage);
   Image image = new Image(null, imageData[0]);
   shell.setBackgroundImage(image);
   //
   shell.open();
   while (!shell.isDisposed()) {
     if (!display.readAndDispatch())
       display.sleep();
   }
   image.dispose();
   region.dispose();
   display.dispose();
  }

美化 RCP 界面

RCP 程序的 GUI 可簡單看成一個窗體,窗體裡有菜單,工具欄,狀態欄,以及由視圖和編輯器組成的 Page。

圖 9. RCP Demo

其實 RCP 遠比看起來要復雜,我們看到的其實是一個工作台 Workbench。Workbench 提供 UI 建造部件,使得 Eclipse 應用程序容易編寫,使用,升級,擴展。Workbench 非常強大,它是一個基於 OSGi 的插件容器,裡面定義了多達 42 個擴展點和大約 350 個 API 類,我們可以基於這些擴展點和提供的 api 創建自己需要的應用程序界面。

Workbench 包含一個或多個 WorkbenchWindows, WorkbenchWindows 是界面元素,裡面包含了 Shell,窗口中包含菜單,工具欄,狀態欄,以及 WorkbenchPage 等信息。WorkbenchPage 其實是一組視圖和編輯器的組合,一個 WorkbenchWindows 可以有多個 WorkbenchPage(但是通常我們只有一個 WorkbenchPage),WorkbenchPage 可以含有多個透視圖。WorkbenchPage,ToolsBar 和 StatusLine 都是 Control,我們可以定義其外形。WorkbenchWindows 還可以添加很多其他的控件,如示例中的按鈕。除去菜單以外,我們可以定義窗體內所有 Control 的外形,包括 WorkbenchWindows。

圖 10. RCP 用戶界面組成

注意我們不能定制 WorkbenchPage 中的視圖和編輯器的外形,但是視圖和編輯器中定義的 Control 可以,如 Button, Label, Link, ProgressBar, Sash, Scale, Scrollable, Slider 等。這些 Control 外形定制的方法跟前面 SWT 中介紹的一樣。

Workbench 和 WorkbenchWindows 都是些內部類,我們想定制多邊形窗體,並不能直接修改他們。不過我們可以繼承 Eclipse 暴露出一些回調函數組成的 Advisor 來重載這些 Eclipse 定義好的回調函數來設置一些相關的信息。當 Workbench 和 WorkbenchWindows 啟動運行是,這些回調函數就會被調用到。Workbench 的 Advisor 是 WorkbenchAdvisor,這是一個應用級別的建議者,Workbench 啟動和關閉是被調用到,用來設置透視圖等信息,跟 UI 沒有太大關系。WorkbenchWindows 的 Advisor 是 WorkbenchWindowsAdvisor,WorkbenchWindowsAdvisor 比 WorkbenchAdvisor 承擔了更多的 UI 方面的角色。我們基本上可以使用該 Advisor 來設置每個工作台窗口的顯示,如窗體風格、標題、工具欄、狀態欄等。我們要實現多邊形的 RCP 窗體,就是繼承該類,重載其中的 postWindowCreate 和 createWindowContents 來實現的。

Eclipse 中的 OSGi 機制是通過 Equinox 來實現的。插件 org.eclipse.equinox.app 中定義了一個普通應用程序的啟動的擴展點,Application 是它的一個擴展。下面是 RCP 啟動的一個時序圖:

圖 11. RCP 啟動時序圖

Application 調用 PlatformUI.createAndRunWorkbench 來創建一個工作台 . 工作台 Workbench 調用 WorkbenchAdvisor 來設置相關信息,創建 WorkbenchWindow。 WorkbenchWindow 又通過 WorkbenchWindowAdvisor 來設置窗口的一些信息。重載 preWindowOpen 來設置窗體風格;重載 createWindowContents 來創建窗體內的部件,如菜單、工具欄、狀態欄、WorkbenchPage 等;重載 postWindowCreate 來設置窗體大小、外形。

定制 RCP 窗體外形前,Shell 對象已經初始化,所以我們只能是在窗體創建後。設置 RCP 窗體外形跟前面的 SWT 程序一樣簡單,首先通過通過窗體配置參數獲得當前窗體 Shell 的引用,然後定義一個外形 Region,使用這個外形設置窗體就成。

窗體中的部件的外形定制跟其他的 Control 一樣,先創建圖形,然後使用圖形設置 Control 外形。需要注意的是我們需要定義好 Control 的位置,因為父子部件是一層層疊加的,最頂層的部件可能會覆蓋其父部件的外形。

通過定制多邊形外形,我們可以制作一個界面美觀的媒體播放器 :

圖 12. 媒體播放器

實例的部分代碼如下:

public void preWindowOpen() {
   getWindowConfigurer().setShellStyle(
     SWT.NO_TRIM | SWT.CENTER | SWT.NO_BACKGROUND);
  }

首先是在窗體打開前設置窗體的風格,沒有邊界,沒有背景。

清單 7. 設置窗體風格

public void postWindowCreate() {
   IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
   //
   final Shell shell = configurer.getWindow().getShell();
   backRegion = getBackRegionFromImage(shell.getDisplay(), backImage, 0, 0);
   ImageData data = backImage.getImageData();
   shell.setRegion(backRegion);
   shell.setSize(data.x+data.width, data.y+data.height);
   //
   Listener listener = new Listener() {
     int startX, startY;
     public void handleEvent(Event e) {
       if (e.type == SWT.MouseDown && e.button == 1) {
         startX = e.x;
         startY = e.y;
       }
       if (e.type == SWT.MouseMove && (e.stateMask & SWT.BUTTON1) != 0) {
         Point p = shell.toDisplay(e.x, e.y);
         p.x -= startX;
         p.y -= startY;
         shell.setLocation(p);
       }
        if (e.type == SWT.Paint) {
         ImageData data = backImage.getImageData();
         e.gc.drawImage(backImage, data.x, data.y);
       }
     }
   };
   shell.addListener(SWT.MouseDown, listener);
   shell.addListener(SWT.MouseMove, listener);
   shell.addListener(SWT.Paint, listener);
   //
  }

窗體創建後,程序通過讀取圖片信息獲得圖片的大小,多邊形外形等信息,然後設置窗體的大小外形。 同時添加窗體變化的 Listener,通過繪制圖片來設置窗體的背景。

清單 8. 設置窗體的背景

public void createWindowContents(final Shell shell) {
   IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
   //
   page = configurer.createPageComposite(shell);
   pageRegion = getBackRegionFromImage(shell.getDisplay(), pageImage, 0, 0);
   ImageData pageData = pageImage.getImageData();
   page.setRegion(pageRegion);
   page.setBounds(45, 21, pageData.width, pageData.height);
   //
   Button startBt = new Button(shell, SWT.PUSH);
   //startBtRegion = getBackRegionFromImage(shell.getDisplay(), startBtImage);
   startBtRegion = new Region();
   startBtRegion.add(circle(35, 40, 40));
   ImageData startData = startBtImage.getImageData();
   startBt.setRegion(startBtRegion);
   startBt.setSize(startData.width+10, startData.height+10);
   startBt.setLocation(575, 297);
   startBt.setImage(startBtImage);
   startBt.setToolTipText("Play media");
   startBt.addSelectionListener(new SelectionAdapter(){
     public void widgetSelected(SelectionEvent e) {
       // TODO Auto-generated method stub 
       String [] ext = {"*.AVI", "*.mp3", "*.*"};
       FileDialog dlg = new FileDialog(shell, SWT.OPEN);
       dlg.setText("Select Media...");
       dlg.setFilterExtensions(ext);
       String file = "file://" + dlg.open();
       //
       String editorID = "RCPDemo.mediaplay";
       try {
         PlatformUI.getWorkbench().getActiveWorkbenchWindow()
         .getActivePage().openEditor(new MediaPlayerInput(file), editorID);
       } catch (PartInitException e1) {
         // TODO Auto-generated catch block 
         e1.printStackTrace();
       }
   });
   //
   Button exitBt = new Button(shell, SWT.PUSH);
   exitRegion = getBackRegionFromImage(shell.getDisplay(), exitImage, 5, 5);
   ImageData exitData = exitImage.getImageData();
   exitBt.setRegion(exitRegion);
   exitBt.setSize(exitData.width+10, exitData.height+10);
   exitBt.setLocation(615, 350);
   exitBt.setImage(exitImage);
   exitBt.setToolTipText("Exit");
   exitBt.addSelectionListener(new SelectionAdapter(){
     public void widgetSelected(SelectionEvent e) {
       // TODO Auto-generated method stub 
       PlatformUI.getWorkbench().close();
     }
   });
  }

這段代碼是創建窗體上的控件,一個 video 播放 editor,一個播放按鈕和一個退出按鈕。video 播放 editor 是 Page 的一部分,而 Page 的外形通過圖片來定義;一個播放按鈕是通過一個文件對話框讓用戶來選擇需要播放的 video 文件。

通過上面的介紹,我們可以看到,SWT/RCP 是一個非常優秀的應用界面 SDK,不光可以編寫經典的窗體,還可以編寫漂亮的多邊形窗體及控件。希望讀者通過本文的介紹,多使用 SWT/RCP 相關技術,開發出具有自己風格的應用程序。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved