輕松實現Java用戶界面編程 – JAVA編程語言程序開發技術文章

Buoy 是一個構建在 Swing 之上的免費用戶界面(UI)工具包,它為 UI 開發人員提供瞭方便性和簡單性。在本文中作者用一個簡單的 fractal 用戶界面程序,介紹瞭 Buoy 可以做什麼、為什麼這麼做。

第一次嘗試用 Java 語言構建簡單的用戶界面時,我對 Swing 接口的復雜性感到有些驚訝。老實說,有點想打退堂鼓。最近,一個朋友向我提到,他使用的渲染程序 Art of Illusion(請參閱 參考資料)基於一個不同的工具包:Buoy。推薦它的原因之一是它的界面更友好。當他第一次提到它時,我以為他在談 “BUI”,而它與 GUI 這個名字的相似是故意的。在這裡 B 代表 better(更好),但是名字 Buoy 並不是縮寫。

Buoy 是免費的。實際上,它是公共的東西。它並沒有在某個開放程度合理的許可下發佈,實際上它根本不受任何許可控制。這意味著在任何用 Java 語言編寫的能夠運行 Buoy 的項目中都可以使用 Buoy,而不用考慮許可問題。因為提供瞭完整的源代碼,所以這個工具包很容易修改和擴展。本文基於 Buoy 1.3 發行版,要求讀者對 Swing 有基本的瞭解,雖然不瞭解也能對付過去。

示例程序

我曾經嘗試用 Swing 構建的第一個應用程序最後以失敗告終。為瞭看出工具包之間的對比情況,我決定使用 Buoy 來構建這同一個程序。文章中的代碼示例全部來自該程序的 Buoy 版本。程序生成瞭一些分形,具體地說,是迭代的分形。基本思想很簡單:在平面上定義一系列的線條區段,從(0,0) 到(1,0),圍繞任意一個單位線條定位。繪制這些區段之後,繪制同一套變形線條,用這個區段作為單位向量。做起來比說的更容易,就像在圖 1 中看到的。

圖 1. 分形編輯器中的分形

這個程序的界面相當簡單。它有一些界面小部件,有一個畫佈,在畫佈上繪制漂亮的圖片,還支持用鼠標操縱圖片。實際上,必須要做的全部工作就是操縱構成原始曲線的點,原始曲線會迭代地繪制出來。界面還有一個最小化的菜單;它可以打開和關閉文件,關閉窗口,或者把當前圖像保存為 PNG 格式的文件。雖然簡單,但是這個界面簡要地提供瞭一個 Buoy 小部件的合理示例,還有相當數量對事件處理系統的體驗。

程序實際的核心代碼 —— 分形生成器 —— 已經寫好瞭,這把這個示例變成一個很好的測試程序。當然,在更新它的過程中,我也發現並且修補瞭一些 bug。
發行包中包含示例程序的源代碼,還有編譯好的類文件和 Buoy 的 JAR 文件(單擊本文頂部或底部的 Code 圖標,下載 factal.tar)。包中還包含一個叫做 frac 的目錄,裡面包含一些示例分形。如果使用一臺 UNIX 風格的機器,在路徑中有 Java 編譯器,那麼隻要運行 make 就能運行它。否則,需要設置 classpath 包含當前路徑和 Buoy 的 JAR 文件所在的目錄,然後運行 FractalViewer 類。在 Windows 系統上,正確的命令行應當是 java -classpath .;Buoy.jar FractalViewer。

  sed -e s/J/B/g

  在第一次深入研究代碼時,也許會形成這樣的印象:把 Swing 代碼轉換成 Buoy 代碼簡單得就像把 UI 元素名稱中的字母 J 換成 B 一樣簡單。例如, FractalViewer 類不再擴展 JFrame;它現在擴展的是 BFrame。主要的小部件名稱也可以照此推測得到。Spinner 和 slider 像以前一樣有相同的名字,隻是換瞭一個字母。 MenuBar(菜單條) 仍然由 Menus(菜單)構成,菜單則容納 MenuItems。

  有些命名轉換略有不同。在 Swing 引用 BorderLayout 的地方,Buoy 有 BorderContainer。一般來說,Buoy 的命名轉換相當統一,雖然不總是與 Swing 的命名一樣。一個明顯的區別是 Buoy 幾乎組合瞭容器和佈局管理器的概念;每種容器類型都知道自己如何佈局。這大大簡化瞭設計。例如,在分形生成器中使用的 LabelWidget 類是一個 BorderContainer;在 Swing 中,這可能是一個帶有 BorderLayout 佈局管理器的 JPanel。

  但是,兩者還是有許多相似之處。這對適應新東西有很大幫助。更重要的是,Buoy 構建在 Swing 之上。這意味著,一般來說,如果需要做的事不能輕松地用 Bouy 完成時,可以把 Buoy 對象傳遞給它包裝的 Swing 對象。對於這種情況,如果想訪問一些沒有 Buoy 對應物的 Swing 對象,可以簡單地把它包裝在 AWTWidget 對象中,這個對象提供瞭非常薄的包裝器,通過它,不僅 Buoy 自己的小部件,而且所有的小部件都能訪問 Buoy 的小部件 API。例如,如果發現確實需要 GridBagLayout,可能就需要這樣做。

  例如,FractalPanel 類是一個 AWTWidget。在早期設計中, 它是 JPanel 的子類, 但實際上我並不需要 JPanel 代碼。相反,我構建瞭包裝定制類的類 FractalCanvas, 它本身是普通的 Canvas 類的一個子類。把它變成一個 AWTWidget,就可以在它上面利用 Buoy 高效的事件處理機制。

  事件處理代碼非常簡單。在按下鼠標按鈕時,通過 addEventLink() 的魔力,Buoy 發送一個新的 MousePressedEvent 事件到 mousePressed() 函數。我忽略瞭按下哪個按鈕這個問題,隻考慮按住 shift 單擊或普通單擊。普通單擊選擇最靠近的點,而按住 shift 單擊則重新把顯示居中。然後,如果鼠標移動,那麼每次 Buoy 註意到移動時都會開始發送 MouseDraggedEvent 事件。在處理這些事件時,FractalPanel 會生成自己的事件。

  近觀 PointChangedEvent

  為瞭讓一些討論更加具體,請來看 PointChangedEvent。這是一個試驗性的類,如果不喜歡它,那也隻能怪老天瞭。這個類的想法是:讓一個類來表示狀態點中的變化。編輯器跟蹤“當前”點 —— 也就是編輯器小部件目前正在編輯的點。可以用這些小部件或在分形面板中單擊選擇新的點,選擇的是最靠近的點。

  我得出這樣一個結論:在代碼中,大概有三類涉及到點的事件需要從一個類發送到另一個類。

  一個是改變某個點的特征: POINT 事件類型。如果由編輯器發送,就是告訴分形改變原型線條上的點,並要求重畫線條。如果由分形發送,則是告訴編輯器剛剛選中的點的特性。

  下一個是選擇某個點。可以按索引或位置進行選擇。所以,如果隻提供瞭索引或位置,那麼構造函數會認為意圖是填充其他值。有一點特殊的地方,點索引 -1 用來表示沒有選中的點,所以必須用 -2表示編輯器正在尋找指定位置的點。這可能不漂亮,但是有效。

  有點意思的是 Fractal 類響應 SELECT 事件的方式。如果成功地選擇瞭一個點,就會發回一個新的 POINT 類型的 PointChangedEvent 事件,如清單 1 所示。

  清單 1. 用事件回答事件
