在 Eclipse Workbench 之外使用 Eclipse GUI,第 2 部分: 使用 JFace 圖像註冊表

 
在本文中,A. O. Van Emmenis 繼續研究在第 1 部分中着手構建的示例。他將完善內容提供程序和標籤提供程序,並演示如何在 JFace 查看器中使用排序和過濾。他將演示如何給窗口添加狀態行,給兩個查看器添加圖標,並討論如何通過使用 JFace 圖像註冊表來節省系統資源。

安裝說明

在本系列的 第 1 部分中,我着手構建了一個示例,該示例將 JFace 應用程序窗口子類化並且使用樹查看器和表查看器來顯示文件夾和文件。這次,我們將完成上個示例的收尾工作,然後給窗口添加一條狀態行。我們要做的較大更改是給兩個查看器添加圖標,並瞭解 JFace 圖像註冊表。最後,我們將對查看器進行排序和過濾,以獲得一個更加真實的“文件資源管理器”樣式。

安裝說明

如果要下載本文中 示例的源代碼,請注意我的系統設置:

  • Windows 2000
  • Eclipse,穩定構建版 M3(2002 年 11 月 15 日)
  • Eclipse 安裝在 C:/eclipse-2.1.0 中

請您自行完成隨後所有調整名稱和文件分隔符的工作,以便程序能在您的系統上正確地運行。

構建/運行指示信息

請確保以下 jar 文件位於類路徑上:

C:/eclipse-2.1.0/plugins/org.eclipse.jface_2.1.0/jface.jar
C:/eclipse-2.1.0/plugins/org.eclipse.runtime_2.1.0/runtime.jar
C:/eclipse-2.1.0/plugins/org.eclipse.swt.win32_2.1.0/ws/win32/swt.jar
C:/eclipse-2.1.0/plugins/org.eclipse.ui.workbench_2.1.0/workbench.jar
C:/eclipse-2.1.0/plugins/org.eclipse.core.runtime_2.1.0/runtime.jar

爲確保 Java VM 能找到您在運行時所用 GUI 的正確共享庫,請使用以下參數運行 Java VM:

-Djava.library.path=C:/eclipse-2.1.0/plugins/org.eclipse.swt.win32_2.1.0/os/win32/x86/

最後,請從包含 icons文件夾的文件夾中運行這些程序,以便示例能找到包含圖標的 gif 文件。





回頁首


繼續使用第 1 部分的示例

上一篇文章結束時,我們的資源管理器應用程序如圖 1 所示:


圖 1. 資源管理器(V4)
圖 1. 資源管理器(V4)

我們在最左邊窗格中使用樹查看器顯示文件夾和文件。當在左邊窗格中選中某個文件夾時,它所包含的文件就顯示在最右邊窗格的表查看器中。讓我們通過設置窗口標題來着手完善這個示例。





回頁首


設置窗口標題

這是 JFace 不試圖向您隱藏 SWT 的又一個例子。必須獲取底層 SWT Shell 窗口小部件並設置其標題。

我們使用 getShell() 向 JFace 窗口求取其 SWT shell,然後使用 setText() 設置 shell 的標題,於是,在資源管理器 createContents() 方法中,我們將使用如下語句:

getShell().setText("JFace File Explorer");

我們在表視圖中有了一列。讓我們添加另一個以字節爲單位顯示文件大小的列。我們將使該列中的文本向右對齊。它的代碼與第一列類似,所以我不在這裏顯示。

既然有了兩列,那就把表查看器的選擇樣式設置爲 FULL ,表示選中某行後,整行都將被突出顯示。

new TableViewer(sash_form, SWT.BORDER | SWT.FULL_SELECTION);

狀態行

狀態行實際上是由類 StatusLineManager 照管的 SWT 組合控件,該類可以包含其它控件。

通常這些控件都是顯示狀態信息的只讀文本控件,但也可請求在那裏顯示臨時進展監視器。

狀態行知道要顯示標準消息,並且有時要顯示錯誤消息。 ApplicationWindow 方法 setMessage(String) 實際上只是 getStatusLineManager().setMessage(String) 的快捷方式。





回頁首


給應用程序窗口添加狀態行

狀態行是應用程序窗口的另一個可選組件。必須通過使用 addStatusLine() ,在窗口創建 SWT 窗口小部件之前要求窗口創建狀態行。

讓我們用狀態行顯示在表視圖中選中了多少項。每當表視圖中的選擇發生改變時,我們就更新狀態行並顯示有多少項被選中,如清單 1 所示:

清單 1. 資源管理器 — 設置狀態行中的文本
tbv.addSelectionChangedListener(new ISelectionChangedListener()
{
      public void selectionChanged(SelectionChangedEvent event)
  {
        IStructuredSelection selection =
          (IStructuredSelection) event.getSelection();
        setStatus("Number of items selected is " + selection.size());
  }
});

