用更好的方法加速你的Swing GUI构建

摘要

如果每一个对话框不得不用一个IDE可视设计器进行编码和构建,那么开发一个基于Swing使用多个对话框的用户界面可能会变得很慢及单调乏味。这篇文章介绍一种快速的方法,使用两个有用的类,LabelledItemPanel和StandardDialog在当多数对话框有着相似布局时处理对话框的布局。使用这些有帮助的类,开发者可以减少对话框的开发时间达80%。

想法是这些类使用一个标准的方法封装了表单和数据输入对话框的布局。特定的对话框能够通过简单地添加必需的对话框字段到两个类,而无需担心字段的布局。

这两个字段是:

· LabelledItemPanel: 这个类将项目及其标签整洁地布置在一个面板上。

· StandardDialog: 这个类提供对话框和缺省按钮处理的常规外表。

这两个类基于多数用于输入信息的对话框通常都有一样的基本布局这样一个事实。看看用于输入客户、产品和职员数据的三个对话框:
 
图1.客户详细资料对话框

 
图2.产品详细资料对话框
 
图3.职员详细资料对话框

每个对话框都有一样的由一些带标签的字段列表组成的布局。每个字段的布局也是一样的。在数据输入对话框中都有标准的Ok和Cancel按钮。

在我们的工程中,我们用这些类代替一个IDE图形用户界面(GUI)构建器以节约时间。我们需要构建20多个用于创建、修改、查看各种各样数据类型的对话框。在项目开始时,第一个LabelledItemPanel和StandardDialog版本将被建立。两个类都没有排列问题。所有要求的面板和对话框都将被创建而无需担心布局方面的问题。排列问题将在LabelledItemPanel和StandardDialog中得以解决。所有使用这两个类的面板和对话框将得以校正。当要求完全独立的对话框时这个方法允许开发者将GUI和应用程序代码以最快的速度整合在一起。

在这篇文章中,将我们的方法同使用一个IDE GUI构建器的流行方法作了一个比较。我们将解释是怎样提出关于这些类的想法及它们是如何节约开发时间的。我们也将详细描述这个两个类及其用法。

使用一个IDE GUI构建器

今天多数IDE都有能力无需写太多的代码就能构建用户界面。对于许多开发者来说这是一个吸引人的选项因为写一个用户界面可能会很复杂。然而,使用一个IDE GUI构建器建立许多的窗口和对话框也有其自身的缺点。下面部分将阐述这些缺点。

对于巨大的用户界面的神话与现实

当你仅用很少一些窗口和对话框创建一个简单GUI时IDE是一款很好的工具。当你需要创建很多窗口及对话框时,使用一个IDE GUI构建器有下面一些缺点:

· 除非你使用简单的布局管理或绝对定位,否则你仍需要理解各种各样复杂的布局管理,尤其是GridBagLayout。

· 在对话框中的每一条款都需要分别进行布置,这包括设置每个条款的许多属性。这使其在布置大数目的对话框和带有许多字段的对话框时显得缓慢和单调乏味。

· 你不能在相似的屏幕上使用同一个布局,每一个新的对话框都需从设计板上进行布置。

· 在IDE间产生的代码通常是不可移植的,在不同的IDE版本间有时甚至不兼容。在重视代码维护时这是必须考虑的一个重要事实。

· 对话框不容易修改因为布局对每个条款都进行了约束。假如一个条款将被移动,则对它的约束及所有其它条款的约束都将被移动的条款所影响并需要重新调整。

· 假如有一个bug,审核产生的代码将是十分恐怖的事情,在多数的案例中产生的代码不具有良好的排列及易读性。例如,下面是由通用的IDE GUI构建器产生的一个用户数据输入面板的代码:

public class JBuilderPanel extends JPanel

{

...

GridBagLayout gridBagLayout1 = new GridBagLayout();

private JLabel myCustomerCodeLabel = new JLabel();

private JTextField myCustomerCodeField = new JTextField();

private JLabel myNameLabel = new JLabel();

private JTextField myNameField = new JTextField();

private JLabel myAddressLabel = new JLabel();

private JScrollPane myAddressScrollpane = new JScrollPane();

private JTextArea myAddressField = new JTextArea();

...

public JBuilderPanel()

{

try

{

jbInit();

}

catch(Exception e)

{

e.printStackTrace();

}

}

private void jbInit() throws Exception

{

this.setLayout(gridBagLayout1);

this.setBorder(BorderFactory.createEtchedBorder());

myCustomerCodeLabel.setText("Customer Code");

myNameLabel.setText("Name");

myAddressLabel.setText("Address");

...

this.add(myCustomerCodeLabel,

new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,

GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,

new Insets(10, 10, 0, 0), 0, 0));

this.add(myCustomerCodeField,

new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0,

GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,

new Insets(10, 10, 0, 10), 0, 0));

