向導在今天的桌面應用中非常常用。向導應該是個什么樣子呢?相信你應該很清楚,因為你使用過很多的向導。也許你使用過一些安裝程序向導或是一些程序的配置向導。這篇文章,我們會創建一個簡單的向導框架。
一個向導包括很多Panel,每個Panel里面包含用戶的配置組件或是文本域或是選擇框等。用戶點擊”Next”或是”Back”按鈕,在各個Panel之間切換,輸入需要的信息。注意的是,當最后一個Panel是”Next”按鈕要變成”Finish”按鈕,并且再次按下的時候向導關閉。在向導關閉的時,發起向導的類要得到向導Panel中所有的數據。在任何情況下,用戶可以點擊”Cancel”按鈕關閉向導并丟棄前面所填的所有數據。
看上去很簡單是嗎?對的,但是有些設計細節我們需要考慮。
第一,向導中的每個Panel不是都需要訪問的,換句話說,如果向導包含1到5個Panel,點擊”Next”按鈕可以從第一個Panel依次到第五個Panel,但是有時候可能由于用戶的選項直接從第一個Panel跳到第五個Panel. 而且還有的情況是,假設向導中需要連接到遠程服務器或是遠程數據庫,如果連接不上的,那么就不能到達下一個Panel。這樣向導中的Panel就像是樹形,你從樹的根開始,通過不同的分支到達葉子節點,這時”Next”按鈕變成”Finish”按鈕。
第二,有些時候Next按鈕和Back按鈕需要禁用。比如,第一個Panel出現的時候,back按鈕應該禁用,因為沒有上一個Panel。另外,當有些值必須輸入的時候,沒有輸入的情況下Panel中的Next按鈕應該為禁用。
第三,輸入的數據需要一直保持到用戶完成向導或是取消。因為當用戶點擊Back按鈕會到上一個Panel時,上一個Panel填寫的數據應該能夠保持,并且再次使用Next按鈕時,本Panel中的數據也應該保持。
有了這些設計細節,我們可以考慮設計自己的向導了。我們先規劃下我們將要完成的一些類。
Wizard-這個類包含模型(model)和控制器(controller),其主要是一個對話框(JDialog),并且包含有Next,Back和Cancel按鈕。還有一個使用CardLayout布局管理的大組件,可以把各個Panel顯示在上面。想下圖的樣子:
Java.awt.Componet的子類,這個類一般是繼承了java.awt.Componet,通常是一個javax.swing.JPanel.這個類是用于顯示在wizard類中的大組件位置。下圖是其中一個Panel。
WizardPanelDescriptor-這第三個類用于關聯wizard和panel。這個需要類用戶繼承,并用于標識Panel.這個類指定了訪問下一個和前一個Panel的規則,并且在Panel的顯示前,現實中,和顯示后執行相應的動作。
Wizard
首先我們需要創建一個用于顯示向導對話框本身,它包含有三個按鈕Back,Next,Cancel.一般這些按鈕是按照從左到右的順序分布的,另外Cancel按鈕要離其他的兩個按鈕遠一點,這樣防止用戶不小心點擊到Cancel按鈕。接下來,就用需要一個布局,可以在同一個區域顯示各個Panel,在AWT中有CardLayout布局。
在這個設計中,我們使用一個簡單的方法來檢測我們的數據。下面我們看看Wizard類:
Public Class Wizard{
private WizardModel wizardModel;
private WizardController wizardController;
private JDialog wizardDialog;
private JPanel cardPanel;
private CardLayout cardLayout;
private JButton backButton;
private JButton nextButton;
private JButton cancelButton;
private MainFrame mainFrame;
private int returnCode;
public Wizard(MainFrame owner) {
this.mainFrame = owner;
wizardModel = new WizardModel();
wizardDialog = new JDialog(owner);
Point np = owner.getLocation();
wizardDialog.setLocation(np);
initComponents();
}
}
注意到initComponents()方法,這個方法是用于布置界面中的組件和按鈕的,并且把按鈕事件關聯到控制器中。
private void initComponents() {
JPanel buttonPanel = new JPanel();
Box buttonBox = new Box(BoxLayout.X_AXIS);
cardPanel = new JPanel();
cardPanel.setBorder(new EmptyBorder(new Insets(5, 10, 5, 10)));
cardLayout = new CardLayout();
cardPanel.setLayout(cardLayout);
backButton = new JButton();
nextButton = new JButton();
cancelButton = new JButton();
backButton.setActionCommand(BACK_BUTTON_ACTION_COMMAND);
nextButton.setActionCommand(NEXT_BUTTON_ACTION_COMMAND);
cancelButton.setActionCommand(CANCEL_BUTTON_ACTION_COMMAND);
buttonPanel.setLayout(new BorderLayout());
buttonPanel.add(separator, BorderLayout.NORTH);
buttonBox.setBorder(new EmptyBorder(new Insets(5, 10, 5, 10)));
buttonBox.add(backButton);
buttonBox.add(Box.createHorizontalStrut(10));
buttonBox.add(nextButton);
buttonBox.add(Box.createHorizontalStrut(30));
buttonBox.add(cancelButton);
buttonPanel.add(buttonBox, java.awt.BorderLayout.EAST);
wizardDialog.getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
wizardDialog.getContentPane().add(cardPanel, java.awt.BorderLayout.CENTER);
}
接下來,我們要把Componet Panel注冊到Wizard中。Wzard中使用registerWizardPanel()方法。我們知道CardLayout布局中包含有next(),previous()這樣的方法來回翻動Panel,然而我們需要的是樹形結構,而不是線性的結構,因此我們需要使用標識符來標識各個Panel對象。
public void registerWizardPanel(Object id, WizardPanelDescriptor panel) {
cardPanel.add(panel.getPanelComponent(), id);
wizardMdel.registerPanel(id, panel);
}
最后wizard中是用setCurrentPanel(Object id)來設置,Wizard初始化時顯示的第一個Panel。剩下的就是一些事件處理,比較簡單。Wizard中大量的使用Wizard來保存數據,并使用WizardController來處理對話框本身的事件。
WizardPanelDescriptor
注冊到Wizard的每個方法都需要繼承WizardPanelDescriptor類,這個類包含一些方法可以把組件集成到Wizard向導中。以下的四個方法:是訪問組件和組件對象標識符的方法。
public final void setPanelComponent(Component panel) {
targetPanel = panel;
}
public final Component getPanelComponent() {
return targetPanel;
}
public final Object getPanelDescriptorIdentifier() {
return panelIdentifier;
}
public final void setPanelDescriptorIdentifier(Object id) {
panelIdentifier = id;
}
下面是比較重要的一部分,就是每個繼承類都需要改寫的一些方法。包括控制Next,Back之后顯示的Panel。在每次Panel初始化的時候都會執行Next這個方法,當Next和Back方法中返回的是null之的時候Next和Back按鈕被禁用。因此,Next方法不能用于數據的校驗,需要有另外的方法,在這里使用了一個Validator方法,當然如果數據需要校驗,也需要在WizardPanelDescriptor子類中進行覆蓋。具體如下:
public Object getNextPanelDescriptor() {
return null;
}
public Object getBackPanelDescriptor() {
return null;
}
public boolean validator(){
return true;
}
另外提供三個方法,用于控制Panel顯示前,顯示中和顯示后的事件。
public void aboutToDisplayPanel() {
}
public void displayingPanel() {
}
public void aboutToHidePanel() {
}
WizardPanelDescriptor
最后,用上圖來表示Wizard類和其他幾個類之間的關系圖,并展示了兩個實例。