Eclipse外掛程式開發之定製嚮導

來源:互聯網
上載者:User
  以前我有一個微型的攜帶型電子地址薄。我一直認為它很不錯,直到有一天它停止運行了。銷售該產品的人員無法找回我的聯絡地址名冊,卻提議更換一台。這時候我才知道資料的重要性。這個閃亮的小發明與儲存在它裡面的資料相比根本就不值一提。

  在這個序列文章的第一部分中,我介紹了Eclipse外掛程式的開發環境,並開發了一個簡單的外掛程式。在第二部分,我添加了工具條按鈕、功能表項目和對話方塊。它實際上沒有實現任何具體功能。它簡單地用某種字型顯示了樣本常值內容。現在我們要讓它能夠管理實際的資料。我們將修改這個外掛程式,讓它實現我們所需要的功能。本文討論的是編輯器文檔,並示範了如何定製一個嚮導。

   Invokatron的曆史

  首先,我們詳細說明一下Invokatron本身。在前面的文章中我們討論過,Invokatron是一個產生Java代碼的的圖形工具。你可以簡單地通過拖放操作建立類的方法。拖入的方法被編輯的方法(也就是外掛程式)"調用"。我們將讓資料來驅動應用程式的設計。在後面一篇文章中,我們將開發這個GUI。現在我們需要做的是,找到外掛程式將輸入和儲存的重要資料。它通常被稱為應用程式的模型(model)。在設計這個系統的時候,我們需要考慮下面一些內容:

  · 哪些細節資料需要儲存?

  · 這些資料在記憶體中用什麼來表現?POJO、JavaBean還是EJB?

  · 這些資料的儲存格式是怎樣的?資料庫表、XML檔案、屬性檔案還是串列二進位檔案?

  · 輸入資料的方式有哪幾種?用"建立檔案"嚮導還是在文件屬性頁面上使用彈出對話方塊、用編輯器繪製、在文字編輯器中輸入的其它嚮導?

  在我們繼續工作之前必須回答這些問題。不可能有適合所有項目的答案;它完全依賴於你的需求。在我們的例子中,我做出了一些隨意的、可能有問題的決定,如下所示:

  · 一個Java類,它包含類名、程式包、超類(superclass)和實現介面。我們以它為基礎,在後面的文章中添加更多資料。

  · 我將把資料表現為擴充Properties類的類。它建立了編輯器的"文檔類"。

  · 我將使用的格式是屬性檔案,很容易使用Properties類來分析它。

  · 在"建立檔案"嚮導中,我將先尋找資料,接著讓使用者改變屬性視窗或文字編輯器中的資料。這個步驟將在下一篇文章中完成。

   Document(文檔)類

  下一步是編寫文檔類。建立一個新程式包(invokatron.model)和一個新類(InvokatronDocument)。下面是我們的文檔類的開頭:

public class InvokatronDocument
extends Properties
{
public static final String PACKAGE = "package";
public static final String SUPERCLASS = "superclass";
public static final String INTERFACES = "interfaces";
}


  使用Properties類可以更簡單地分析和儲存我們的資料。Getter和 setter不是必須的,但是如果你想要,也可以加上它們。這個類還沒有完成;我們將添加一個介面,在後面的部分中Eclipse需要使用它。

  有了這個類之後,我們要擷取一個屬性就非常簡單了:

String package =document.getProperty(InvokatronDocument.PACKAGE);


   定製嚮導

  請看一看前面的文章中所出現的嚮導。你應該記得,我們可以通過點擊(我們自己添加的)工具條按鈕或者功能表項目來訪問它。圖1是它的介面:


圖1:舊的嚮導


  它只有一個頁面,右上方沒有圖片。我們想輸入更多的資訊,並提供一個很好的圖片。換句話說,我們希望定製這個嚮導。

  我們來分析一下這個嚮導。請開啟InvokatronWizard.java檔案。請注意這個類是如何擴充Wizard並實現INewWizard介面的。你應該理解它裡面的很多方法。為了定製嚮導,我們簡單地調用或重載其中的某些方法。下面是一些重要的方法:

  生命週期方法

  我們應該重載這些方法,把初始化和析構(destruction)代碼插入嚮導中:

  · Constructor(建構函式):嚮導執行個體化的時候、在Eclipse給它傳遞資訊之前調用。嚮導的一般初始化實現。通常你希望調用"美化方法"(後面有描述)並設定對話方塊的預設值。

  · init(IWorkbench workbench, IStructuredSelection editorSelection): Eclipse調用它為嚮導提供工作台的資訊。請重載它,儲存IWorkbench和對象的控制代碼供以後使用。如果它是一個編輯器嚮導而不是新嚮導,我們最好把當前的編輯器選項作為第二個參數。

  · dispose():Eclipse調用它執行清理工作。重載它來清除嚮導使用的資源。

  · finalize():清除代碼,可能使用dispose()代替。

  美化方法

  這些方法都是用於裝飾嚮導表單的。

  · setWindowTitle(String title):設定表單的標題列字串。

  · setDefaultPageImageDescriptor(ImageDescriptor image):用於提供顯示在嚮導的所有頁面右上方的圖片。

  · setTitleBarColor(RGB color):指定標題列用什麼顏色。

  按鈕方法

  這些方法控制著嚮導按鈕的實用性和行為。

  · boolean canFinish():重載它用於指定Finish(完成)按鈕是否啟用(根據嚮導的狀態)。

  · boolean performFinish():重載它來實現嚮導的根本的商務邏輯。如果嚮導沒有完成(錯誤的條件),就返回false。

  · boolean performCancel():重載它,在使用者點擊Cancel(取消)按鈕的時候進行清除操作。如果嚮導不能終止,則返回false。

  · boolean isHelpAvailable():重載它用於指定Help(協助)按鈕是否可視。

  · boolean needsPreviousAndNextButtons():重載它來指定Previous(前一步)和Next(後一步)按鈕是否可視。

  · boolean needsProgressMonitor():重載它來指定進度條組件是否可視。當點擊Finish按鈕調用performFinish()方法的時候,它就會出現。

  頁面方法

  這些方法控制著頁面的外觀。

  · addPages():嚮導顯示的時候調用。重載它給嚮導插入新頁面。

  · createPageControls(Composite pageContainer):Eclipse調用它來執行個體化所有的嚮導頁面(用前面的addPages()方法已經添加的頁面)。重載它給嚮導添加持續可視的表單小組件(除頁面之外的組件)。

  · IWizardPage getStartingPage():重載它來檢測哪個頁面是嚮導的第一個頁面。

  · IWizardPage getNextPage(IWizardPage nextPage):在預設情況下,點擊Next按鈕將進入addPages()所提供的數組中的下一個頁面。你可能希望根據使用者選擇進入不同的頁面。重載它來計算後一個頁面。

  · IWizardPage getPreviousPage(IWizardPage previousPage):與getNextPage()類似,用於計算前一個頁面。

  · int getPageCount():檢索addPages()添加的頁面的數量。在典型情況下,你不必重載它,除非你希望顯示頁面的數量和形式。

  其它有用的方法

  這些都是有用的輔助方法:

  · setDialogSettings(IDialogSettings settings):你可以載入對話方塊的狀態,並通過在init()中調用這個方法來設定這些值。在典型情況下,這些設定可以作為嚮導欄位的預設值。請查看DialogSettings類瞭解更詳細的資訊。

  · IDialogSettings getDialogSettings():當我們需要資料的時候,就調用這個方法來檢索它。在performFinish()的對話方塊的末尾,你再次可以把資料儲存到檔案中。

  · IWizardContainer getContainer():對於檢索Shell、啟動並執行後台線程、重新整理視窗等非常有用。

  嚮導頁面方法

  你已經看到了,嚮導是由一個或多個頁面組成的。這些頁面擴充了WizardPage類,並實現了IWizardPage介面。為了定製單獨的頁面,你必須瞭解很多方法。下面是一些重要的方法:

  · Constructor:用於執行個體化頁面。

  · dispose():重載它用於實現清除代碼。

  · createControl(Composite parent):重載它來給頁面添加控制項。

  · IWizard getWizard():用於擷取父嚮導對象。對於調用getDialogSettings()是有用處的。

  · setTitle(String title):調用它來設定顯示在嚮導標題區中的字串。

  · setDescription(String description):調用它來提供標題下面顯示的常值內容。

  · setImageDescriptor(ImageDescriptor image):調用它來提供頁面右上方出現的圖片(用於代替預設的圖片)。

  · setMessage(String message):調用它來顯示描述字串下方的訊息文本。這些文本是用於警告或提示使用者的。

  · setErrorMessage(String error):調用它來高亮度顯示描述字串下方的訊息文本。它一般意味著嚮導不能繼續,除非錯誤被修正。

  · setPageComplete(boolean complete):如果為true,Next按鈕就可視。

  · performHelp():重載它來提供內容敏感的協助資訊。當點擊Help按鈕的時候嚮導會調用它。

  編寫嚮導的代碼

  有了這些方法之後,我們就能夠開發出具有極大的靈活性的嚮導了。我們現在修改以前建立的Invokatron嚮導,給它添加一個頁面來請求使用者輸入初始的文檔資料。我們還給嚮導添加了一個圖片。新代碼是粗體的:

public class InvokatronWizard extends Wizard
implements INewWizard {
 private InvokatronWizardPage page;
 private InvokatronWizardPage2 page2;
 private ISelection selection;

 public InvokatronWizard() {
  super();
  setNeedsProgressMonitor(true);
  ImageDescriptor image =AbstractUIPlugin.imageDescriptorFromPlugin("Invokatron", "icons/InvokatronIcon32.GIF");
  setDefaultPageImageDescriptor(image);
 }

 public void init(IWorkbench workbench,IStructuredSelection selection) {
  this.selection = selection;
 }


  在建構函式中,我們開啟了進度條,並設定了嚮導的圖片。你可以下載並儲存下面的圖片:


  請把這個圖片儲存在Invokatron/icons檔案夾之下。為了更容易載入這個圖片,我們使用了便捷的AbstractUIPlugin.imageDescriptorFromPlugin()方法。

  請注意:你應該知道,儘管這個嚮導是INewWizard類型的,但是並非所有的嚮導都是用於建立新文檔的。你可以參考其它一些資料來瞭解如何建立"獨立的"嚮導的資訊。

  下面是addPages()方法:

public void addPages() {
 page=new InvokatronWizardPage(selection);
 addPage(page);
 page2 = new InvokatronWizardPage2(selection);
 addPage(page2);
}


  在這個方法中,我們添加了一個新頁面(InvokatronWizardPage2),我們在後面編輯它。下面是使用者點擊嚮導的"完成"按鈕的時候執行的一些方法:

public boolean performFinish() {
 //首先把所有的頁面資料儲存在變數中
 final String containerName = page.getContainerName();
 final String fileName =page.getFileName();
 final InvokatronDocument properties = new InvokatronDocument();
 properties.setProperty(InvokatronDocument.PACKAGE,page2.getPackage());
 properties.setProperty(InvokatronDocument.SUPERCLASS,page2.getSuperclass());
 properties.setProperty(InvokatronDocument.INTERFACES,page2.getInterfaces());

 //現在調用完成(finish)方法
 IRunnableWithProgress op =new IRunnableWithProgress() {
  public void run(IProgressMonitor monitor)
  throws InvocationTargetException {
   try {
    doFinish(containerName, fileName,properties,monitor);
   } catch (CoreException e) {
    throw new InvocationTargetException(e);
   } finally {
    monitor.done();
   }
  }
 };
 try {
  getContainer().run(true, false, op);
 } catch (InterruptedException e) {
  return false;
 } catch (InvocationTargetException e) {
  Throwable realException =e.getTargetException();
  MessageDialog.openError(getShell(),"Error",realException.getMessage());
  return false;
 }
 return true;
}


  為了儲存資料,我們必須做一個後台事務。該事務是由嚮導的容器(Eclipse工作台)來執行的,並且必須實現IRunnableWithProgress介面,包含(唯一)一個run()方法。傳遞進來的IProgressMonitor允許我們報告事務的進度。實際的資料儲存工作在一個輔助方法(doFinish())中進行:

private void doFinish(String containerName,String fileName, Properties properties,
IProgressMonitor monitor)
throws CoreException {
 // 建立一個樣本檔案
 monitor.beginTask("Creating " + fileName, 2);
 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
 IResource resource = root.findMember(new Path(containerName));
 if (!resource.exists() || !(resource instanceof IContainer)) {
 throwCoreException("Container \"" + containerName + "\" does not exist.");
}
IContainer container =(IContainer)resource;
final IFile iFile = container.getFile(new Path(fileName));
final File file =iFile.getLocation().toFile();
try {
 OutputStream os = new FileOutputStream(file, false);
 properties.store(os, null);
 os.close();
} catch (IOException e) {
 e.printStackTrace();
 throwCoreException("Error writing to file " + file.toString());
}

//確保項目已經重新整理了,該檔案在Eclipse API 之外建立
container.refreshLocal(IResource.DEPTH_INFINITE, monitor);

monitor.worked(1);

monitor.setTaskName("Opening file for editing...");
getShell().getDisplay().asyncExec(new Runnable() {
 public void run() {
  IWorkbenchPage page =PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
  try {
   IDE.openEditor(page,iFile,true);
  } catch (PartInitException e) {
  }
 }
});
monitor.worked(1);
}


  我們還做了很多工作:

  · 我們檢索了自己希望儲存檔案的位置(用Eclipse的IFile類)。

  · 我們還擷取了該File。

  · 我們把屬性儲存到了這個位置。

  · 接著我們讓Eclipse工作台重新整理項目,這樣就可以顯示該檔案了。

  · 我們最後調度了一個事務,它在以後執行。這個事務包括在編輯器中開啟那個新檔案。

  · 在整個過程中,我們通過調用IProgressMonitor對象(它是作為參數傳遞進來的)的方法來提示使用者目前的進展情況。
 
  最後一個方法是一個輔助的方法,當該檔案儲存失敗的時候,它在嚮導中顯示錯誤資訊:

private void throwCoreException(String message) throws CoreException {
 IStatus status =new Status(IStatus.ERROR,"Invokatron",IStatus.OK,message,null);
 throw new CoreException(status);
}
}


  嚮導可以捕獲CoreException異常,接著可以把它所包含的Status對象顯示給使用者看。嚮導不會被關閉。

  編寫新的嚮導頁面的代碼

  下一步,我們編寫InvokatronWizardPage2。它的整個類都是全新的:

public class InvokatronWizardPage2 extends WizardPage {
 private Text packageText;
 private Text superclassText;
 private Text interfacesText;

 private ISelection selection;

 public InvokatronWizardPage2(ISelection selection) {
  super("wizardPage2");
  setTitle("Invokatron Wizard");
  setDescription("This wizard creates a new"+" file with *.invokatron extension.");
  this.selection = selection;
 }

 private void updateStatus(String message) {
  setErrorMessage(message);
  setPageComplete(message == null);
 }

 public String getPackage() {
  return packageText.getText();
 }
 public String getSuperclass() {
  return superclassText.getText();
 }
 public String getInterfaces() {
  return interfacesText.getText();
 }


  上面的建構函式設定了頁面的標題(在標題列下方高亮度顯示)和描述(在頁面標題的下方顯示)。我們還有一些輔助方法。 updateStatus處理頁面特定的錯誤資訊的顯示。如果沒有錯誤資訊,就意味著頁面完成了;因此,"下一步"按鈕就可以使用了。還有資料欄位內容的getter(擷取)方法。下面是createControl()方法,它建立了頁面的所有可視化組件:

public void createControl(Composite parent) {
 Composite controls =new Composite(parent, SWT.NULL);
 GridLayout layout = new GridLayout();
 controls.setLayout(layout);
 layout.numColumns = 3;
 layout.verticalSpacing = 9;

 Label label =new Label(controls, SWT.NULL);
 label.setText("&Package:");

 packageText = new Text(controls,SWT.BORDER | SWT.SINGLE);
 GridData gd = new GridData(GridData.FILL_HORIZONTAL);
 packageText.setLayoutData(gd);
 packageText.addModifyListener(
  new ModifyListener() {
   public void modifyText(ModifyEvent e) {
    dialogChanged();
   }
  });

 label = new Label(controls, SWT.NULL);
 label.setText("Blank = default package");

 label = new Label(controls, SWT.NULL);
 label.setText("&Superclass:");

 superclassText = new Text(controls,SWT.BORDER | SWT.SINGLE);
 gd = new GridData(GridData.FILL_HORIZONTAL);
 superclassText.setLayoutData(gd);
 superclassText.addModifyListener(new ModifyListener() {
  public void modifyText(ModifyEvent e) {
   dialogChanged();
  }
 });

 label = new Label(controls, SWT.NULL);
 label.setText("Blank = Object");

 label = new Label(controls, SWT.NULL);
 label.setText("&Interfaces:");

 interfacesText = new Text(controls,SWT.BORDER | SWT.SINGLE);
 gd = new GridData(GridData.FILL_HORIZONTAL);
 interfacesText.setLayoutData(gd);
 interfacesText.addModifyListener(
  new ModifyListener() {
   public void modifyText(ModifyEvent e) {
    dialogChanged();
   }
  });

 label = new Label(controls, SWT.NULL);
 label.setText("Separated by ','");

 dialogChanged();
 setControl(controls);
}


