首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

SWT/JFace Wizard 精靈對話框開發實戰:飯店訂房系統完整範例

     編輯:Java 相關知識

Wizard 框架架構概覽

JFace Wizard 框架 (org.eclipse.jface.wizard) 將複雜的多步驟任務封裝為標準化流程:

  • Wizard:核心控制器,管理頁面順序、資料模型、完成/取消邏輯。
  • WizardPage:單一步驟頁面,負責 UI 建立 (createControl)、資料驗證 (setPageComplete)、錯誤訊息顯示 (setErrorMessage)。
  • WizardDialog:顯示精靈的對話框容器,提供下一步/上一步/完成/取消按鈕。

完整案例:飯店訂房精靈

包含三個頁面:FrontPage (房型/日期)、CustomerInfoPage (客戶資料驗證)、PaymentInfoPage (信用卡資訊)。資料模型 ReservationData 集中管理,支援對話框設定持久化 (DialogSettings) 與完成時進度監控 (IRunnableWithProgress)。

1. 主程式進入點與精靈啟動

package swt_jface.demo12;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

public class HotelReservation extends ApplicationWindow {
    @Override
    protected Control createContents(Composite parent) {
        Button button = new Button(parent, SWT.PUSH);
        button.setText("Make a reservation");
        button.addListener(SWT.Selection, e -> {
            ReservationWizard wizard = new ReservationWizard();
            WizardDialog dialog = new WizardDialog(getShell(), wizard);
            dialog.setBlockOnOpen(true);
            int returnCode = dialog.open();
            if (returnCode == Dialog.OK) System.out.println(wizard.data);
            else System.out.println("Cancelled");
        });
        return button;
    }

    public HotelReservation(Shell parentShell) { super(parentShell); }

    public static void main(String[] args) {
        HotelReservation reservation = new HotelReservation(null);
        reservation.setBlockOnOpen(true);
        reservation.open();
    }
}

2. 資料模型

class ReservationData {
    Date arrivalDate, departureDate;
    int roomType; // 0: Standard, 1: Deluxe
    String customerName, customerPhone, customerEmail, customerAddress;
    int creditCardType; // 0: Amex, 1: Master, 2: Visa
    String creditCardNumber, creditCardExpiration;

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("* HOTEL ROOM RESERVATION DETAILS *\n");
        sb.append("Arrival date:\t" + arrivalDate + "\n");
        sb.append("Departure date:\t" + departureDate + "\n");
        sb.append("Room type:\t" + (roomType == 0 ? "Standard ($198)" : "Deluxe ($298)") + "\n");
        sb.append("Customer name:\t" + customerName + "\n");
        sb.append("Customer email:\t" + customerEmail + "\n");
        sb.append("Credit card no.:\t" + creditCardNumber + "\n");
        return sb.toString();
    }
}

3. 精靈主類別

package swt_jface.demo12;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.DialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.wizard.Wizard;

public class ReservationWizard extends Wizard {
    static final String SETTING_FILE = "C:/userInfo.xml";
    static final String KEY_NAME = "customer-name";
    static final String KEY_EMAIL = "customer-email";
    static final String KEY_PHONE = "customer-phone";
    static final String KEY_ADDRESS = "customer-address";

    ReservationData data = new ReservationData();

    public ReservationWizard() {
        setWindowTitle("Hotel room reservation wizard");
        setNeedsProgressMonitor(true); // 啟用完成時進度監控
        setDefaultPageImageDescriptor(ImageDescriptor.createFromFile(null, "C:/icons/hotel.gif"));
        
        // 載入持久化設定
        DialogSettings settings = new DialogSettings("userInfo");
        try { settings.load(SETTING_FILE); } catch (IOException e) { e.printStackTrace(); }
        setDialogSettings(settings);
    }

    @Override
    public void addPages() {
        addPage(new FrontPage());
        addPage(new CustomerInfoPage());
        addPage(new PaymentInfoPage());
    }

