現實生活中的Swing和NetBeans平臺開發

開發豐富的桌面 Java 應用程序歷來是非常辛苦的過程;但是現在 NetBeans 平臺中有了新的高級 Swing 組件和完整的應用程序框架。瞭解這個平臺如何讓開發複雜的桌面應用程序變得輕鬆,以及在構建這個平臺方面的一些教訓。

 

  和很早就開始使用 Java 的大多數人一樣,我最初接觸這項技術是使用(小型)桌面應用程序的時候:它們是我讀博士期間的一些研究素材和一個衛生保健呼叫中心的簡單控制面板。那時是 AWT 時代,能做的事有限。因此我很快就轉到了服務器這邊,這邊的系統似乎更強健、更有發展。事實確實如此。我在這次領域工作了很長時間,並且成爲一名 J2EE 架構師。

  幾年之後,由於對數字攝影熱情的不斷高漲,我又重新被吸引到桌面。我仍然遇到了許多問題,但是就在我即將放棄的時候,Sun 和開發人員社區推出的 SwingLabsjava.net 和新版本的 NetBeans 把我從困難中解救出來。現在,我正在熱衷於使用一個(可能是)富有前途的開放源碼應用程序 blueMarine,這個程序基於 NetBeans 平臺。

  在本文中,我將告訴您有關 blueMarine 的更多故事,並且回顧一些主要的 NetBeans 擴展 API。我將介紹如何使用和自定義這些 API,同時指出我曾經面臨的問題以及解決這些問題的方法。如果您對 NetBeans 知之甚少,而您又需要接觸各種各樣的客戶端應用程序,我認爲您應該好好閱讀這篇文章。

開始

  2001 年前後,由於厭倦了使用 OpenOffice 電子表格,我第一次編寫了一些 Java 代碼,用於管理我的照片。我將所有內容導出到 XML 並且利用 XSLT 轉換,確定了我自己的數據庫格式,通過基於 Swing 的一個非常簡單的圖形用戶界面進行管理。

  2003 年夏天,我在數碼相機世界有了較大的飛躍,購買了 Nikon D100(專業的 SLR)。那個夏天是意大利本世紀最炎熱的夏天,因此我不得不最大程度地減少拍照旅行的數量:外出散步也是受罪。我不得不待在家裏,儘管托斯卡納鄉村的環境能令人放鬆,因此,大部分假期我都在學習 NEF 格式。

  當時,NEF 是一種“原始數據文件格式”,並且幾乎沒有公開。原始數據文件格式包含的數據直接來自於相機 CCD 傳感器,未經過處理。如果要將它轉換爲質量比較好的圖片,還需要對數據進行處理。這通常認爲這一過程是舊式潮溼暗室相片成像的數字過程。我從來沒有擁有過自己的潮溼暗室,我爲“數字沖洗”照片而入迷,開始爲此編寫了一些 Java 代碼。

  夏天快結束的時候,我創建了一個簡單的能夠顯示照片的微型導航程序—— blueMarine 就此誕生了。一年之後,這個項目便能夠用編錄設備標記照片,並且能夠在網上發佈圖片庫。

  但是,讓我非常苦惱的是,我需要多個軟件來執行編輯、打印、編錄、歸檔以及 Web 發佈任務。因此我着手開始研究在一個應用程序中實現所有這些工作流程。同時,我認爲是時候公開發布 blueMarine 了,因此第一個 alpha 版本發佈到了 GPL License(後來更改爲 Apache 2.0)支持下的 SourceForge

  另一個推動力是在臺式計算機上挑戰使用 Java 對數字圖像進行處理。對於我來說,Java 在科學圖像處理方面的優勢已經非常明顯;例如, NASA 的工程師們已經成功使用了 JAI,它是一種高級成像的 API。但是,對於普通攝影師來說,桌面處理包含哪些內容呢?自從成爲 Java 顧問 10 多年來,證明 Java 在各種各樣的應用程序方面的優勢一直是我追求的目標。