this.add(myNameLabel,

new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,

GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,

new Insets(10, 0, 0, 0), 0, 0));

this.add(myNameField,

new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0,

GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,

new Insets(10, 10, 0, 10), 0, 0));

this.add(myAddressLabel,

new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,

GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,

new Insets(10, 10, 0, 0), 0, 0));

this.add(myAddressScrollpane,

new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0,

GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,

new Insets(10, 10, 0, 10), 0, 0));

...

}

...

}

一种可选择的方法

在我们的工程开始时我们找寻一种方案使用一个IDE创建对话框因为我们有太多的对话框要建立同时又要意识到上面所描述的缺点。然而,我们在简单使用Swing布局管理器及组件时还没有更好的境况。我们将结束用相同的缺点产生类似的IDE代码。为加速开发我们需要更好的构建类以照顾对话框的公用外表及容易定制处理屏幕间的差异。

什么是公用?什么是差异?

我们确定下面三个元素对于多数的对话框是公用的:

· 对话框通常由一个整洁地排列在页中的条款和标签列表组成。

· 每个对话框通常都有Ok和Cancel按钮。

· 当Ok和Cancel按钮被按下时行为的执行通常都是一样的。

屏幕间的差异就是指被编辑的实际字段和它们的标签。

更好的构建模块

开发对话框中多数单调乏味的部分就是为每个字段及其标签指定布局约束。快速的开发方法是允许我们指定字段和它们的标签而无须指定布局约束。可以理解为使用两个不同的类:一个处理带有标签的字段布局,另一个用于公用对话框按钮及相关处理。我们叫布局处理的类为LabelledItemPanel,用于公用对话框特征的类为StandardDialog。

一个公用面板:LabelledItemPanel

LabelledItemPanel的用途是在一个面板上整洁地布置条款及它们的标签。类使用一个GridBagLayout控制实际的布局,但这对于类用户来说都是隐藏的。要使用类,条款及其标签用addItem()方法被添加到面板,并传递字段标签及字段组件的文本。用户没有必要指定任何布局约束因为类完成了所有需要的布局约束。

LabelledItemPanel能够用两种方法被使用:

· 创建一个LabelledItemPanel的实例并添加数据输入字段到面板。

· 在构建过程中创建子类LabelledItemPanel并添加数据输入字段到面板。

一个使用LabelledItemPanel的例子直接在下面的代码段中被显示:

JTextField customerCodeField = new JTextField();

JTextField nameField = new JTextField();

JTextArea addressField = new JTextArea(3, 20);

...

LabelledItemPanel panel = new LabelledItemPanel()

panel.setBorder(BorderFactory.createEtchedBorder());

panel.addItem("Customer Code", customerCodeField);

panel.addItem("Name", nameField);

panel.addItem("Address", new JScrollPane(addressField,

JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,

JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));

...

//对用户显示在一个对话框中的面板

String customerCode = customerCodeField.getText();

String name = nameField.getText();

String address = addressField.getText();

...

//处理数据

如果你比较一下如下显示的用IDE产生的代码调用简单的addItem()方法,则使用这个类节约时间的优势将特别明显:

this.add(myCustomerCodeLabel,

new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,

GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,

new Insets(10, 10, 0, 0), 0, 0));

this.add(myCustomerCodeField,

new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0,

GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,

new Insets(10, 10, 0, 10), 0, 0));

this.add(myNameLabel,

new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,

GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,

new Insets(10, 0, 0, 0), 0, 0));

一个用LabelledItemPanel创建子类的代码显示如下:

public class CustomerPanel extends LabelledItemPanel

{

private JTextField myCustomerCodeField = new JTextField();

private JTextField myNameField = new JTextField();

private JTextArea myAddressField = new JTextArea(3, 20);

...

public CustomerPanel()

{

init();

}

private void init()

{

setBorder(BorderFactory.createEtchedBorder());

addItem("Customer Code", myCustomerCodeField);

addItem("Name", myNameField);

addItem("Address", new JScrollPane(myAddressField,

JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,

JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));

...

}

public CustomerData getCustomerData()

{

CustomerData customerData = new CustomerData();

customerData.myCustomerCode = myCustomerCodeField.getText();

customerData.myName = myNameField.getText();

customerData.myAddress = myAddressField.getText();

...

return customerData;

}

...

}