    @Override
    public boolean performFinish() {
        // 1. 儲存設定
        if (getDialogSettings() != null) {
            getDialogSettings().put(KEY_NAME, data.customerName);
            getDialogSettings().put(KEY_PHONE, data.customerPhone);
            getDialogSettings().put(KEY_EMAIL, data.customerEmail);
            getDialogSettings().put(KEY_ADDRESS, data.customerAddress);
            try { getDialogSettings().save(SETTING_FILE); } catch (IOException e) { e.printStackTrace(); }
        }

        // 2. 執行耗時操作 (模擬儲存至資料庫/遠端)
        try {
            getContainer().run(true, true, new IRunnableWithProgress() {
                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    monitor.beginTask("Store data", 100);
                    monitor.worked(40);
                    System.out.println(data); // 實際業務邏輯
                    Thread.sleep(2000); // 模擬耗時
                    monitor.done();
                }
            });
        } catch (Exception e) { e.printStackTrace(); }
        return true;
    }

    @Override
    public boolean performCancel() {
        return MessageDialog.openConfirm(getShell(), "Confirmation", "Are you sure to cancel the task?");
    }
}

4. 頁面一:FrontPage (日期與房型選擇)

使用 Combo 選擇年/月/日,驗證入住日期早於退房日期。監聽所有 Combo 選擇事件統一驗證。

package swt_jface.demo12;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;

public class FrontPage extends WizardPage {
    Combo comboRoomTypes, comboArrivalYear, comboArrivalMonth, comboArrivalDay;
    Combo comboDepartureYear, comboDepartureMonth, comboDepartureDay;

    public FrontPage() {
        super("FrontPage");
        setTitle("Your reservation information");
        setDescription("Select the type of room and your arrival date & departure date");
    }

    @Override
    public void createControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new GridLayout(2, false));

        // 到達日期
        new Label(composite, SWT.NULL).setText("Arrival date: ");
        Composite compArr = new Composite(composite, SWT.NULL);
        compArr.setLayout(new RowLayout());
        String[] months = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
        Calendar cal = new GregorianCalendar();
        data.arrivalDate = cal.getTime(); // 初始化模型

        comboArrivalMonth = new Combo(compArr, SWT.BORDER | SWT.READ_ONLY);
        for(String m: months) comboArrivalMonth.add(m);
        comboArrivalMonth.select(cal.get(Calendar.MONTH));

        comboArrivalDay = new Combo(compArr, SWT.BORDER | SWT.READ_ONLY);
        for(int i=0;i<31;i++) comboArrivalDay.add(""+(i+1));
        comboArrivalDay.select(cal.get(Calendar.DAY_OF_MONTH)-1);

        comboArrivalYear = new Combo(compArr, SWT.BORDER | SWT.READ_ONLY);
        for(int i=2004;i<2010;i++) comboArrivalYear.add(""+i);
        comboArrivalYear.select(cal.get(Calendar.YEAR)-2004);

        cal.add(Calendar.DATE, 1);
        data.departureDate = cal.getTime();

        // 離開日期
        new Label(composite, SWT.NULL).setText("Departure date: ");
        Composite compDep = new Composite(composite, SWT.NULL);
        compDep.setLayout(new RowLayout());
        comboDepartureMonth = new Combo(compDep, SWT.BORDER | SWT.READ_ONLY);
        for(String m: months) comboDepartureMonth.add(m);
        comboDepartureMonth.select(cal.get(Calendar.MONTH));
        comboDepartureDay = new Combo(compDep, SWT.BORDER | SWT.READ_ONLY);
        for(int i=0;i<31;i++) comboDepartureDay.add(""+(i+1));
        comboDepartureDay.select(cal.get(Calendar.DAY_OF_MONTH)-1);
        comboDepartureYear = new Combo(compDep, SWT.BORDER | SWT.READ_ONLY);
        for(int i=2004;i<2010;i++) comboDepartureYear.add(""+i);
        comboDepartureYear.select(cal.get(Calendar.YEAR)-2004);

        // 分隔線
        Label line = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
        line.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL));
        ((GridData)line.getLayoutData()).horizontalSpan = 2;

        // 房型
        new Label(composite, SWT.NULL).setText("Room type: ");
        comboRoomTypes = new Combo(composite, SWT.BORDER | SWT.READ_ONLY);
        comboRoomTypes.add("Standard room (rate: $198)");
        comboRoomTypes.add("Deluxe room (rate: $298)");
        comboRoomTypes.select(0);

        // 統一日期驗證監聽器
        Listener dateListener = e -> {
            int aDay = comboArrivalDay.getSelectionIndex() + 1;
            int aMon = comboArrivalMonth.getSelectionIndex();
            int aYear = comboArrivalYear.getSelectionIndex() + 2004;
            int dDay = comboDepartureDay.getSelectionIndex() + 1;
            int dMon = comboDepartureMonth.getSelectionIndex();
            int dYear = comboDepartureYear.getSelectionIndex() + 2004;
            setDates(aDay, aMon, aYear, dDay, dMon, dYear);
        };
        comboArrivalDay.addListener(SWT.Selection, dateListener);
        comboArrivalMonth.addListener(SWT.Selection, dateListener);
        comboArrivalYear.addListener(SWT.Selection, dateListener);
        comboDepartureDay.addListener(SWT.Selection, dateListener);
        comboDepartureMonth.addListener(SWT.Selection, dateListener);
        comboDepartureYear.addListener(SWT.Selection, dateListener);

        comboRoomTypes.addListener(SWT.Selection, e -> data.roomType = comboRoomTypes.getSelectionIndex());

        setControl(composite);
    }

    private void setDates(int aDay, int aMon, int aYear, int dDay, int dMon, int dYear) {
        Calendar cal = new GregorianCalendar();
        cal.set(aYear, aMon, aDay);
        Date arrival = cal.getTime();
        cal.set(dYear, dMon, dDay);
        Date departure = cal.getTime();

        if (!arrival.before(departure)) {
            setErrorMessage("The arrival date is not before the departure date");
            setPageComplete(false);
        } else {
            setErrorMessage(null);
            setPageComplete(true);
            data.arrivalDate = arrival;
            data.departureDate = departure;
        }
    }
}