挫折

  雖然開始時熱情高漲,但是在 2005 年底,我在這個項目上受到了很大的打擊。性能不是大問題,但是,在使用 plain Swing 開發豐富的 GUI 應用程序時,我遇到了困難Swing 是一個非常出色的 API,但是當您使用它構建複雜的應用程序時,您會發現仍然需要增加很多功能。

  實現這些缺少的部分並不是前言科學,但是這項工作浪費了很多寶貴的時間。再次舉例來說明這個問題,例如構建菜單、以上下文相關的方式啓用操作、爲內部窗口定義靈活的、可在工作時停靠的機制等等,並且您將發現自己的大部分時間都花費在編寫通用的 GUI 組件上,而沒有花費在應用程序的核心內容上。

  直到最近,纔有了幾個處理此類問題的開放源碼庫,但是大部分庫都不太令人滿意,而且也很難集成。還有幾個較早版本的 NetBeans,但是我不滿意它們的性能。也可以選擇 Eclipse SWT,但是我覺得我並沒有真正地仔細研究完全的替代方法和非標準的 API,我採用的學習投資回報低,繼承 Swing的方法比較麻煩。

  總的來講,我認真地考慮過放棄 blueMarine – 或許 Java 尚未準備用於桌面開發。

新生

  但是,同時出現了兩個事件使這個項目得以保留下來,這兩個事件是,我在 2005 年底參與了 JavaPolis 以及 2006 年初發布了 NetBeans 5.0

  JavaPolis,我呼吸到了我幾乎都快忘記的社區空氣(自從我上次參加 JavaOne 已經過去了三年)。這重新點燃了我的熱情,Romain Guy 的演示說明了使用 Swing 構建 GUI 的效果如何,,這使我興趣倍增。我開始查看 Romain 的博客,並按照下面的鏈接訪問了其他人的博客,如 Joshua Marinacci 的博客,然後再從那裏訪問所有 java.net JavaDesktop 的站點。我發現 Swing 裏面有很多新鮮有趣的事;像 SwingLabs 裏質量優良的 Swing組件和出衆的演示程序很多材料我都能夠使用。但是,我仍然需要一個平臺。

  幾周之後,推出了NetBeans 5.0 。這個新的版本看起來似乎最終解決了傳統的平臺問題,因此我決定試一試。我開始分解 blueMarine,只提取成像代碼並對該代碼重新設計以便使用 NetBeans 平臺。幾個月之後,便可以發佈了第一個 Early Access 版本,我開始使用這個工具管理我自己的照片。同時,從前一個 PPC Apple iBook 切換到新的 Intel MacBook Pro 沒有出現任何問題則強有力地表明瞭我的選擇是正確的。

  目前,我正致力於使新的 blueMarine 更穩定、更便於使用。獲得了新的 early access 版本,並且我正在進行所需的質量測試(整個新設計明顯破壞了以前版本的一些穩定性;這就是付出的代價)。

NetBeans 平臺的功能

由於您已經瞭解了 blueMarine 的由來,因此我將概述一些 NetBeans Swing 帶來的開發優勢、我曾經面臨的問題以及解決這些問題的方法。

第一點:是 Swing

  對於我來說,與很多競爭對手(如 Eclipse RCP)相比,NetBeans 平臺基於常規的Swing 是一個巨大的優勢。搜索一下,您會發現 Swing 組件(包括實現動畫和效果比較酷的組件)有更廣闊的選擇餘地。

  去年 6 月份我非常具體地認識到了這個優勢,當時 Joshua Marinacci 發佈了能夠顯示地圖的 Aerith Swing 組件的源代碼,命名爲 JXMapViewerAerith 2006 JavaOne 上最熱門的演示程序)。幾周以來,我一直都在等待這個時刻,blueMarine 的其中一個功能是地理標記(將地理位置與每個照片相關聯,以便這些照片可以顯示在地圖上)。將 JXMapViewer 集成到 blueMarine 中只需要幾個小時;

