本文是本系列的第三篇也是最後一篇文章,在本文中,A. O. Van Emmenis 將通過添加操作、菜單欄、彈出菜單和工具欄完成在第 1 和第 2 部分中着手討論的文件資源管理器示例。他將演示如何設置菜單項特性,如何重用菜單和工具欄中的操作,以及如何通過偵聽來自查看器的事件使操作識別上下文。示例操作使用實用程序來啓動程序和訪問系統剪貼板。
本系列的 第 1 部分着手討論一個示例,該示例將 JFace 應用程序窗口子類化並使用樹查看器和表查看器來顯示文件夾和文件。在 第 2 部分中,我們做了些完善工作並使用 JFace 圖像註冊表添加了一些圖標。
這次,我們將研究操作,您可以在菜單和工具欄中使用它們。我們將看到如何使用 Program類啓動程序,及如何使用 Clipboard類訪問系統剪貼板。我們已經使用了圖標在查看器中顯示文件和文件夾。我們將看到如何在菜單和工具欄中也使用它們。最後,我們將使操作偵聽來自查看器的事件以使其對上下文敏感。
如果要下載本文中 示例的代碼,請注意我的系統設置:
- 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 所示。
我們在左邊窗格中使用樹查看器顯示文件夾和文件。當在左邊窗格中選中某個文件夾時,它所包含的文件就顯示在右邊窗格的表查看器中。我們對右邊窗格中的各項排序以便首先出現的是文件夾。我們在兩個查看器中都使用圖標來表示文件和文件夾。
讓我們給窗口添加一個簡單的菜單欄。
|
JFace MenuManager簡化了 SWT 菜單的創建和更新。菜單管理器可包含菜單項、其它菜單管理器(用於子菜單)和分隔符。一旦創建了菜單管理器,就可以用菜單欄、上下文菜單(也就是彈出菜單)或工具欄下拉菜單表示它。
同查看器一樣,儘管通常不需要訪問 SWT 菜單本身,但菜單管理器是助手對象而不是包裝器對象。在討論菜單之前,首先看看菜單管理器能包含什麼。
給菜單管理器添加操作。實際上,也可以給按鈕和工具欄添加操作。其方法是:將 Action 子類化,設置希望在菜單/工具欄/按鈕中出現的文本,然後實現 run()
方法以使其做您想做的事情。
讓我們先看一下清單 1 中顯示的示例 ExitAction:
清單 1. ExitAction(V1)import org.eclipse.jface.action.*; import org.eclipse.jface.window.*; public class ExitAction extends Action { ApplicationWindow window; public ExitAction(ApplicationWindow w) { window = w; setText("E&xit"); } public void run() { window.close(); } } |
一切都相當簡單。Exit 中 x前的 &字符表明 x是該菜單項的 鍵盤導航鍵(助記符)。注:這不同於 加速鍵(熱鍵)。很快就會看到這些……
|
爲了配置應用程序窗口使其有菜單欄,我們在 ApplicationWindow中使用下面的方法:
protected void addMenuBar()
請記住:我們必須在創建 SWT shell 之前這麼做。而且,這將調用應用程序窗口方法 createMenuManager()
,後者會返回它稍後用來創建 SWT 菜單欄的菜單管理器。我們的實現如清單 2 所示:
protected MenuManager createMenuManager() { MenuManager bar_menu = new MenuManager(""); MenuManager file_menu = new MenuManager("&File"); MenuManager edit_menu = new MenuManager("&Edit"); MenuManager view_menu = new MenuManager("&View"); bar_menu.add(file_menu); bar_menu.add(edit_menu); bar_menu.add(view_menu); file_menu.add(new ExitAction(this)); return bar_menu; } |
資源管理器現在如圖 2 所示:
圖 2. 資源管理器(V9)
注意空菜單是被禁用的。試着用導航鍵 ALT+Fx關閉資源管理器應用程序。
讓我們稍稍改進一下“退出”操作,如清單 3 所示:
清單 3. ExitAction(V2)import org.eclipse.jface.action.*; import org.eclipse.jface.resource.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; public class ExitAction extends Action { ApplicationWindow window; public ExitAction(ApplicationWindow w) { window = w; setText("E&xit@Ctrl+W"); setToolTipText("Exit the application"); setImageDescriptor( ImageDescriptor.createFromURL(Util.newURL("file:icons/close.gif"))); } public void run() { window.close(); } } |
我們添加了一個加速鍵(熱鍵)、一條工具提示和一個圖像(工具提示不會在菜單項上顯示,但會在工具欄項上顯示,稍後將看到這一點)。有一個可以直接設置加速鍵的方法,但在文本中 @字符後指定它更方便,因爲用這種方法,加速鍵被添加到菜單項的文本中,如圖 3 所示:
圖 3. 資源管理器(V10)
眼尖的讀者或許已經注意到我們直接將一個圖像描述符添加到操作中。我們真正想做的是從圖像註冊表獲取圖像描述符。問題是圖像註冊表只提供 Images— 無法要求它提供 ImageDescriptor。這是 Eclipse 錯誤數據庫中的 錯誤 23555。
運行與文件關聯的程序非常有用。實際上使用 Program類來這樣做也非常簡單,如清單 4 所示:
清單 4. OpenAction(V1)import java.io.*; import org.eclipse.jface.action.*; import org.eclipse.jface.resource.*; import org.eclipse.jface.viewers.*; import org.eclipse.swt.program.*; public class OpenAction extends Action { Explorer window; public OpenAction(Explorer w) { window = w; setText("Run"); setToolTipText("Run the associated program on a file"); setImageDescriptor( ImageDescriptor.createFromURL(Util.newURL("file:icons/run.gif"))); } public void run() { IStructuredSelection selection = window.getTableSelection(); if (selection.size() != 1) return; File selected_file = (File) selection.getFirstElement(); if (selected_file.isFile()) { Program.launch(selected_file.getAbsolutePath()); } } } |
我們使用 getTableSelection()
(稍後討論該方法)從表獲得選中的元素,然後檢查是否恰好有一個元素被選中 — 請記住表現在是多選風格的表 — 然後獲取元素本身,確定它確實是文件而不是文件夾,然後啓動它。
Program 的 launch()
方法負責根據文件擴展名查找關聯的程序,然後運行適當的可執行文件(將絕對文件名作爲參數提供)。
在嘗試這個操作之前,讓我們先實現最後一個操作。
這裏簡單地利用一下系統剪貼板。 CopyFileNamesToClipboardAction
操作將所有選中文件的絕對文件名複製到剪貼板中。
我們使用 Clipboard對象將文本傳送到系統剪貼板。
首先使 Util
類簡單地創建一個剪貼板對象,如清單 5 所示:
import java.net.*; import org.eclipse.jface.resource.*; import org.eclipse.swt.dnd.*; import org.eclipse.swt.widgets.*; public class Util { private static ImageRegistry image_registry; private static Clipboard clipboard; 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; } public static Clipboard getClipboard() { if (clipboard == null) { clipboard = new Clipboard(Display.getCurrent()); } return clipboard; } } |
要將文本放入剪貼板,可對 Clipboard 使用以下方法:
public void setContents(Object[] data, Transfer[] dataTypes)
數據中的每個數組槽都與一個傳送對象關聯,傳送對象告訴剪貼板數據是什麼數據類型。在本例中,我們使用 TextTransfer對象,它告訴剪貼板我們所傳送的是純文本(而不是 RTF 或別的)。
public void setContents(Object[] data, Transfer[] dataTypes)
的參數是數組,因此可以一次傳送幾種格式的數據。例如,某個字處理應用程序可能要傳送 RTF 和純文本格式的文本。
在清單 6 中展開的代碼中,我們做了以下事情:
- 獲取選擇
- 確保它不爲空
- 循環檢查選擇,將絕對文件名添加到字符串緩衝區
- 用 Text Transfer 對象將字符串傳送到剪貼板
- 將字符串放入狀態行以提供一些反饋
清單 6. CopyFileNamesToClipboardAction(V1)
import java.io.*; import java.util.*; import org.eclipse.jface.action.*; import org.eclipse.jface.resource.*; import org.eclipse.jface.viewers.*; import org.eclipse.swt.*; import org.eclipse.swt.dnd.*; public class CopyFileNamesToClipboardAction extends Action { Explorer window; public CopyFileNamesToClipboardAction(Explorer w) { window = w; setToolTipText("Copy absolute file names of selected files to the clipboard"); setText("Copy File &Names@Ctrl+Shift+C"); setImageDescriptor( ImageDescriptor.createFromURL(Util.newURL("file:icons/copy.gif"))); } public void run() { Clipboard clipboard = Util.getClipboard(); TextTransfer text_transfer = TextTransfer.getInstance(); IStructuredSelection selection = window.getTableSelection(); if (selection.isEmpty()) { return; } StringBuffer string_buffer = new StringBuffer(); for (Iterator i = selection.iterator(); i.hasNext();) { File file = (File) i.next(); string_buffer.append(" "); string_buffer.append(file.getAbsolutePath()); } clipboard.setContents( new Object[] { string_buffer.toString()}, new Transfer[] { text_transfer }); } } |
最後,更改資源管理器,添加 getTableSelection()
方法以及給菜單欄添加兩個新操作的代碼,如下所示(清單 7):
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 { private TableViewer tbv; ... public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); Util.getClipboard().dispose(); } protected MenuManager createMenuManager() { MenuManager bar_menu = new MenuManager(""); MenuManager file_menu = new MenuManager("&File"); MenuManager edit_menu = new MenuManager("&Edit"); MenuManager view_menu = new MenuManager("&View"); bar_menu.add(file_menu); bar_menu.add(edit_menu); bar_menu.add(view_menu); file_menu.add(new ExitAction(this)); edit_menu.add(new CopyFileNamesToClipboardAction(this)); edit_menu.add(new OpenAction(this)); return bar_menu; } public IStructuredSelection getTableSelection() { return (IStructuredSelection) (tbv.getSelection()); } } |
於是,我們有了 3 個操作和另外 8 個類。讓我們查閱資源管理器的實例圖(圖 4)(已實現的類以深灰色顯示)。
圖 4. 資源管理器(V10)及其相關對象
讓我們運行這一版本,看看這些操作(圖 5):
圖 5. 資源管理器(V10)
依我的經驗,在設計 GUI 對象時,往往最後構建了相當龐大的實例層次結構。不同層次結構中的低層對象要相互知道對方,因此人們往往會在它們之間創建專門的交叉引用,而這會導致代碼十分混亂。
即使在我們這樣小的示例中,也可以看到混亂的苗頭。操作對象需要知道表查看器中當前選中的項。問題是:操作存儲什麼對象?是表查看器或 SWT 表本身,還是窗口?
圖 6. 窗口、窗口小部件和操作
一種解決方案是讓所有對窗口小部件/選擇對象的訪問都經過窗口。讓每個操作存儲創建它的窗口,然後對該窗口使用取值方法獲取它所需的對象。
如果希望在不同窗口間共享操作(在不同窗口中用不同的方法名稱訪問窗口小部件),可以把通過操作對窗口小部件的訪問包裝在一個方法中,然後在該操作的子類中重新實現該方法以訪存正確的窗口小部件。
例如,要共享訪問“選擇”所需的操作,可將超類中的方法 getSelection()
在某個子類中實現爲:
window.getTableSelection()
而在另一個子類中(比方說)實現爲
window.getThirdListViewerSelection()
。
可以對操作做的另一件事是使它們知道窗口其它地方所發生的事,並據此自動適應之。我們將使 OpenAction
偵聽表查看器中當前選擇的任何變化。當它注意到變化時,會查看新的選擇並更改它的文本、工具提示和啓用狀態來反映這一變化。
OpenAction
對當前選中文件啓動“關聯的”程序。如果當時什麼也沒有選中,與其讓它運行並報錯(或乾脆令人費解地什麼也不幹),倒不如禁用該操作。
儘管我們是在爲用戶着想,但如果我看到菜單項被禁用卻不知道原因的話,通常我會覺得困惑。所以,有這樣一個辦法:能不能更改工具提示以便能讓用戶也知道 爲什麼禁用該操作呢?
我們還要確定當選擇包括幾個文件時要怎麼做。對,我們也許可以逐一地運行所有文件,但是,由於我曾有過在 Windows 文件資源管理器中選中 300 個文件後選擇打開選項的痛苦經歷,所以還是暫時先禁用打開操作吧。
最後,我們將檢查選中的項是否爲文件夾,如果是,那麼也禁用打開操作。
我們要使 OpenAction
偵聽來自表查看器的 SelectionChanged 事件,因此我們將使它實現 ISelectionChangedListener(清單 8)。
我們將在 selectionChanged()
中這麼做:
- 將文本和工具提示文本設置爲缺省值
- 檢查選擇
- 如果選中的不是恰好一項,則禁用該操作,調整工具提示以說明爲什麼禁用,然後返回。
- 如果選擇是文件(而不是文件夾),則調整文本和工具提示以反映該文件的名稱並啓用它。
import java.io.*; import org.eclipse.jface.action.*; import org.eclipse.jface.resource.*; import org.eclipse.jface.viewers.*; import org.eclipse.swt.program.*; public class OpenAction extends Action implements ISelectionChangedListener { Explorer window; public OpenAction(Explorer w) { window = w; setText("Run"); setToolTipText("Run the associated program on a file"); setImageDescriptor( ImageDescriptor.createFromURL(Util.newURL("file:icons/run.gif"))); } public void run() { IStructuredSelection selection = window.getTableSelection(); if (selection.size() != 1) { return; } File selected_file = (File) selection.getFirstElement(); if (selected_file.isFile()) { Program.launch(selected_file.getAbsolutePath()); } } public void selectionChanged(SelectionChangedEvent event) { setText("Run"); setToolTipText("Run the associated program on a file"); IStructuredSelection selection = window.getTableSelection(); if (selection.size() != 1) { setEnabled(false); setToolTipText( getToolTipText() + " (Only enabled when exactly one item is selected)"); return; } File file = (File) selection.getFirstElement(); if (file.isFile()) { setEnabled(true); setText("Run the associated program on " + file.getName()); setToolTipText( "Run the program associated with " + file.getName() + " with this file as the argument"); } } } |
接下來,使打開操作偵聽來自表查看器的 SelectionChanged 事件(清單 9)。
清單 9. 資源管理器(V11);省略了某些未更改的代碼... public class Explorer extends ApplicationWindow { private TableViewer tbv; private OpenAction open_action; ... protected Control createContents(Composite parent) { ... tbv.addSelectionChangedListener(open_action); return sash_form; } ... protected MenuManager createMenuManager() { MenuManager bar_menu = new MenuManager(""); MenuManager file_menu = new MenuManager("&File"); MenuManager edit_menu = new MenuManager("&Edit"); MenuManager view_menu = new MenuManager("&View"); bar_menu.add(file_menu); bar_menu.add(edit_menu); bar_menu.add(view_menu); file_menu.add(new ExitAction(this)); edit_menu.add(new CopyFileNamesToClipboardAction(this)); open_action = new OpenAction(this); edit_menu.add(open_action); return bar_menu; } ... } |
圖 7. 資源管理器(V11),顯示選中一個文件時的 Edit 菜單
現在讓我們看看已選中文件的數目如何改變打開操作(圖 8):
圖 8. 資源管理器(V11),顯示沒有選中文件時的 Edit 菜單最後,我們將添加工具欄和彈出(上下文)菜單。值得高興的是,其實沒有太多事情要做,因爲在操作中已經完成了所有的艱鉅工作。工具欄和彈出菜單隻需共享這些操作。
與狀態行和菜單欄一樣,我們配置窗口使其有一個工具欄,並且實現 createToolBarManager()
方法來創建它。
對於彈出菜單則略有不同。我們用 createContents()
方法創建它,然後直接把它添加到表窗口小部件。
我們還重構了代碼,將這三個操作作爲字段(而不是局部變量),這樣我們就可以用三種方法來訪問它們。讓我們看看資源管理器的最終版本(清單 10):
清單 10. 資源管理器(V12)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 { private TableViewer tbv; private TreeViewer tv; private OpenAction open_action; private ExitAction exit_action; private CopyFileNamesToClipboardAction copy_action; public Explorer() { super(null); exit_action = new ExitAction(this); copy_action = new CopyFileNamesToClipboardAction(this); open_action = new OpenAction(this); addStatusLine(); addMenuBar(); addToolBar(SWT.FLAT | SWT.WRAP); } protected Control createContents(Composite parent) { getShell().setText("JFace File Explorer"); SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL); tv = new TreeViewer(sash_form); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C://")); tv.addFilter(new AllowOnlyFoldersFilter()); tbv = new TableViewer(sash_form, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI); tbv.setContentProvider(new FileTableContentProvider()); tbv.setLabelProvider(new FileTableLabelProvider()); tbv.setSorter(new FileSorter()); 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()); } }); tbv.addSelectionChangedListener(open_action); MenuManager menu_manager = new MenuManager(); tbv.getTable().setMenu(menu_manager.createContextMenu(tbv.getTable())); menu_manager.add(exit_action); menu_manager.add(copy_action); menu_manager.add(open_action); return sash_form; } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); Util.getClipboard().dispose(); } protected MenuManager createMenuManager() { MenuManager bar_menu = new MenuManager(""); MenuManager file_menu = new MenuManager("&File"); MenuManager edit_menu = new MenuManager("&Edit"); MenuManager view_menu = new MenuManager("&View"); bar_menu.add(file_menu); bar_menu.add(edit_menu); bar_menu.add(view_menu); file_menu.add(exit_action); edit_menu.add(copy_action); edit_menu.add(open_action); return bar_menu; } public IStructuredSelection getTableSelection() { return (IStructuredSelection) (tbv.getSelection()); } public void openFolder(File folder) { tv.setExpandedState(folder, true); tv.setSelection(new StructuredSelection(folder), false); } protected ToolBarManager createToolBarManager(int style) { ToolBarManager tool_bar_manager = new ToolBarManager(style); tool_bar_manager.add(exit_action); tool_bar_manager.add(copy_action); tool_bar_manager.add(open_action); return tool_bar_manager; } } |
現在,讓我們最後一次啓動資源管理器,看看操作中的工具欄和彈出菜單(圖 9 和圖 10)。
圖 9. 資源管理器(V12),顯示選中一個文件時的彈出菜單
圖 10. 資源管理器(V12),顯示選中兩個文件時的工具提示
|
我們已經在這三篇文章中學習了許多 JFace 的知識。我們瞭解瞭如何使用相對較少的代碼讓可插入 JFace 窗口、查看器和菜單框架生成漂亮的用戶界面。
希望您已經掌握:
- 如何將應用程序窗口子類化
- 如何使用可插入查看器和內容提供程序
- 如何使用圖像及圖像註冊表添加圖標
- 如何使用系統剪貼板
- 如何啓動程序
- 如何使用菜單和操作
- 操作如何使用不同的菜單容器和偵聽器來生成對上下文敏感的應用程序
不過,當然還有更多。請查閱 參考資料以獲得更多信息。
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文.
- 請閱讀本系列的 本系列的第 1 部分 和 本系列的第 2 部分。
- 下載本文中 示例的代碼。
- 請訪問主 Eclipse 網站以獲得下載、文檔、郵件歸檔和文章。在那裏,您可以瞭解關於以下類和接口的更多信息:
Program、 Clipboard、 MenuManager、 Action、 ApplicationWindow、 TextTransfer
和ISelectionChangedListener
。 - 有關在 Eclipse Workbench 中使用樹查看器的描述,請參閱 Eclipse 文章“ How to use the JFace Tree Viewer”。
- Eclipse 文章“ Using Images in the Eclipse UI”中討論了圖像處理。
- 要獲得項目開發計劃、FAQ 和有用的 SWT 代碼片段列表,請查閱 SWT component development resources。
- 在以下 developerWorks文章中瞭解有關 Eclipse 的更多信息:
- 在 developerWorks開放源碼項目專區找到 更多開放源碼參考資料。
A. O. Van Emmenis 是一位獨立顧問,專門從事 Java/J2EE 培訓和諮詢工作,他在英國的劍橋工作。Van 已經在軟件行業工作了約 20 年左右。他最初與對象打交道是在 CAD 行業使用 Smalltalk 的時候,他現在在工作中主要使用 Java。他對敏捷(Agile)方法和 GUI 設計特別感興趣。您可以通過 [email protected]與 Van 聯繫。 |