//一个在一些使用CustomerPanel的类中的方法

{

CustomerPanel panel = new CustomerPanel();

//对用户显示在一个对话框中的面板

CustomerData customerData = panel.getCustomerData();

//处理数据

}

封装下的LabelledItemPanel

这部分解释了LabelledItemPanel是如何工作的。类的代码显示如下:

public class LabelledItemPanel extends JPanel

{

/** 将下一个带标签的条款添加到此行*/

private int myNextItemRow = 0;

public LabelledItemPanel()

{

init();

}

private void init()

{

setLayout(new GridBagLayout());

//创建一个空白标签作为一个竖直添充以便标签/条款对被排列到面板的顶部并且在中心不成组,

//假如父组件高于面板的大小。

GridBagConstraints constraints = new GridBagConstraints();

constraints.gridx = 0;

constraints.gridy = 99;

constraints.insets = new Insets(10, 0, 0, 0);

constraints.weighty = 1.0;

constraints.fill = GridBagConstraints.VERTICAL;

JLabel verticalFillLabel = new JLabel();

add(verticalFillLabel, constraints);

}

public void addItem(String labelText, JComponent item)

{

//创建标签及其约束

JLabel label = new JLabel(labelText);

GridBagConstraints labelConstraints = new GridBagConstraints();

labelConstraints.gridx = 0;

labelConstraints.gridy = myNextItemRow;

labelConstraints.insets = new Insets(10, 10, 0, 0);

labelConstraints.anchor = GridBagConstraints.NORTHEAST;

labelConstraints.fill = GridBagConstraints.NONE;

add(label, labelConstraints);

//添加一个带约束的组件

GridBagConstraints itemConstraints = new GridBagConstraints();

itemConstraints.gridx = 1;

itemConstraints.gridy = myNextItemRow;

itemConstraints.insets = new Insets(10, 10, 0, 10);

itemConstraints.weightx = 1.0;

itemConstraints.anchor = GridBagConstraints.WEST;

itemConstraints.fill = GridBagConstraints.HORIZONTAL;

add(item, itemConstraints);

myNextItemRow++;

}

}

类从Swing的JPanel继承并且有两个附加方法init()和addItem()。init()是从构建器中调用并且通过面板使用建立GridBagLayout。我们已经显示了第二种方法addItem()。这个方法创建一个JLabel用于标签文本并且用恰当的GridBagConstraints添加JLabel及条款到面板。约束将被建立以便标签文本和条款能被适当排列。

一个公用对话框

类StandardDialog的用途是控制一个数据输入面板(比如一个LabelledItemPanel),提供标准的Ok和Cancel按钮,当按下Ok或Cancel按钮时处理基本的操作。

StandardDialog是模式化的,因此对于show()方法的调用是一个模块调用。调用show()方法后,假如用户已经通过按下Cancel按钮或对话框的关闭按钮(在对话框框架上)来取消对话框则hasUserCancelled()方法应该被显式调用。

StandardDialog能够用两种方法被使用:

· 创建一个StandardDialog实例并添加一个数据输入面板到对话框。

· 在构建过程中创建StandardDialog子类并添加一个数据输入面板到对话框。

一个使用StandardDialog的例子被直接显示在如下代码段中:

//创建一个数据输入面板

StandardDialog dialog = new StandardDialog(

(Frame)null, "Customer Details");

dialog.setContentPane(panel);

dialog.pack();

dialog.show();

if(!dialog.hasUserCancelled())

{

//在面板中处理数据

}

一个通过创建子类使用StandardDialog的例子显示在如下代码段中:

public class CustomerDialog extends StandardDialog

{

private JTextField myCustomerCodeField = new JTextField();

private JTextField myNameField = new JTextField();

private JTextArea myAddressField = new JTextArea(3, 20);

...

private LabelledItemPanel myContentPane = new LabelledItemPanel();

public CustomerDialog()

{

init();

}

private void init()

{

setTitle("Customer Dialog");

myContentPane.setBorder(BorderFactory.createEtchedBorder());

myContentPane.addItem("Customer Code", myCustomerCodeField);

myContentPane.addItem("Name", myNameField);

myContentPane.addItem("Address", new JScrollPane(myAddressField,

JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,

JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));

...

setContentPane(myContentPane);

}

public CustomerData getCustomerData()

{

CustomerData customerData = new CustomerData();

customerData.myCustomerCode = myCustomerCodeField.getText();

customerData.myName = myNameField.getText();

customerData.myAddress = myAddressField.getText();

...

return customerData;

}

}

//一个在一些使用CustomerDialog的类中的方法