模塊系統

  當然,NetBeans 平臺應用程序自然分爲幾個模塊,實際上,它是一組綁定在一起的模塊。每個模塊都有一個名稱、一組版本標記及其自己的類路徑以及一個聲明的依賴關係的列表。開發人員可以控制哪些公共類的子集向其他模塊公開,其他模塊可以看到哪些公共類的子集,平臺可以在模塊之間施加依賴關係(例如,如果所需的模塊都不存在或者太舊,則阻止模塊安裝)。

  此外,通過發佈新的模塊(放在 nbm 文件中)可以在以後擴展應用程序,用戶可以建立他們自己的“更新中心”,以便從 Internet 下載更新。可以對各個模塊進行數字簽名,系統會自動彈出他們的許可證以便進行批准(如果需要的話)。

  BueMarine 項目充分利用這個組織。該應用程序的核心 API 由實現工作空間管理器、照片、縮略圖管理、簡單縮略圖和照片查看器的相對比較小的模塊集進行定義。更多的高級功能(如編錄、圖庫管理器和地理標記功能,包括地圖查看器)可以在單獨並且幾乎不相關的模塊中實現,這些模塊就作爲核心 API 的“客戶端”。

DataObjectNode ExplorerManager

  ExplorerManagerNode DataObject 可能是 NetBeans 中最有用的 API。使用 DataObject,您可以實現特定於應用程序的實體,這些實體是映射到磁盤上的文件中。例如,blueMarine 的基本實體爲 PhotoDataObject,它代表數據庫中的照片。

  DataObject 包含實體的所有狀態和行爲,爲了進行可視化可以將 Node DataObject綁定。還可以採用很多種不同的方式(如集合或圖形)將實體聚合在一起。NetBeans 平臺提供 GUI 組件,如表和列表,這些組件可以將一組 Node 對象用作其模型;最常見的組件是 BeanTreeViewContextTreeView ListView最後,ExplorerManager 控制選擇和樹導航。

  是的,這只不過是一個複雜的 MVC 實現,而且這個實現中已經爲您編寫了很多樣板文件代碼。例如,平臺 API 考慮類似於拖放支持(以及拖放操作過程中類似於可視提示等詳細的細節信息)、剪切粘貼操作和上下文菜單的事情。

