向?qū)г诮裉斓淖烂鎽?yīng)用中非常常用。向?qū)?yīng)該是個(gè)什么樣子呢?相信你應(yīng)該很清楚,因?yàn)槟闶褂眠^很多的向?qū)АR苍S你使用過一些安裝程序向?qū)Щ蚴且恍┏绦虻呐渲孟驅(qū)А_@篇文章,我們會(huì)創(chuàng)建一個(gè)簡單的向?qū)Э蚣堋?
一個(gè)向?qū)Оê芏郟anel,每個(gè)Panel里面包含用戶的配置組件或是文本域或是選擇框等。用戶點(diǎn)擊”Next”或是”Back”按鈕,在各個(gè)Panel之間切換,輸入需要的信息。注意的是,當(dāng)最后一個(gè)Panel是”Next”按鈕要變成”Finish”按鈕,并且再次按下的時(shí)候向?qū)шP(guān)閉。在向?qū)шP(guān)閉的時(shí),發(fā)起向?qū)У念愐玫较驅(qū)anel中所有的數(shù)據(jù)。在任何情況下,用戶可以點(diǎn)擊”Cancel”按鈕關(guān)閉向?qū)РG棄前面所填的所有數(shù)據(jù)。
看上去很簡單是嗎?對(duì)的,但是有些設(shè)計(jì)細(xì)節(jié)我們需要考慮。
第一,向?qū)е械拿總€(gè)Panel不是都需要訪問的,換句話說,如果向?qū)О?到5個(gè)Panel,點(diǎn)擊”Next”按鈕可以從第一個(gè)Panel依次到第五個(gè)Panel,但是有時(shí)候可能由于用戶的選項(xiàng)直接從第一個(gè)Panel跳到第五個(gè)Panel. 而且還有的情況是,假設(shè)向?qū)е行枰B接到遠(yuǎn)程服務(wù)器或是遠(yuǎn)程數(shù)據(jù)庫,如果連接不上的,那么就不能到達(dá)下一個(gè)Panel。這樣向?qū)е械腜anel就像是樹形,你從樹的根開始,通過不同的分支到達(dá)葉子節(jié)點(diǎn),這時(shí)”Next”按鈕變成”Finish”按鈕。
第二,有些時(shí)候Next按鈕和Back按鈕需要禁用。比如,第一個(gè)Panel出現(xiàn)的時(shí)候,back按鈕應(yīng)該禁用,因?yàn)闆]有上一個(gè)Panel。另外,當(dāng)有些值必須輸入的時(shí)候,沒有輸入的情況下Panel中的Next按鈕應(yīng)該為禁用。
第三,輸入的數(shù)據(jù)需要一直保持到用戶完成向?qū)Щ蚴侨∠R驗(yàn)楫?dāng)用戶點(diǎn)擊Back按鈕會(huì)到上一個(gè)Panel時(shí),上一個(gè)Panel填寫的數(shù)據(jù)應(yīng)該能夠保持,并且再次使用Next按鈕時(shí),本Panel中的數(shù)據(jù)也應(yīng)該保持。
有了這些設(shè)計(jì)細(xì)節(jié),我們可以考慮設(shè)計(jì)自己的向?qū)Я恕N覀兿纫?guī)劃下我們將要完成的一些類。
Wizard-這個(gè)類包含模型(model)和控制器(controller),其主要是一個(gè)對(duì)話框(JDialog),并且包含有Next,Back和Cancel按鈕。還有一個(gè)使用CardLayout布局管理的大組件,可以把各個(gè)Panel顯示在上面。想下圖的樣子:
Java.awt.Componet的子類,這個(gè)類一般是繼承了java.awt.Componet,通常是一個(gè)javax.swing.JPanel.這個(gè)類是用于顯示在wizard類中的大組件位置。下圖是其中一個(gè)Panel。
WizardPanelDescriptor-這第三個(gè)類用于關(guān)聯(lián)wizard和panel。這個(gè)需要類用戶繼承,并用于標(biāo)識(shí)Panel.這個(gè)類指定了訪問下一個(gè)和前一個(gè)Panel的規(guī)則,并且在Panel的顯示前,現(xiàn)實(shí)中,和顯示后執(zhí)行相應(yīng)的動(dòng)作。
Wizard
首先我們需要?jiǎng)?chuàng)建一個(gè)用于顯示向?qū)?duì)話框本身,它包含有三個(gè)按鈕Back,Next,Cancel.一般這些按鈕是按照從左到右的順序分布的,另外Cancel按鈕要離其他的兩個(gè)按鈕遠(yuǎn)一點(diǎn),這樣防止用戶不小心點(diǎn)擊到Cancel按鈕。接下來,就用需要一個(gè)布局,可以在同一個(gè)區(qū)域顯示各個(gè)Panel,在AWT中有CardLayout布局。
在這個(gè)設(shè)計(jì)中,我們使用一個(gè)簡單的方法來檢測(cè)我們的數(shù)據(jù)。下面我們看看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()方法,這個(gè)方法是用于布置界面中的組件和按鈕的,并且把按鈕事件關(guān)聯(lián)到控制器中。
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注冊(cè)到Wizard中。Wzard中使用registerWizardPanel()方法。我們知道CardLayout布局中包含有next(),previous()這樣的方法來回翻動(dòng)Panel,然而我們需要的是樹形結(jié)構(gòu),而不是線性的結(jié)構(gòu),因此我們需要使用標(biāo)識(shí)符來標(biāo)識(shí)各個(gè)Panel對(duì)象。
public void registerWizardPanel(Object id, WizardPanelDescriptor panel) {
cardPanel.add(panel.getPanelComponent(), id);
wizardMdel.registerPanel(id, panel);
}
最后wizard中是用setCurrentPanel(Object id)來設(shè)置,Wizard初始化時(shí)顯示的第一個(gè)Panel。剩下的就是一些事件處理,比較簡單。Wizard中大量的使用Wizard來保存數(shù)據(jù),并使用WizardController來處理對(duì)話框本身的事件。
WizardPanelDescriptor
注冊(cè)到Wizard的每個(gè)方法都需要繼承WizardPanelDescriptor類,這個(gè)類包含一些方法可以把組件集成到Wizard向?qū)е小R韵碌乃膫€(gè)方法:是訪問組件和組件對(duì)象標(biāo)識(shí)符的方法。
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;
}
下面是比較重要的一部分,就是每個(gè)繼承類都需要改寫的一些方法。包括控制Next,Back之后顯示的Panel。在每次Panel初始化的時(shí)候都會(huì)執(zhí)行Next這個(gè)方法,當(dāng)Next和Back方法中返回的是null之的時(shí)候Next和Back按鈕被禁用。因此,Next方法不能用于數(shù)據(jù)的校驗(yàn),需要有另外的方法,在這里使用了一個(gè)Validator方法,當(dāng)然如果數(shù)據(jù)需要校驗(yàn),也需要在WizardPanelDescriptor子類中進(jìn)行覆蓋。具體如下:
public Object getNextPanelDescriptor() {
return null;
}
public Object getBackPanelDescriptor() {
return null;
}
public boolean validator(){
return true;
}
另外提供三個(gè)方法,用于控制Panel顯示前,顯示中和顯示后的事件。
public void aboutToDisplayPanel() {
}
public void displayingPanel() {
}
public void aboutToHidePanel() {
}
WizardPanelDescriptor
最后,用上圖來表示W(wǎng)izard類和其他幾個(gè)類之間的關(guān)系圖,并展示了兩個(gè)實(shí)例。