{

CustomerDialog dialog = new CustomerDialog();

dialog.pack();

dialog.show();

if(!dialog.hasUserCancelled())

{

CustomerData customerData = dialog.getCustomerData();

//处理数据

}

}

如果数据验证需要在对话框关闭前被执行,那么isValidData()方法应该被重载。一个相关的例子显示如下:

protected boolean isValidData()

{

if(myCustomerCodeField.getText().equals(""))

{

JOptionPane.showMessageDialog(this,

"Please enter a Customer Code",

"Blank Customer Code",

JOptionPane.WARNING_MESSAGE);

myCustomerCodeField.requestFocus();

return false;

}

if(myNameField.getText().equals(""))

{

JOptionPane.showMessageDialog(this,

"Please enter a Name",

"Blank Name",

JOptionPane.WARNING_MESSAGE);

myNameField.requestFocus();

return false;

}

return true;

}

布景背后的StandardDialog

这个部分解释StandardDialog是如何工作的。StandardDialog的外观和行为在init()方法中建立,显示如下:

private void init()

{

setModal(true);

setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

//建立内部内容面板以控制用户内容面板和标准按钮面板

JPanel internalContentPane = new JPanel();

internalContentPane.setLayout(

new BorderLayout(COMPONENT_SPACING, COMPONENT_SPACING));

internalContentPane.setBorder(

BorderFactory.createEmptyBorder(COMPONENT_SPACING,

COMPONENT_SPACING, COMPONENT_SPACING, COMPONENT_SPACING));

//创建带“Ok”和“Cancel”标准按钮的面板

Action okAction = new AbstractAction("Ok")

{

public void actionPerformed(ActionEvent actionEvent)

{

if(isValidData())

{

myIsDialogCancelled = false;

dispose();

}

}

};

Action cancelAction = new AbstractAction("Cancel")

{

public void actionPerformed(ActionEvent actionEvent)

{

myIsDialogCancelled = true;

dispose();

}

};

JPanel buttonPanel = new JPanel();

buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

buttonPanel.add(new JButton(okAction));

buttonPanel.add(new JButton(cancelAction));

internalContentPane.add(buttonPanel, BorderLayout.SOUTH);

//用JPanel初始化用户内容面板

setContentPane(new JPanel(new BorderLayout()));

super.setContentPane(internalContentPane);

//最后,给窗口关闭按钮添加一监听器。事件的处理与“Cancel”按钮一样。

WindowAdapter windowAdapter = new WindowAdapter()

{

public void windowClosing(WindowEvent windowEvent)

{

myIsDialogCancelled = true;

dispose();

}

};

addWindowListener(windowAdapter);

}

init()从构建器中被调用。方法使用如下:

· 建立对于Ok和Cancel按钮的行为。这部分设置字段myIsDialogCancelled以指出哪个按钮被按下并调用dispose。方法hasUserCancelled()检查myIsDialogCancelled的值在用法范例中显示。

· 建立两个按钮并将它们与行为相关联。将两个按钮添加到面板中,再将面板添加到对话框中。

· 为关闭窗口事件建立一个事件操作,这个事件操作执行与Cancel按钮行为相同的处理。

优势

使用这种方法我们节约了开发时间。用一个IDE GUI构建器建立一个面板根据面板上字段数目的多少所花的时间平均在1到3小时之间。使用我们的LabelledItemPanel类,面板建构所需时间少于20分钟。我们已经使用了35次LabelledItemPanel。

这个方法的其他优势还包括:

· 完全不需要理解和指定布局约束。这允许开发者只需很少的Swing知识就能快速构建用户界面。

· 数据输入面板与对话框对于用户和开发者都具有兼容性。

· 排列字段十分简单。你仅需要排列addItem()调用,而不必对已排列的字段和标签进行重新调整约束。

· 对于所有输入面板的布局和属性都能够在中心被改变。

· 对于标签没有多余的引用。

更进一步的提高

我们简单的类能够被更进一步提高包括如下一些:

· LabelledItemPanel能够支持一个只读标记,不允许数据编辑。

· LabelledItemPanel能够通过一个ResourceBundle关键字支持I18N以代替已经国际化的文本。

速度的需求

我们已经描述了一种方法通过使用更好的构建模块能够加速Swing用户界面的构建。这依赖两个类:LabelledItemPanel和StandardDialog。我们的方法精髓在于确定GUI中能够被封装到易重用代码中的重复元素。

注意:从资源下载LabelledItemPanel和StandardDialog的源代码

posted on 2004-09-21 17:07  Java & .Net  阅读(903)  评论(0编辑  收藏  举报

导航