Lookup API

  NetBeans 平臺組件的生命週期(更像是容器中的 EJB)由平臺控制,因此未直接實例化這些組件。若要檢索現有模塊的引用,可以使用 Lookup API。這個 API 與其他查詢機制非常類似。獲得對象的引用要以對象“名稱”開頭,這並不是一個字符串,而是相應的 Class 對象。

  例如,假設我們有一個名爲 it.tidalwave.catalog.CatalogImpl(實現一個接口 it.tidalwave.catalog.Catalog的模塊。首先,通過將特殊文件放置在 META-INF/services 目錄下的類路徑中“註冊”該模塊。該文件必須按實現的接口命名,並且包含該實現類的名稱。只要加載模塊,NetBeans 都會掃描這些特殊文件、實例化對象並將它們放置到“默認的”Lookup 對象中,以後其他任何代碼片段都可以從這裏檢索到該對象。

  我通常使用定位器模式將查詢代碼包起來,如清單 1 中所示,然後執行類似於下面內容的查詢:

Catalog catalog = CatalogLocator.findCatalog();

Listing 1. A Locator that uses the Lookup class.


public class CatalogLocator {
public static final synchronized Catalog findCatalog() {
final Lookup lookup = Lookup.getDefault();
final Catalog catalog = (Catalog)lookup.lookup(Catalog.class);

if (catalog == null) {
throw new RuntimeException("Cannot lookup Catalog");
}
return catalog;
}
}

  該機制不僅僅支持解耦,而且還創建可插拔的行爲。例如,讓我們看一看 blueMarine 的地圖顯示功能。正如您所知,有很多地圖提供程序,如 Google MapsMicrosoft Visual EarthNASA 以及其他提供程序。我想讓人們能夠通過向其中插入用於處理其他地圖提供程序的新代碼來擴展 blueMarine。這個解決方案非常簡單:首先定義一個接口 MapProvider該接口聲明所有所需的功能,然後編寫備用實現,每個實現都位於其自己的模塊中,例如 GoogleMapProviderMicrosoftVisualEarthMapProvider

  使用相同的“名稱”MapProvider(允許註冊多個具有此相同名稱的對象)在默認的 Lookup 實例中註冊每個實現。現在,檢索對象變成一個非常簡單的任務。如清單 2 中的示例所示。您可以添加具有新地圖提供程序的模塊,檢索代碼會在運行時查找這些模塊

Listing 2. Retrieving registered objects.


private DefaultComboBoxModel mapProvidersModel =
    new DefaultComboBoxModel();

private void searchMapProviders() {   
  Result result = Lookup.getDefault().lookup(
      new Template(MapProvider.class));

  for (Object provider : result.allInstances()) {
    mapProvidersModel.addElement(provider); 
  }
}

Lookup API 還促進解耦

  默認的 Lookup 實例還包含當前選擇的 Node 對象組。這樣便有可能設計有效且耦合鬆散的機制用於內部模塊通信。它基於“觀察者”模式:某些模塊將它們的節點選擇發佈到默認的 Lookup,而其他模塊偵聽更改。通過對與更改節點關聯的信息種類進行某些相關的篩選,我們便可以達到“發佈/訂閱”設計模式。

  blueMarine 中,有很多方法用於導航照片數據庫和顯示縮略圖組,例如,瀏覽文件夾、日曆、共享標記的照片、相同圖片庫中的照片等等。“瀏覽器”模塊只是將綁定到 PhotoDataObject Node 選擇發佈到默認的 Lookup 中;縮略圖查看器接收通知並相應地更新自身

  瀏覽器組件並不依賴於縮略圖查看器。實際上,瀏覽器組件與縮略圖查看器已完全解耦(此處我們正在應用控制反轉)。通過這個設計,我可以添加任意多個瀏覽器,甚至在可以作爲附加項安裝的獨立模塊中也可以添加。我還可以非常容易地添加新的查看器。例如,我能夠將 Filmstrip Viewer 作爲完全解耦的組件,可以單獨使用,也可以與原始的縮略圖查看器一起使用

  由於很多對象類型(包括 Node 自身)都有自己的本地 Lookup 實例,Lookup API 也有很多其他用處。這裏我所說的還只是冰山的一角。

文件系統 API

  Java 只是通過 java.io.File 類提供了一種文件管理的基本方法,該類包含文件名並提供屬性訪問、基本操作和目錄清單。由於沒有文件系統的概念,這種方法還不甚成熟,此外,File 對象還綁定到本地的物理文件/目錄。如果您需要表示一個虛擬或遠程的目錄樹該怎麼辦呢?幾年前我就曾經面臨這個問題,最後我通過大量繼承 File 解決了這個問題,儘管可行但不簡潔

  NetBeans FileSystem API 填補了這個空隙。有一整套的 FileSystem 類可以用於表示不同類型的文件系統:本地的、遠程的、甚至虛擬的。(NetBeans 的內部配置設置,包括您自定義代碼的配置設置,都存儲在這樣一個虛擬的文件系統中。)唯一的告誡是您必須使用 FileObject 而不是 File 的特定於 NetBeans 的實例,但是將其中一個轉換爲另外一個非常容易:

FileObject fileObject = ...;
File file = ...;
file = FileUtil.toFile(fileObject);
fileObject = FileUtil.toFileObject(file);

  BlueMarine 對管理文件有非常嚴格的要求。每個文件都必須與本地數據庫中存儲的唯一 ID 相關聯。之後可以使用該 ID 建立每個照片和其他實體(如縮路圖、元數據、圖片庫、編輯設置等等)之間的關係。對於這種應用程序來說,這是相當普通的設計,該設計允許您之後無需在數據庫中進行太多更改即可移動或重命名照片。還允許您使用遠程卷,如外部磁盤和 DVD。換句話說,即使系統中該文件不可用時,您也可以查看該文件的縮略圖和元數據。

  調節文件系統管理的良好開端就是 LocalFileSystem 類,該類表示具有一個根的文件樹(對於具有多個根層次的系統,如 Windows,您只需要將幾個 LocalFileSystem 對象放到一起)。

  LocalFileSystem 類包括 AbstractFileSystem.Attr AbstractFileSystem.ListAttr 允許操縱每個 FileObject 的一組屬性,並且 List 允許自定義目錄清單。(屬性只是綁定到 FileObject 的簡單屬性,並且可以通過 getter setter 進行操縱。)

  開始時,我編寫了 LocalFileSystem 的一個簡單子類,用於安裝 Attr List 的裝飾器,如清單 3 中所示。AttrDecorator 類檢索每個文件路徑的惟一 IDFileIndexer 只是該數據的一種 DAO 排序),並使其可以作爲 FileObject ID_ATTRIBUTE)的一種特殊屬性。代碼如清單 4 中所示。

Listing 3. Plugging decorators into the LocalFileSystem class.

class LocalIndexedFileSystem extends LocalFileSystem {

  public LocalIndexedFileSystem() {

     attr = new AttrDecorator(attr, this);

     list = new ListDecorator(list, this);

  }

}             

 

Listing 4. Retrieving registered objects.

class AttrDecorator implements AbstractFileSystem.Attr {

  private static final FileIndexer fileIndexer =

  FileIndexerLocator.findFileIndexer();

  private AbstractFileSystem.Attr peer;

  private LocalIndexedFileSystem fileSystem;

 

  public AttrDecorator(AbstractFileSystem.Attr peer,

  LocalIndexedFileSystem fileSystem) {

    this.peer = peer;

    this.fileSystem = fileSystem;

  }

   public Object readAttribute (String path, String name) {

     if (IndexedFileSystem.ID_ATTRIBUTE.equals(name)) {

     String path2 = fileSystem.findCompletePath(path);

     Serializable id = fileIndexer.findIdByPath(path2);

     if (id == null) {

       fileIndexer.createIndexing(path2, false);

       id = fileIndexer.findIdByPath(path2);

     }

     return id;

     }

     else {

       return peer.readAttribute(path, name);

     }

    }

 

...

}

 

  儘管 AttrDecorator 足夠滿足功能規範,但還是存在批量加載的問題。將會非常隨機地調用 readAttribute() 方法,因此防礙了所有有效的批處理策略(FileIndexer 能夠通過延遲加載進行批處理,但是若要使其有效,必須具有大量要進行批處理的條目!)。

  這裏 ListDecorator 幫助了我們,它在子文件從父目錄中列出之後攔截了子文件(請參見清單 5)。在列出的一組文件上立即調用 createIndexing() 允許 FileIndexer 對其 ID 的檢索進行批處理。

Listing 5. Decorating directory scanning.


class ListDecorator implements AbstractFileSystem.List {
  private static final FileIndexer fileIndexer =
      FileIndexerLocator.findFileIndexer();
  private AbstractFileSystem.List peer;
  private LocalIndexedFileSystem fileSystem;
 
  public ListDecorator (
      AbstractFileSystem.List peer, LocalIndexedFileSystem fileSystem)
  {
    this.peer = peer; 
    this.fileSystem = fileSystem;
  }

  public String[] children (String path) {
    String[] result = peer.children(path);
   
    if (!fileSystem.disableChildrenIndexing) {
      String path2 = fileSystem.findCompletePath(path);
      if (result != null) {
        for (String child : result) {
          fileIndexer.createIndexing(path2 + child, true);
          }
        } 
    }
    return result;
  }
}

操作和菜單

  操作和菜單(以及輔助組件,如工具欄)是與用戶交互的主要工具。Swing 基本支持這些工具,但是您很快就會發現這遠遠不夠用,尤其當您設計一個模塊化的應用程序時更是如此。

  菜單是按照層次進行組織,並且根據直觀標準進行分組的。因此可插拔模塊需要在適當的位置(例如,在菜單欄中的全局“編輯”或“視圖”項下)放置自己的菜單項,可能遵照一些有意義的順序(例如,“模塊 C 的菜單項應該出現在模塊 A 和模塊 B 的菜單項之間”)。您還可能希望引入菜單分隔器以便將某些菜單項組合在一起。

  通過簡單的屬性更改可以啓用或禁用 Swing 操作;但到底是啓用還是禁用操作由您決定。通常,通過一種特殊的方法來執行該操作,這個方法就是計算一組動作的狀態並在用戶進行更改之後進行調用,如下所示:

private void setEnablementStatus() {

       myAction1.setEnabled(/* condition 1 */);

       myAction2.setEnabled(/* condition 2 */);

       ...

       }

  可以使用該方法,但是它既不是模塊也不容易維護。大多數情況必須考慮布爾條件(以前的代碼中的條件 12 等等)只是用戶當前選擇的一組對象的一個功能,例如,只在選中照片的情況下才可以運行“編輯”或“打印”。

  採用比較明智的方法使用 plain Swing 管理菜單和操作並不是需要尖端科學,但是對於一個複雜的、模塊化的應用程序來說,需要從零開始做,這讓人很頭疼。幸運的是,NetBeans 平臺也可以在這個方面幫助您。

  首先,該平臺提供了比 Swing Action 更豐富的類。一些最常用的類爲:

·                                 NodeAction 選中一組新的 Node 時更改狀態的常規操作。程序員必須繼承它並覆蓋 enable(Node[]) 方法,該方法評估正確的布爾表達式以便激活操作。

·                                 CookieAction 這是一個操作,其狀態取決於當前選中的 Node,以及它是否綁定到給定的對象(通常是特定的 DataObject)。它還涉及不同的選擇模式,如“只一個”、“至少一個”等等。

  使用正確的類實現您的操作之後,您在模塊的 layer.xml 中聲明該類,layer.xml 充當常規配置文件(它將“虛擬文件系統”結構模型化爲 包含 XML DOM )。

注意:通常,您不需要手動執行該操作:NetBeans IDE 提供了一個“新建操作”嚮導,該向導要求輸入所需的信息,生成 Java 代碼框架並更新 layer.xml 的相關部分。實際上,以下示例中的大部分 XML 都可以通過 IDE 生成或操縱。

  該方法適用於上下文菜單上出現的操作以及需要附加到“常規”菜單的操作。在 layer.xml 中,您還可以聲明工具欄(其中很多按鈕已經按邏輯順序綁定到操作),定義鍵盤快捷方式。


窗口 API

NetBeans 平臺提供了特定的窗口組件,名爲 TopComponent該組件模擬矩形形狀的主窗口,可以調整該部分的大小以及將其停靠在屏幕的不同區域(這些區域稱爲“模式”)。例如,“瀏覽器”模式是左側的一個垂直列;“屬性”模式是右側的一個垂直列;“編輯器”模式是中央的剩餘空間

  此外,還可以激活或取消激活 TopComponent它具有自己的機制來保持永久狀態(重新啓動時自動恢復該狀態),並且可以通過使其選項卡閃爍引起注意。

  可以使用鼠標或通過編程的方式調整停靠區域的大小。通過用鼠標拖動或通過編程,可以將 TopComponent 指定給不同的區域。也可以定義您自己的停靠區域。例如,我需要一個應該放置在窗口底部的名爲“Film Strip”的組件。因此我定義了一個名爲“strip”的停靠區域,並且將 Film Strip 與之綁定在一起

  對於某些類型的應用程序(例如 IDE CAD 系統)來說,這種靈活性非常好,因此大量控制可以轉移到某些級別的用戶。對於 blueMarine,我不喜歡有這樣靈活的停靠:使用固定模式,只是通過菜單命令(允許您在“瀏覽器”模式和“屬性”模式中交換組件)提供某些控制。

  blueMarine 中,通過指定特殊的 TabDisplayerUI(用於每個模式的可視組件)已經刪除了允許用鼠標停靠的選項卡和控制代碼。我通過清單 6 中的代碼用程序進行控制。

Listing 6. Programmatic component docking.

TopComponent topComponent = ...;

   String newMode = “explorer”;

   Mode targetMode = WindowManager.getDefault().findMode(newMode);

if (targetMode != null) {

  component.close();

  targetMode.dockInto(component);

  component.open();

  component.requestVisible();

}            

注意:實際上,都是由於我在 Geertjan Wielenga 的博客上發現了這個解決方案,因此實現 TabDisplayerUI 技術並不太難;如果沒有它的幫助,我將需要更長的時間。我發現程序員可以獲得 NetBeans 社區中郵件列表和傳播者博客的大力支持,我建議您對這些內容進行標記!