5. 頁面二:CustomerInfoPage (客戶資料驗證與持久化)

使用 ModifyListener 即時驗證 Email 格式 (@ 必須存在),所有欄位填寫完畢才啟用「下一步」(setPageComplete(true))。載入/儲存 DialogSettings。

package swt_jface.demo12;

import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;

public class CustomerInfoPage extends WizardPage {
    Text textName, textPhone, textEmail, textAddress;

    public CustomerInfoPage() {
        super("CustomerInfo");
        setTitle("Customer Information");
        setPageComplete(false);
    }

    @Override
    public void createControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new GridLayout(2, false));

        new Label(composite, SWT.NULL).setText("Full name: ");
        textName = new Text(composite, SWT.SINGLE | SWT.BORDER);
        textName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        new Label(composite, SWT.NULL).setText("Phone Number: ");
        textPhone = new Text(composite, SWT.SINGLE | SWT.BORDER);
        textPhone.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        new Label(composite, SWT.NULL).setText("Email address: ");
        textEmail = new Text(composite, SWT.SINGLE | SWT.BORDER);
        textEmail.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        new Label(composite, SWT.NULL).setText("Address: ");
        textAddress = new Text(composite, SWT.MULTI | SWT.BORDER);
        textAddress.setText("\r\n\r\n\r\n"); // 預留高度
        textAddress.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        Listener listener = e -> {
            if (!(e.widget instanceof Text)) return;
            String s = ((Text)e.widget).getText();
            if (e.widget == textName) data.customerName = s;
            else if (e.widget == textPhone) data.customerPhone = s;
            else if (e.widget == textEmail) {
                if (s.indexOf('@') < 0) { setErrorMessage("Invalid email: " + s); data.customerEmail = null; }
                else { setErrorMessage(null); data.customerEmail = s; }
            } else if (e.widget == textAddress) data.customerAddress = s;

            // 檢查是否全部填寫
            boolean complete = data.customerName != null && data.customerPhone != null 
                            && data.customerEmail != null && data.customerAddress != null;
            setPageComplete(complete);
        };
        textName.addListener(SWT.Modify, listener);
        textPhone.addListener(SWT.Modify, listener);
        textEmail.addListener(SWT.Modify, listener);
        textAddress.addListener(SWT.Modify, listener);

        // 載入歷史設定
        if (getDialogSettings() != null && validDialogSettings()) {
            textName.setText(getDialogSettings().get(ReservationWizard.KEY_NAME));
            textPhone.setText(getDialogSettings().get(ReservationWizard.KEY_PHONE));
            textEmail.setText(getDialogSettings().get(ReservationWizard.KEY_EMAIL));
            textAddress.setText(getDialogSettings().get(ReservationWizard.KEY_ADDRESS));
        }
        setControl(composite);
    }

    private boolean validDialogSettings() {
        return getDialogSettings().get(ReservationWizard.KEY_NAME) != null
            && getDialogSettings().get(ReservationWizard.KEY_ADDRESS) != null
            && getDialogSettings().get(ReservationWizard.KEY_EMAIL) != null
            && getDialogSettings().get(ReservationWizard.KEY_PHONE) != null;
    }
}