case PointChangedEvent.SELECT:
if (e.getIndex() >= -1)
selectPoint(e.getIndex());
else
selectPoint(e.getPoint());
// just in case they dont know
event(new FractalChangedEvent(FractalChangedEvent.SIZE, size));
if (selectedPoint >= 0 && selectedPoint < size)
event(new PointChangedEvent(selectedPoint, points[selectedPoint]));
else
event(new PointChangedEvent(selectedPoint, null));
event(new FractalChangedEvent(FractalChangedEvent.REDRAW));
break;


  最後,移動點是一個特殊情況,如果不需要改變點的其他屬性(例如顏色),那麼所要處理的就是位置。這就是 MOVE 事件類型。在效果上,它與 POINT 事件類型效果很像,但它不需要事件生成器(通常是 FractalPanel 類)去關心那些它根本不知道的屬性。

 INSERT 和 DELETE 事件類型隻有部分相關,可能應當屬於 FractalChangedEvent 事件。

  事件處理

  正如已經開始看到的,事件處理是 Buoy 與 Swing 最明顯的不同之處。事件處理提供瞭大量靈活性。Buoy 本身的事件集相當豐富,且允許您挑選自己感興趣的事件,從任何小部件向其他對象發送事件。例如,如果想在 Swing 中捕獲鼠標事件,捕獲事件的類需要實現 MouseListener 接口。這個接口有 5 個函數需要實現,即使它們就是擺設也必須實現。而且必須使用接口提供的函數名稱。更糟的是,函數必須是偵聽器接口的公共部分;要麼把這作為公共接口的一部分公開,要麼創建一個什麼都不做、隻是包裝事件偵聽器代碼的內部類。

  在 Buoy 中,每個小部件都是 EventSource 。這意味著可以從每個小部件偵聽事件。什麼類型的事件呢?任何類型都可以。關鍵的函數是 addEventLink()。這允許您指定類、偵聽器以及可選的方法。每當 EventSource 分派這個類或它的子類的事件時,偵聽器都會接收到事件,要麼是通過一個叫做 processEvent()的方法,要麼是通過在開始調用 addEventLink() 時提供的方法名稱。提供的函數不能接受參數,也不能接受與指定事件類型兼容的類的對象;父類和接口可以。

  這是一個方便的設置。可以把不同的事件路由到不同的函數或相同的函數。例如,MousePressedEvent 和 MouseReleasedEvent 會被分別處理。在示例程序中,鼠標的按下、釋放和拖動分別有不同的線程,如清單 2 所示。註意,這遠遠超過 Swing 的 MouseListener 所能做的。如果用 Swing 編程的話,就需要實現 MouseListener 和 MouseMotionListener 這兩個接口。

  清單2. 隻挑感興趣的事件
this.addEventLink(MousePressedEvent.class, this, “mousePressed”);
this.addEventLink(MouseRele

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。