缺省情況下,表查看器被設置爲單選方式。可以通過在創建表查看器時將多選方式作爲樣式參數的另一位(以文字表示)添加,將表查看器更改爲多選方式。

new TableViewer(sash_form, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);

綜合以上代碼就得到清單 2:

清單 2. 資源管理器(V5)
import java.io.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.widgets.*;
public class Explorer extends ApplicationWindow
{
  public Explorer()
  {
    super(null);
    addStatusLine();
  }
  protected Control createContents(Composite parent)
  {
    getShell().setText("JFace File Explorer");
    SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL);
    TreeViewer tv = new TreeViewer(sash_form);
    tv.setContentProvider(new FileTreeContentProvider());
    tv.setLabelProvider(new FileTreeLabelProvider());
    tv.setInput(new File("C://"));
    final TableViewer tbv =
      new TableViewer(sash_form, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
    tbv.setContentProvider(new FileTableContentProvider());
    tbv.setLabelProvider(new FileTableLabelProvider());
    TableColumn column = new TableColumn(tbv.getTable(), SWT.LEFT);
    column.setText("Name");
    column.setWidth(200);
    column = new TableColumn(tbv.getTable(), SWT.RIGHT);
    column.setText("Size");
    column.setWidth(100);
    tbv.getTable().setHeaderVisible(true);
    tv.addSelectionChangedListener(new ISelectionChangedListener()
    {
      public void selectionChanged(SelectionChangedEvent event)
      {
        IStructuredSelection selection =
          (IStructuredSelection) event.getSelection();
        Object selected_file = selection.getFirstElement();
        tbv.setInput(selected_file);
      }
    });
    tbv.addSelectionChangedListener(new ISelectionChangedListener()
    {
      public void selectionChanged(SelectionChangedEvent event)
      {
        IStructuredSelection selection =
          (IStructuredSelection) event.getSelection();
        setStatus("Number of items selected is " + selection.size());
      }
    });
    return sash_form;
  }
  public static void main(String[] args)
  {
    Explorer w = new Explorer();
    w.setBlockOnOpen(true);
    w.open();
    Display.getCurrent().dispose();
  }
}

運行該程序的結果如圖 2 所示:


圖 2. 資源管理器(V5)
圖 2. 資源管理器(V5)

如您所見,size 列目前不正確 — 我們馬上就修正它。





回頁首


圖標和圖像

在 SWT 中,用來表示圖標的對象是 Image (有關類 Image 的詳細信息,請參閱本文後面的 參考資料)。當直接創建可顯示圖像的 SWT 窗口小部件時,可使用 setImage(Image) 設置它。

關於圖像有一個問題:它們是受限資源。圖像是比較重量級的對象,具有對外部 OS 資源的引用(圖 3)。在某些操作系統上,對任一時間內可擁有圖像的數量有嚴格限制,因此使用它們時必須注意這一限制。


圖 3. 操作系統中的圖像描述符、圖像和資源
圖 3. 操作系統中的圖像描述符、圖像和資源

最終,當應用程序退出時,會釋放 Image 所用的資源 — 但即使那樣,在某些操作系統上仍可能有問題:共享庫造成資源被繼續佔用。出於各種原因,不能指望 Java 垃圾收集器會替您清理它們。有關標準窗口小部件工具箱(Standard Widget Toolkit)的完整說明,請參閱 參考資料

有個好消息:圖像 可以在窗口小部件之間共享,而且 SWT 爲 Image 提供了 dispose() 方法,它可以釋放圖像所用的資源。

因爲窗口小部件可以共享圖像,所以 SWT 做出了這樣的設計決定:當窗口小部件不再存在時(也就是說,在關閉窗口時),SWT 將不會自動清除這些圖像。然而,JFace 會給予您一些幫助,它告訴您大型 UI 對象(如窗口和查看器)何時可以清除它們的圖像,然後由您負責清除已創建的任何圖像。

實際上,絕大多數應用程序都從文件或數據庫獲取其圖標,這些文件或數據庫包含一些標準格式(如 gif 或 jpeg)的數據。爲此,JFace 提供了 ImageDescriptor (請參閱 參考資料),這是一個輕量級對象,它不存儲圖像本身,而是可以按需要創建特定的圖像。這個類有許多可以從不同來源構造圖像的子類。

在本例中,我們將圖標存儲在位於 icons 文件夾的 .gif 文件中,因此我們將在 ImageDescriptor 中使用這一工廠(factory)方法:
public static ImageDescriptor createFromURL(URL url)

TableViewer 獲取元素圖標的方式和它獲取元素文本的方式大體相同。它請求標籤提供程序。我們必須做的就是從 ITableLabelProvider 實現該方法:

public Image getColumnImage(Object element, int columnIndex)