  為了編寫這段代碼,你必須瞭解SWT(請你自己查看一些這方面的資料)。基本上,這個方法建立了標籤和欄位,並把它們放置到網格布局上。欄位發生改變的時候,就調用dialogChanged()來驗證它的資料:

private void dialogChanged() {
 String aPackage = getPackage();
 String aSuperclass = getSuperclass();
 String interfaces = getInterfaces();

 String status = new PackageValidator().isValid(aPackage);
 if(status != null) {updateStatus(status);
  return;
 }

 status = new SuperclassValidator().isValid(aSuperclass);
 if(status != null) {updateStatus(status);
  return;
}

status = new InterfacesValidator().isValid(interfaces);
if(status != null) {updateStatus(status);
 return;
}

updateStatus(null);
}

}


  這個工作是在三個工具類--PackageValidator、SuperclassValidator和 InterfacesValidator的協助下完成的。接下來我們編寫這些類。

  驗證類

  驗證可以在外掛程式的使用者輸入資料的任何部分中進行。因此,把驗證代碼放入可重複使用的類中是有意義的,這樣就不用把它複製到多個位置。下面是一個驗證類的例子。

public class InterfacesValidator implements ICellEditorValidator
{
 public String isValid(Object value)
 {
  if( !( value instanceof String) )
   return null;

  String interfaces = ((String)value).trim();
  if( interfaces.equals(""))
   return null;

  String[] interfaceArray = interfaces.split(",");
  for (int i = 0; i < interfaceArray.length; i++)
  {
   IStatus status = JavaConventions.validateJavaTypeName(interfaceArray[i]);
   if (status.getCode() != IStatus.OK)
    return "Validation of interface " + interfaceArray[i] + ": " + status.getMessage();
  }
  return null;
 }
}


  其它的驗證類與它非常類似。

  Eclipse類庫中的另外一個極好的類是JavaConventions,它為我們驗證資料!它包含了很多驗證方法,例如:

  · validateJavaTypeName() 檢查類和介面的名稱。

  · validatePackageName() 檢查程式包的名稱。

  · validateFieldName() 檢查資料成員的名稱。

  · validateMethodName() 檢查方法的名稱。

  · validateIdentifierName() 檢查變數的名稱。

  現在我們不需要ICellEditorValidator介面,但是在以後的文章中,我們是需要它的。

   結果

  到目前為止,我們擁有了一個可以工作的嚮導,它擁有一張圖片和兩個頁面,第二個頁面建立了原來的Invokatron文檔。圖2顯示了結果:


圖2:定製的嚮導
   閃亮的發明

  我們可以看到,通常是資料驅動應用程式的。外表(Presentation)也是很重要的。醜陋的發明難以出售,但是閃亮的發明可能容易出售。但是資料是我們這些程式員實現的非常本質的東西。

  在本文中,我們首先決定了自己將處理的資料。然後,我們以定製嚮導的方式來擷取這些資料。下一篇文章將繼續講解顯示的問題,包括定製的編輯器和屬性頁面。



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.