6. 頁面三:PaymentInfoPage (信用卡資訊驗證)

驗證有效期格式 (MM/YY),欄位皆填寫正確才啟用完成。

package swt_jface.demo12;

import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;

public class PaymentInfoPage extends WizardPage {
    Combo comboCreditCardTypes;
    Text textCreditCardNumber, textCreditCardExpiration;

    public PaymentInfoPage() {
        super("PaymentInfo");
        setTitle("Payment information");
        setDescription("Please enter your credit card details");
        setPageComplete(false);
    }

    @Override
    public void createControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new GridLayout(2, false));

        new Label(composite, SWT.NULL).setText("Credit card type: ");
        comboCreditCardTypes = new Combo(composite, SWT.READ_ONLY | SWT.BORDER);
        comboCreditCardTypes.add("American Express");
        comboCreditCardTypes.add("Master Card");
        comboCreditCardTypes.add("Visa");
        comboCreditCardTypes.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        new Label(composite, SWT.NULL).setText("Credit card number: ");
        textCreditCardNumber = new Text(composite, SWT.SINGLE | SWT.BORDER);
        textCreditCardNumber.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        new Label(composite, SWT.NULL).setText("Expiration (MM/YY)");
        textCreditCardExpiration = new Text(composite, SWT.SINGLE | SWT.BORDER);
        textCreditCardExpiration.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        comboCreditCardTypes.addListener(SWT.Selection, e -> {
            data.creditCardType = comboCreditCardTypes.getSelectionIndex();
            checkComplete();
        });
        textCreditCardNumber.addListener(SWT.Modify, e -> {
            data.creditCardNumber = textCreditCardNumber.getText();
            checkComplete();
        });
        textCreditCardExpiration.addListener(SWT.Modify, e -> {
            String t = textCreditCardExpiration.getText().trim();
            if (t.length() == 5 && t.charAt(2) == '/') {
                data.creditCardExpiration = t; setErrorMessage(null);
            } else {
                data.creditCardExpiration = null; setErrorMessage("Invalid expiration: " + t);
            }
            checkComplete();
        });
        setControl(composite);
    }

    private void checkComplete() {
        setPageComplete(data.creditCardNumber != null && data.creditCardExpiration != null);
    }
}

核心設計模式總結

  1. 資料模型集中化:ReservationData 作為單一真實來源,所有 Page 直接操作 wizard.data,避免頁面間傳值耦合。
  2. 即時驗證與頁面完成狀態:使用 ModifyListener/SelectionListener 即時更新模型並呼叫 setPageComplete(boolean),Wizard 會自動啟用/禁用「下一步」/「完成」按鈕。
  3. 錯誤訊息顯示:setErrorMessage(String) 會在頁面頂部顯示紅字提示,setMessage(String) 顯示一般資訊。
  4. 持久化設定:DialogSettings + XML 檔案實現使用者偏好記憶 (姓名、Email 等),Wizard 建構子載入,performFinish 儲存。
  5. 長時間操作與進度回報:setNeedsProgressMonitor(true) + performFinish 中使用 getContainer().run(IRunnableWithProgress),自動顯示進度對話框支援取消/暫停。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved