向导在今天的桌面应用中非常常用。向导应该是个什么样子呢?相信你应该很清楚,因为你使用过很多的向导。也许你使用过一些安装程序向导或是一些程序的配置向导。这篇文章,我们会创建一个简单的向导框架。
一个向导包括很多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类和其他几个类之间的关系图,并展示了两个实例。