在Alexander的模式分类和软件模式的分类中,每种模式都遵循特定的格式。
首先描叙背景,即引发设计问题的情形;
接着解释问题,通常这里会有几个冲突的因素;
最终,权衡这些冲突,给出问题的解决方案
AWT和Swing设计中使用的几种模式:
模型-视图-控制器(model-view-controller,MVC)
容器和组件是“组合(composite)”模式
带滚动条的面板是“装饰(decorate)”模式
布局管理器是“策略(strategy)”模式
每个组件都有三要素(如按钮,复选框,文本框或复杂的树形组件)
内容,如按钮的状态(是否按下),或者文本框的文本
外观(颜色、大小等)
行为(对事件的反应)
面向对象设计的一个基本原则:限制一个对象拥有的功能数量。如不要用一个按钮类完成所有的事情,而是应该让一个对象负责组件的观感,另一个对象负责存储内容。MVC模式告诉我们,实现三个独立的类:
模型(model):存储内容
视图(view):显示内容
控制器(controller):处理用户输入
模型存储内容,他没有用户界面,完全不可见,必须实现改变内容和查找内容的方法。显示存储在模型中的数据是视图的工作。
MVC的一个优点,一个模型可以有多个视图,每个视图可以显示全部内容或不同部分或不同形式。
控制器负责处理用户输入事件,如鼠标和键盘。然后决定是否把这些事件转化成对模型或视图的改变。
对大多数组件来说,模型类将实现一个名字以Model结尾的接口,如按钮实现了ButtonModel接口。实现了此接口的类可以定义各种按钮的状态。
每个JButton对象都存储了一个按钮模型对象,用下列方式得到它的引用;
JButton button = new JButton("Blue"); ButtonModel model = button.getModel();
实际上,不必要关注按钮状态的零散信息,只有绘制它的视图才对此感兴趣。
模型不存储按钮标签或者图标。
需注意的是,同样的模型(即DefaultButtonModel)可用于下压按钮、单选安钮、复选框甚至是菜单项。当然这些按钮都有各自不同的视图和控制器。通常,每个Swing组件都有一个相关的后缀为UI的视图对象,但并非所有Swing组件都有专门的控制器对象。
ButtonModel接口的属性
属性名 |
值 |
ActionCommand |
与按钮关联的动作命令字符串 |
Mnemonic |
按钮的快捷键 |
Armed |
如果按钮被按下且鼠标仍在按钮上则为true |
Enabled |
如果按钮是可选择则为true |
Pressed |
如果按钮被按下且鼠标按键没有释放则为true |
Rollover |
如果鼠标在按钮上则为true |
Selected |
如果按钮已经被选择(用于复选框和单选按钮)则为true |
通常,组件放置在容器中,布局管理器(layout manager)决定容器中的组件放置的位置和大小。
按钮、文本域和其他用户界面元素都继承于Component类,组件可以放置在面板这样的容器中。由于Container类继承于Component类,所以容器也可以放置在另一容器中。下图给出Component的类层次结构。
每个容器都有一个默认的布局管理器,但可以重新设置。
panel.setLayout(new GridLayout(4,4));
这个面板用GridLayout类布局组件。可往容器中添加组件。容器的add方法把组件和放置的方位传递给布局管理器。
API java.awt.Container 1.0
void SetLayout(LayoutManager m)
为容器设置布局管理器
Component add(Component c)
Component add(Component c,Object constraints) 1.1
将组件添加到容器中,并返回组件的引用。
参数:c 要添加的组件
constraints 布局管理器理解的标识符
API java.awt.FlowLayout 1.0
FlowLayout()
FlowLayout(int align)
FlowLayout(int align,int hgap,int vgap)
构造一个新的FlowLayout对象。
参数:align LEFT、CENTER或RIGHT
hgap 以像素为单位的水平间距(如果为负值,则强行重叠)
vgap 以像素为单位的垂直间距(如果为负值,则强行重叠)
边框布局管理器(border layout manager)是每个JFrame的内容窗格的默认布局管理器。流布局管理器完全控制每个组件的放置位置,边框布局管理器允许为每个组件选择一个放置位置。可选把组件放在内容窗格的中、北、南、东、西部。如:
frame。add(component,BorderLayout.SOUTH);
先放置边缘组件,剩余可用空间由中间组件占据。容器缩放时,边缘组件厚度不变,中间组件大小变化。默认为CENTER。
与流布局不同,边框布局会扩展所有组件的尺寸以便填满可用空间(流布局将维持每个组件的最佳尺寸)。当讲一个按钮添加到容器中时会出现问题:
frame.add(yellowButton,BorderLayout.SOUTH);//don't
按钮将扩展至填满整个南部区域,如果在添加一个按钮到南部,就会取代第一个按钮。解决常用的方法是使用另一个面板(panel),再将面板放置在内容窗格的南部。
先创建一个新的JPanel对象,然后逐一将按钮添加到面板。面板的默认布局管理器是FlowLayout。然后用frame.add将面板添加到框架的内容窗格中。
JPanel panel = new JPanel(); panel.add(yellowButton); panel.add(blueButton); panel.add(redButton); frame.add(panel,BorderLayout.SOUTH);
边框布局管理器将会扩展面板大小,直至填满整个南部区域。
API java.awt.BorderLayout 1.0
BorderLayout()
BroderLayout(int hgap,int vgap)
构造一个新的BorderLayout对象。
参数:hgap 以像素为单位的水平间距(如果为负值,则强行重叠)
vgap 以像素为单位的水平间距(如果为负值,则强行重叠)
网格布局像电子数据表一样,按行列排列所有的组件。不过,他的每个单元大小都一样的。在网格布局对象的构造器中,需要指定行数和列数:
panel.setLayout(new GridLayout(5,4));
添加组件,从第一行的第一列开始,然后是第一行的第二列,以此类推。
panel.add(new JButton("1")); panel.add(new JButton("2"));
下图是一个计算器:
程序中,在将组件添加到框架之后,调用了pack方法,这个方法用于将所有组件以最佳的高度和宽度显示在框架中。当然,很少有像计算器这样整齐的布局。实际上,在组件窗口的布局是小网格(通常只有一行或一列)比较有用。将其放置在一个面板上,这个面板使用只有一行的网格布局进行管理。
Calculator.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class Calculator { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CalculatorFrame frame = new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * A frame with a calculator panel. */ class CalculatorFrame extends JFrame { public CalculatorFrame() { setTitle("Calculator"); CalculatorPanel panel = new CalculatorPanel(); add(panel); pack(); } } /** * A panel with calculator buttons and a result display. */ class CalculatorPanel extends JPanel { public CalculatorPanel() { setLayout(new BorderLayout()); result = 0; lastCommand = "="; start = true; // add the display display = new JButton("0"); display.setEnabled(false); add(display, BorderLayout.NORTH); ActionListener insert = new InsertAction(); ActionListener command = new CommandAction(); // add the buttons in a 4 x 4 grid panel = new JPanel(); panel.setLayout(new GridLayout(4, 4)); addButton("7", insert); addButton("8", insert); addButton("9", insert); addButton("/", command); addButton("4", insert); addButton("5", insert); addButton("6", insert); addButton("*", command); addButton("1", insert); addButton("2", insert); addButton("3", insert); addButton("-", command); addButton("0", insert); addButton(".", insert); addButton("=", command); addButton("+", command); add(panel, BorderLayout.CENTER); } /** * Adds a button to the center panel. * @param label the button label * @param listener the button listener */ private void addButton(String label, ActionListener listener) { JButton button = new JButton(label); button.addActionListener(listener); panel.add(button); } /** * This action inserts the button action string to the end of the display text. */ private class InsertAction implements ActionListener { public void actionPerformed(ActionEvent event) { String input = event.getActionCommand(); if (start) { display.setText(""); start = false; } display.setText(display.getText() + input); } } /** * This action executes the command that the button action string denotes. */ private class CommandAction implements ActionListener { public void actionPerformed(ActionEvent event) { String command = event.getActionCommand(); if (start) { if (command.equals("-")) { display.setText(command); start = false; } else lastCommand = command; } else { calculate(Double.parseDouble(display.getText())); lastCommand = command; start = true; } } } /** * Carries out the pending calculation. * @param x the value to be accumulated with the prior result. */ public void calculate(double x) { if (lastCommand.equals("+")) result += x; else if (lastCommand.equals("-")) result -= x; else if (lastCommand.equals("*")) result *= x; else if (lastCommand.equals("/")) result /= x; else if (lastCommand.equals("=")) result = x; display.setText("" + result); } private JButton display; private JPanel panel; private double result; private String lastCommand; private boolean start; }
文本域(JTextField)和文本区(JTextArea)获取用户输入。文本域单行,文本区多行。JPassword也只接收单行文本,不会将输入的内容显示出来。
三个类皆继承与抽象JTextComponent类。
将文本域添加到窗口,常用是将其添加到面板或者其他容器,与按钮一样:
JPanel panel = new JPanel(); JTextField textField = new JTextField("Default input",20); panel.add(textField);
列数是给AWT设定的首选(preferred)大小的一个提示。如果布局管理器需要缩放这个文本域,会调整其大小。重新设置列数用setColumns。
提示:使用setColumns后,要调用包含这个文本框的容器的revalidate.
textField.setColumns(10); panel.revalidate();
revalidate会重新计算容器内所有组件的大小,并重新布局。属于JComponent类.而改变JFrame的组件,要调用validate。
可用setText()改变文本内容。用getText()获取文本。想去掉前后空格,调用trim:
String text = textField.getText().trim();
想要用标示符标示不带标签的组件:
用相应的文本构造一个JLabel组件。
将标签组件放置在距离需要标示组件足够近的地方,以便用户可以知道标签标示的组件。
JLabel的构造器允许指定出事文本和图表,亦可以选择内容的排列方式。用swing Constans接口中的常量来指定排列方式。
JLabel label = new JLabel("User name",Swing.Constants.RIGHT);
或
JLabel label = new JLabel("User name",JLabel.RIGHT);
用setText和setIcon可在运行期间设置标签的文本和图标。
API javax.swing.JPasswordField
JPasswordField(Sting text,int columns)
void setEchoChar(char echo) 为密码域设置回显字符。
char[] getPassword()
返回密码域中的文本。为安全起见,使用之后应该覆写返回的数组内容。
textArea = new JTextArea(4,8)
如果文本区文本超出显示范围,剩下的文本就会被剪掉。可以通过开启换行特性来避免裁剪过长的行:(只是视觉效果)
textArea.setLineWrap(true);
文本区没有滚动条,需要将文本区插入到滚动窗格(scrollpane)
textArea = new JTextArea(8,40); JScrollPane scrollPane = new JScrollPane(textArea);
为任意组件添加滚动功能的通用机制。