爲了便於我們管理圖像的共享和清除,我們將使用 ImageRegistry (請參閱 參考資料)。





回頁首


JFace 圖像註冊表

管理一組共享且昂貴的資源是經典的軟件工程問題。JFace 提供經典的解決方案:一個能高速緩存圖像和圖像描述符的中央共享註冊表。

其思想是:您的代碼獲取它需要的圖像描述符,然後將其添加到圖像註冊表,並使用鍵爲每個圖像描述符建立索引。當希望獲得圖像時,就用它的鍵從註冊表中訪存。

當清除頂級顯示(Display)時,圖像註冊表將負責清除它的圖像。如果需要更頻繁地清除圖像,則可能要創建數個圖像註冊表,並按需要直接清除圖像。要獲得更多的詳細信息,請參閱 Eclipse 網站上關於使用圖像的文章(請參閱 參考資料)以獲得鏈接。

我們的小示例將只使用少數幾個圖標,因此我們將使用單個圖像註冊表。讓我們先看看錶查看器。

給文件表查看器添加圖標

要從名爲 icons/file.gif 的文件創建圖像描述符,可使用以下語句:

image_descriptor = ImageDescriptor.createFromURL(new URL("icons/file.gif"));

獲得圖像描述符之後,可將它存儲在圖像註冊表中。在本例中,我們使用字符串“file”作爲鍵:

image_registry.put("file", image_descriptor);

然後,要再次獲取該圖像,可使用以下語句:

image = image_registry.get("file");

URL 的構造函數拋出一個已檢查異常(checked exception)。我們要使用的所有 URL 都將是硬編碼的,因此我們將把 URL 的創建包裝在某個工具代碼中,以將已檢查異常轉換爲運行時異常。

另外,我們希望在代碼中共享這些圖像,因此需要集中創建圖像註冊表,並使它可被全局訪問。

現在該創建一個實用程序類了,如清單 3 所示:

清單 3. Util(V1)
import java.net.*;
import org.eclipse.jface.resource.*;
public class Util
{
  private static ImageRegistry image_registry;
  public static URL newURL(String url_name)
  {
    try
    {
      return new URL(url_name);
    }
    catch (MalformedURLException e)
    {
      throw new RuntimeException("Malformed URL " + url_name, e);
    }
  }
  public static ImageRegistry getImageRegistry()
  {
    if (image_registry == null)
    {
      image_registry = new ImageRegistry();
      image_registry.put(
        "folder",
        ImageDescriptor.createFromURL(newURL("file:icons/folder.gif")));
      image_registry.put(
        "file",
        ImageDescriptor.createFromURL(newURL("file:icons/file.gif")));
    }
    return image_registry;
  }
}

getImageRegistry() 方法簡單地創建了一個圖像註冊表並添加了兩個圖像描述符。我們現在需要更改 FileTableLabelProvider 以根據元素是文件還是文件夾來返回正確的圖像,如清單 4 所示:

清單 4. FileTableLabelProvider(V2)
import java.io.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.graphics.*;
public class FileTableLabelProvider implements ITableLabelProvider
{
  public String getColumnText(Object element, int column_index)
  {
    if (column_index == 0)
    {
    return ((File) element).getName();
    }
    if (column_index == 1)
    {
      return "" + ((File) element).length();
    }
    return "";
  }
  public void addListener(ILabelProviderListener ilabelproviderlistener)
  {
  }
  public void dispose()
  {
  }
  public boolean isLabelProperty(Object obj, String s)
  {
    return false;
  }
  public void removeListener(ILabelProviderListener ilabelproviderlistener)
  {
  }
  public Image getColumnImage(Object element, int column_index)
  {
    if (column_index != 0)
    {
      return null;
    }
    if (((File) element).isDirectory())
    {
      return Util.getImageRegistry().get("folder");
    }
    else
    {
      return Util.getImageRegistry().get("file");
    }
  }
}

由於現在表有兩列(我們在前面添加了大小(size)列),所以還必須調整 getColumnText(Object,int) 以使程序正確工作。

運行該程序的結果如圖 4 所示:


圖 4. 資源管理器(V6)
圖 4. 資源管理器(V6)

程序開始變得十分漂亮了 — 至少在表查看器中是這樣。讓我們升級 FileTreeLabelProvider 以使用圖像,如清單 5 所示:

清單 5. FileTreeLabelProvider(V2)
import java.io.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.graphics.*;
public class FileTreeLabelProvider extends LabelProvider
{
  public String getText(Object element)
  {
    return ((File) element).getName();
  }
  public Image getImage(Object element)
  {
    if (((File) element).isDirectory())
    {
      return Util.getImageRegistry().get("folder");
    }
    else
    {
      return Util.getImageRegistry().get("file");
    }
  }
}

運行該程序的結果如圖 5 所示:


圖 5. 資源管理器(V7)
圖 5. 資源管理器(V7)

現在,可以很容易地看出文件和文件夾之間的區別,我們還可以看到:在表視圖中,缺省排序算法按字母順序給各項排序 — 但將文件夾和文件混在一起。您可能還注意到:在樹視圖中,我們同時看到了文件夾 文件。我們現在來修正這個問題。





回頁首


排序和過濾

要在查看器中對項進行排序,我們使用 ViewerSorter

ViewerSorter (請參閱 參考資料)是旨在被子類化的類。查看器使用查看器排序程序以便採取兩個步驟對它的元素排序。首先,它詢問元素的 類別(category)。這返回一個整數,然後它把元素按其 類別號的升序歸到各類別組:

public int category(Object element)

接着,在每個類別中,它使用 ViewerSortercompare() 方法進行排序。該方法類似於標準 Java 類 Comparator 中的 compare() 方法:

public int compare(Viewer viewer, Object element1, Object element2)

缺省情況下,使用標籤提供程序返回的字符串並忽略大小寫進行排序。

我們在表查看器中只實現 category 方法,如清單 6 所示:

清單 6. FileSorter(V1)
import java.io.*;
import org.eclipse.jface.viewers.*;
public class FileSorter extends ViewerSorter
{
  public int category(Object element)
  {
    return ((File) element).isDirectory() ? 0 : 1;
  }
}

這將先對文件夾排序,再對文件排序。

在樹查看器中,我們希望只顯示文件夾。我們通過使用 ViewerFilter (請參閱 參考資料)做到這一點。和排序程序一樣,過濾器也旨在被子類化。我們需要實現 select() 方法,它檢查元素,如果將要顯示該元素,則返回 true。在本例中,我們希望只允許文件夾通過過濾器,所以有清單 7 中的 AllowOnlyFoldersFilter() 方法:

清單 7. AllowOnlyFoldersFilter(V1)
import java.io.*;
import org.eclipse.jface.viewers.*;
public class AllowOnlyFoldersFilter extends ViewerFilter
{
  public boolean select(Viewer viewer, Object parent, Object element)
  {
    return ((File) element).isDirectory();
  }
}

注:我們過濾的元素是第 3 個參數。現在給出了查看器和父元素 — 以便在需要時訪問它們。

現在,我們只需將這些類的實例附加到查看器,如清單 8 所示:

清單 8. 資源管理器(V8)
  import java.io.*;
import org.eclipse.jface.action.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.widgets.*;
public class Explorer extends ApplicationWindow
{
  ...
  protected Control createContents(Composite parent)
  {
    getShell().setText("JFace File Explorer");
    SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL);
    TreeViewer tv = new TreeViewer(sash_form);
    tv.setContentProvider(new FileTreeContentProvider());
    tv.setLabelProvider(new FileTreeLabelProvider());
    tv.setInput(new File("C://"));
    tv.addFilter(new AllowOnlyFoldersFilter());
    final TableViewer tbv =
      new TableViewer(sash_form, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
    tbv.setContentProvider(new FileTableContentProvider());
    tbv.setLabelProvider(new FileTableLabelProvider());
    tbv.setSorter(new FileSorter());
     ...
  }
  

注:正如這些方法的名稱所表明的那樣,一個查看器可以同時有多個過濾器,但只能有一個排序程序。

現在運行資源管理器所得的結果如圖 6 所示:


圖 6. 資源管理器(V8)
圖 6. 資源管理器(V8)




回頁首


結束語

好了,這些圖標無疑使程序更漂亮了一些。我們有了窗口標題,它告訴我們看到的是什麼,還有一個新的漂亮的狀態行,它告訴我們選中了多少項,再通過過濾和排序將文件和文件夾整齊地分隔開,這個程序開始象一個真正的文件資源管理器了。

它可能看起來漂亮了一點,但現在還做不了什麼。在本系列(由 3 部分組成)的最後一篇文章中,我們將通過添加菜單和操作來彌補這一缺陷。我們將瞭解如何創建菜單欄、工具欄和彈出菜單。我們還將開發一些示例,這些示例中的菜單會使用一些巧妙的 JFace 實用程序來啓動程序並訪問系統剪貼板,而且我們還將演示如何使用偵聽器使菜單項對上下文敏感。



參考資料



關於作者

作者照片

A. O. Van Emmenis 是一位獨立顧問,專門從事 Java/J2EE 培訓和諮詢工作,他在英國的劍橋工作。Van 已經在軟件行業工作了約 20 年左右。他最初與對象打交道是在 CAD 行業使用 Smalltalk 的時候,他現在在工作中主要使用 Java。他對敏捷(Agile)方法和 GUI 設計特別感興趣。您可以通過 [email protected]與 Van 聯繫。

發佈了45 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章