次にこのウィザードを拡張して、複数のページを持つウィザードを作成してみますが、その前にウィザードのページ遷移の制御についても触れておきましょう。ウィザードの下部には[前へ][次へ][終了][キャンセル]の4つのボタンがあり、addPage()メソッドで追加された順に遷移するわけですが、このうち、[次へ]と[終了]ボタンについては、ウィザードページの入力内容に不備がある場合は[次へ]を無効に、ウィザード全体の入力内容に一部でも不足がありウィザードを完了できない場合は[終了]をそれぞれ無効にするよう実装すべきです。
 [次へ]ボタンはウィザードページのisPageComplete()メソッドがtrueを返す場合有効に、falseを返す場合は無効になります。ウィザードページを実装する際はウィジェットにリスナを追加しておき、変更イベントを検知して入力チェックを行うべきです。入力内容に問題がある場合は以下のようにしてエラーメッセージのセットを行い、isPageCompleteプロパティにfalseをセットします。 
        
           
            | private Text text;public void createControl(Composite parent){
 Composite composite = new Composite(parent, SWT.NULL);
 ...
 this.text = new Text(composite, SWT.BORDER);
 this.text.addModifyLIstener(){
 setPageComplete(validatePage()); // テキストが変更されたら入力チェック
 }
 }
 private boolean validatePage(){
 if(this.text.getText().length()==0){ // 項目が空だったらエラー
 setMessage("値が入力されていません。", ERROR);
 return false;
 }
 return true;
 }
 | 
        
 [終了]ボタンに関してはウィザードのcanFinish()メソッドがtrueの場合有効に、falseの場合無効になります。canFinish()メソッドのデフォルトの実装はすべてのウィザードページのcanPageComplete()メソッドがtrueを返す場合true、1つでもfalseを返す場合はfalse となるよう実装されています。
 ではいよいよ複数のウィザードページを持つウィザードの実装に取り掛かります。最初に作成したウィザードはファイル名を入力するとXML宣言を含んだファイルを生成してくれるだけでしたが、さすがにこれではウィザードのありがたみがあまりありません。ウィザードページを1つ追加して、DTDを基にXML ファイルのひな型を生成できるようにしてみましょう。DTDのパースにはhttp://www.wutka.com/dtdparser.htmlで公開されているDTDParser というライブラリを使用することにします。Webサイトからdtdparser-1.21.zip をダウンロードし、アーカイブに含まれているdtdparser121.jarをプラグインプロジェクトのクラスパスに追加します。また、マニフェストエディタの[ランタイム]タブおよび[ビルド]タブにも追加しておきます。
 図5 ランタイムタブのクラスパスにライブラリを追加