外觀

  隨着 JDK 版本的不斷髮布,預定義的 Java 外觀變得越來越出衆,但是有時您需要特殊的 LAF。例如,要處理照片,您需要一個外觀樸素、主題灰暗的整潔GUI (所有使用的顏色都有嚴格的灰度梯度),以便不會干擾顏色的正確分辨。

  自從 JDK 1.4 開始,UIManager 類就允許在儘量少影響或不影響現有代碼的情況下插入不同的外觀。由於該類是標準 Swing API 的一部分,因此可以將很多兼容的 LAF 輕鬆插入到應用程序中。

  如果您發現了喜歡的外觀,您可以通過簡單的命令行切換將它安裝到 NetBeans 中(當然是安裝到您的 NetBeans 平臺應用程序中):

--look-and-feel

  經過一些測試之後,我決定保留除了主窗口之外每個 GUI 部分的原始外觀,在主窗口中我只更改了組件顏色(Mac OS X 就是一個特例;請參見下面內容)。

  正如您所知,更改 Swing 組件的顏色通常是 c.setForeground() c.setBackground() 的事。由於 NetBeans 平臺是基於 Swing 的,因此並沒有很多不同。但是也有幾個例外。例如,這些標準方法不適用 ListViewNode 對象最常用的視圖組件之一)。在 blueMarine 中,通過  清單  7 中的代碼已經解決了這個問題,該代碼首先檢索內部的 Jlist,然後根據需要更改其屬性。類似的代碼也適用於基於樹的組件(這些組件也有相同的問題)。

Listing 7. An enhanced ListView.

public class EnhancedListView extends ListView {

protected JList jList;

 

@Override

protected JList createList() {

  jList = super.createList();

  jList.setOpaque(isOpaque());

  jList.setBackground(getBackground());

  jList.setForeground(getForeground());

  return jList;

}

@Override

public void setBackground (Color color) {

  super.setBackground(color);

  if (jList != null) {

  jList.setBackground(color);

}

}

@Override

public void setForeground (Color color){

  super.setForeground(color);

  if (jList != null) {

    jList.setForeground(color);

  }

}

@Override

public void setOpaque (boolean opaque){

  super.setOpaque(opaque);

  if (jList != null) {

    jList.setOpaque(opaque);

  }

}

}

              

  我發現了基於樹的組件存在的另一個問題:即使使用前面列出的代碼,樹單元仍以黑白顏色顯示。而且,通過檢查源代碼很容易找到原因:通常,NetBeans 的樹具有一個特殊的單元格渲染器,它執行很多操作,如支持 HTML 顯示(以便您可以使用多個文本樣式);該單元格渲染器還選擇在前景和背景之間對比明確的顏色主題。在大多數情況下,這是一個比較明智的方法,但是當您想要微調顏色時,這個方法就不太合適。通過清單 8 中所示的幾行代碼解決了這個問題。下面是安裝修補後的渲染器的方法:

PatchedNodeRenderer nodeRenderer = new PatchedNodeRenderer(tree.getCellRenderer());
tree.setCellRenderer(nodeRenderer);

Listing 8. A patched cell renderer for controlling colors in JTree’s.

class PatchedNodeRenderer extends DefaultTreeCellRenderer {

  private TreeCellRenderer peer;

  public PatchedNodeRenderer (final TreeCellRenderer peer) {

    this.peer = peer;

  }

  @Override

  public Component getTreeCellRendererComponent (final JTree jTree,

      final Object object, final boolean selected, final boolean expanded,

      final boolean leaf, final int row, final boolean hasFocus)

  {

    final Component component = peer.getTreeCellRendererComponent(

        jTree, object, selected, expanded, leaf, row, hasFocus);

        component.setBackground(

        selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor());

        component.setForeground(

        selected ? getTextSelectionColor() : getTextNonSelectionColor());

        return component;

  }

}

  關於外觀,Mac OS X 提出了一些特殊的問題。對美學很挑剔的 Mac 用戶注意到,一段時間以前,甚至 Apple 創建的 Java 實現都沒有精確地複製操作系統外觀。這樣便促使了第三方產品 Quaqua 的出現,Quaqua 解決了所有這些問題並實現了像素精度的 Aqua GUI。(實際上這些問題遠超出像素精度:例如,Apple  Mac OS LAF 下的 Java JFileChooser 本機的相比精度不夠。由於 Quaqua 的普通外觀由 UIManager 類處理,而它在 blueMarine 中的集成也不是問題,因此除了幾個 還有少數Quaqua 問題由項目的開發人員解決了。


 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章