図5 ランタイムタブのクラスパスにライブラリを追加 図6 ビルドタブのバイナリー・ビルドでライブラリをチェック
図6 ビルドタブのバイナリー・ビルドでライブラリをチェック 今回はWizardPageを直接継承してユーザー・インターフェイスも自前で実装していきます。
        
           
            | package jp.sf.amateras.xmlwizard;import java.net.URL;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.wizard.WizardPage;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
 import com.wutka.dtd.DTD;
 import com.wutka.dtd.DTDElement;
 import com.wutka.dtd.DTDParser;
 public class XMLDTDWizardPage extends WizardPage {
 private Text url;
 private Button loadDTD;
 private Combo elements;
 public XMLDTDWizardPage(String pageName){
 super(pageName);
 }
 public void createControl(Composite parent) {
 Composite composite = new Composite(parent, 
              SWT.NULL);
 composite.setLayout(new GridLayout(2, false));
 composite.setLayoutData(new GridData(GridData.FILL_BOTH));
 Label label = new Label(composite, SWT.NULL);
 label.setText("DTDの場所:");
 this.url = new Text(composite, SWT.BORDER);
 this.url.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
 this.loadDTD = new Button(composite, SWT.PUSH);
 this.loadDTD.setText("DTDの読み込み");
 GridData gd = new GridData();
 gd.horizontalSpan = 2;
 this.loadDTD.setLayoutData(gd);
 this.loadDTD.addSelectionListener(new SelectionAdapter(){
 public void widgetSelected(SelectionEvent 
              evt){
 this.loadDTD();
 }
 });
 label = new Label(composite, SWT.NULL);
 label.setText("ルート要素:");
 this.elements = new Combo(composite, SWT.READ_ONLY);
 setControl(composite);
 }
 /** DTDの読み込み */
 private void loadDTD(){
 try {
 String value = this.url.getText();
 if(value.indexOf(":/")<0){
 value = "file:" 
              + value;
 }
 // コンボボックスの内容をクリア.elements.removeAll();
 // DTDをパース
 DTDParser parser = new DTDParser(new 
              URL(value));
 DTD dtd = parser.parse();
 Object[] items = dtd.getItems();
 // コンボボックスに要素名を追加
 for(int i=0;i<items.length;i++){
 if(items[i] instanceof 
              DTDElement){
 this.elements.add(((DTDElement)items[i]).getName());
 }
 }
 } catch(Exception ex){
 MessageDialog.openError(getControl().getShell(),"エラー",ex.toString());
 ex.printStackTrace();
 }
 }
 /** DTDのURLを取得 */
 public String getDTDURL(){
 return this.url.getText();
 }
 /** 選択されたルート要素名を取得 */
 public String getRootElement(){
 return this.elements.getText();
 }
 }
 | 
        
 これに伴って最初に作成したXMLNewWizardPageを以下のように修正します(追加した部分を赤字で示しています)。DTD情報入力ページで入力された情報を受け取るためのフィールドおよびセッターメソッドを追加し、getInitialContents()メソッド内で作成するファイルの内容を修正しています。
        
           
            | public class XMLNewWizardPage extends 
              WizardNewFileCreationPage {private String dtdURL =null;
 private String rootElement = null;
 ...
 public void setDTDURL(String dtdURL){
 this.dtdURL = dtdURL;
 }
 public void setRootElement(String rootElement){
 this.rootElement = rootElement;
 }
 /** 新規作成するファイルの内容を作成します */
 protected InputStream getInitialContents() {
 try {
 // 文字コードを取得
 String projectName = getContainerFullPath().segment(0);
 IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
 String charset = project.getDefaultCharset();
 
 // 作成するXMLファイルの内容を作成
 StringBuffer sb = new StringBuffer();
 sb.append("<?xml version=\"1.0\"");
 if(charset!=null){
 sb.append(" encoding=\"" 
              + charset + "\"");
 }
 sb.append("?>\n");
 
 if(this.rootElement!=null 
              && this.rootElement.length()!=0){
 sb.append("<!DOCTYPE 
              ").append(this.rootElement).append(" SYSTEM '").append(this.dtdURL).append("'>\n");
 sb.append("<").append(this.rootElement).append(">\n");
 sb.append("</").append(this.rootElement).append(">\n");
 }
 return new ByteArrayInputStream(sb.toString().getBytes());
 } catch(Exception ex){
 ex.printStackTrace();
 }
 return null;
 }
 ...
 }
 | 
        
 最後に作成したウィザードページをウィザードに追加しましょう。XMLNewWizard を以下のように修正します(追加した部分を赤字で示しています)。
        
           
            | public class XMLNewWizard extends Wizard 
              implements INewWizard {private IStructuredSelection selection;
 private XMLNewWizardPage page1;
 private XMLDTDWizardPage page2;
 public XMLNewWizard() {
 super();
 setNeedsProgressMonitor(true);
 setWindowTitle("新規XMLファイル");
 }
 public void init(IWorkbench workbench, IStructuredSelection 
              selection) {
 this.selection = selection;
 }
 /** ウィザードページの作成 */
 public void addPages() {
 this.page1 = new XMLNewWizardPage("page1", 
              this.selection);
 this.page1.setFileName("newfile.xml");
 this.page1.setTitle("新規XMLファイル");
 this.page1.setDescription("新規XMLファイルを作成します。");
 
 this.page2 = new XMLDTDWizardPage("page2");
 this.page2.setTitle("DTD");
 this.page2.setDescription("DTDの情報を入力します。");
 
 addPage(this.page1);
 addPage(this.page2);
 }
 /** 完了ボタンがクリックされたときの処理 */
 public boolean performFinish() {
 this.page1.setDTDURL(page2.getDTDURL());
 this.page1.setRootElement(page2.getRootElement());
 IFile file = this.page1.createNewFile();
 if(file==null){
 return false;
 }
 try {
 IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
 IDE.openEditor(page, file, true);
 } catch(PartInitException ex){
 ex.printStackTrace();
 return false;
 }
 return true;
 }
 }
 | 
        
 以上で実装は完了です。ランタイムワークベンチを起動し、先ほどと同様、XMLファイルの作成ウィザードを起動してみてください。以下のようなウィザードページが追加されていることと思います。
 図7 追加したDTD情報入力用のウィザードページ
図7 追加したDTD情報入力用のウィザードページ このウィザードを実行することで、以下のXMLが生成されます。 
        
           
            | <?xml version="1.0" encoding="MS932"?><!DOCTYPE struts-config SYSTEM 'http://struts.apache.org/dtds/struts-config_1_2.dtd'>
 <struts-config>
 </struts-config>
 | 
        
 今回作成したウィザードのソースコードはここからダウンロードすることができます。ウィザードは単純にファイルやプロジェクトを作成するだけでなく、関連する複数のファイルを一度に作成したり、ライブラリをプロジェクトにクラスパスに追加する、といった利用法も考えられます。またEclipseRCPによるリッチクライアント・アプリケーションにおいても情報の入力などに使用することができるでしょう。ぜひ、ウィザードを活用してプラグインやRCPアプリケーションの開発に役立ててください。