目录
Swing
1、Swing和MVC设计模式
构成用户界面组件的各个组成部分,如按钮、复选框、文本域或复杂的树控件,每个组件都有三个特征:
- 内容,如按钮的状态(是否按下),或者文本域中的文本
- 外观(颜色、大小等)
- 行为(对事件的反应)
这三个特征之间存在相当复杂的交互,即使是最简单的组件(如按钮)也能体现出这一点。很明显,按钮的外观显示取决于它的观感。Metal按钮的外观与Windows按钮或者
Moif按钮的外观就不一样。另外,外观显示还要取决于按钮的状态:当按钮被按下时,按钮需要重新绘制,使它看起来不一样。而状态取决于按钮接收到的事件。当用户在按钮上点击鼠标时,按钮就被按下。
Swing设计者采用了MVC模式,这种设计模式要求我们提供三个不同的对象:
- 模型(model):存储内容
- 视图(view):显示内容
- 控制器(controller):处理用户输入
这种模式明确地规定了三个对象如何交互。模型存储内容,它没有用户界面。按钮的内容非常简单,只有很少的一组标志,用来表示当前按钮是否按下,是否处于活动状态,等等。文本域的内容更有意思,它与内容的视图不同:如果内容的长度大于文本域的大小,用户就只能看到文本域可以显示的那一部分。
对大多数Swing组件来说,模型类将实现一个名字以Model
结尾的接口,在这里,接口就名为ButtonModel
接口。实现了此接口的类可以定义各种按钮的状态。实际上,按钮并不复杂,另外Swing库中有一个名为DefaultButtonModel
的类实现了这个接口。
可以通过查看ButtonModel接口的属性来了解按钮模型维护着什么类型的数据:
- actionCommand:与按钮关联的动作命令字符串
- mnemonic:按钮的助记快捷键
- armed:如果按钮按下且鼠标仍在按钮上则为true
- enabled:如果按钮是可选择的则为true
- pressed:如果按钮按下且鼠标按键没有释放则为true
- rollover:如果鼠标在按钮上则为true
- selected:如果按钮已经被选择(用于复选框和单选按钮)则为true
每个JButton对象都存储着一个按钮模型对象,可以如下访问:
var button = new JButton("Blue"); ButtonModel model = button.getModel();
实际上,不必关心按钮状态的详细信息,只有绘制它的视图才对此感兴趣。所有重要的信息(如按钮是否启用)可以通过JButton类得到(当然,JButton类会向它的模型获取这些信息)。
下面查看ButtonModel接口中不包含的信息。模型不存储按钮标签或者图标。对于一个按钮来说,仅凭模型无法知道它的外观。
另外还需要注意,同样的模型(即DefaultButtonModel)可用于下压按钮、单选按钮、复选框,甚至菜单项。当然,这些按钮都有各自不同的视图和控制器。当使用Metal观感时,JButton类用BasicButtonUI类作为其视图;用ButtonUIListener类作为其控制器。通常,每个Swing组件都有一个相关的后缀为UI的视图对象,但并不是所有的Swing组件都有专门的控制器对象。
JButton就是一个继承自JComponent的包装器类,其中包含DefaultButtonModel对象,一些视图数据(例如按钮标签和图标)以及一个负责按钮视图的BasicButtonUI对象。
2、布局管理
2.1、布局管理器
通常,组件放置在容器中,布局管理器决定容器中组件的位置和大小。按钮、文本域和其他的用户界面元素都会扩展Component类,组件可以放置在容器(如面板)中。由于Container类扩展自Component类,所以容器本身也可以放置在另一个容器中。
每个容器都有一个默认的布局管理器,但可以重新进行设置:
panel.setLayout(new GridLayout(4, 4));
会使用GridLayout类按4行4列摆放组件。往容器中添加组件时,容器的add方法将把组件和所有位置要求传递给布局管理器。
java.awt.Container:
- void setLayout(LayoutManager m):为容器设置布局管理器
- Component add(Component c)
- Component add(Component c, Object constraints):将组件添加到容器中,并返回组件引用
java.awt.FlowLayout
- FlowLayout()
- FlowLayout(int align)
- FlowLayout(int align, int hgap, int vgap):构造一个新的FlowLayout对象。align参数可以是LEFT、CENTER或者RIGHT
2.2、边框布局
边框布局管理器(border layout manager)是每个JFrame的内容窗格的默认布局管理器。流布局管理器会完全控制每个组件的位置,边框布局管理器则不然,它允许为每个组件选择一个位置。可以选择把组件放在内容窗格的中央、北部、南部、东部或者西部。
例如:
frame.add(component, BorderLayout.SOUTH);
先放置边缘组件,剩余的可用空间由中间组件占据。当容器调整大小时,边缘组件的尺寸不会改变,而中间组件的大小会发生变化。添加组件时可以指定BorderLayout类的CENTER
、NORTH
、SOUTH
、EAST
和WEST
常量。不是所有的位置都会被占用,如果没有提供任何值,系统默认为CENTER。
与流布局不同,边框布局会扩展所有组件的尺寸以便填满可用空间(流布局将维持每个组件的最佳尺寸)。
添加一个按钮时,这会有问题:
frame.add(yellowButton, BorderLayout.SOUTH);
按钮会扩展至填满窗体的整个南部区域。而且,如果再将另外一个按钮添加到南部区域,就会取代第一个按钮。
public class BorderFrameDemo { public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new BorderFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } } class BorderFrame extends JFrame { private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; public BorderFrame() throws HeadlessException { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JButton button = new JButton("Button"); add(button, BorderLayout.SOUTH); } }
解决这个问题的常见方法是使用另外的面板(panel),因为Panel的默认布局管理器是FlowLayout(流式布局)。
public BorderFrame() throws HeadlessException { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JButton button1 = new JButton("Button1"); JButton button2 = new JButton("Button2"); JButton button3 = new JButton("Button3"); JPanel panel = new JPanel(); panel.add(button1); panel.add(button2); panel.add(button3); add(panel, BorderLayout.SOUTH); }
java.awt.BorderLayout:
- BorderLayout()
- BorderLayout(int hgap, int vgap):构造一个新的BorderLayout对象
2.3、网格布局
网格布局像电子数据表一样,按行列排列所有的组件。不过,所有组件的大小都是一样的。
public class GridFrame extends JFrame { private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; public GridFrame() throws HeadlessException { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JButton b1 = new JButton("1"); JButton b2 = new JButton("2"); JButton b3 = new JButton("3"); JButton b4 = new JButton("4"); JButton b5 = new JButton("5"); JButton b6 = new JButton("6"); JButton b7 = new JButton("7"); JButton b8 = new JButton("8"); JButton b9 = new JButton("9"); setLayout(new GridLayout(3, 3)); add(b1); add(b2); add(b3); add(b4); add(b5); add(b6); add(b7); add(b8); add(b9); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new GridFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
java.awt.GridLayout:
- GridLayout(int rows, int colums)
- GridLayout(int rows, int columns, int hgap, int vgap):构造一个新的GridLayout对象。rows或者columns可以为零,但不能同时为零,指示每行或每列任意的组件数
3、文本输入
- 文本域(JTextField):只能接收单行文本
- 文本区(JTextArea):可以接收多行文本
- 密码域(JPasswordField):只能接收单行文本,但不会将输入的内容显示出来
这三个类都继承自JTextComponent类:
- String getText()
- void setText(String text):获取或设置文本组件的文本内容
- boolean isEditable()
- void setEditable(boolean b):获取或设置editable特性,这个特性决定了用户是否可以编辑这个文本组件的内容
3.1、文本域
把文本域添加到窗口的常用办法是将它添加到一个面板或者其他容器中,这与添加按钮完全一样:
JPanel panel = new JPanel(); JTextField textField = new JTextField("Default input", 20); panel.add(textField);
这段代码将添加一个文本域,初始化时在其中放入字符串"Default input"。构造器的第二个参数设置了文本域的宽度。在这个示例中,宽度值为20“列”。但是,这里所说的列不是一个精确的测量单位。一列就是指当前使用的字体一个字符的宽度。
如果希望文本域最多能够输入n个字符,就应该把宽度设置为n列。在实际中,这样做效果并不理想,应该将最大输人长度再多加1~2个字符。另外要记住,列数只是给AWT设定首选(preferred)大小的一个提示。
如果布局管理器需要缩放这个文本域,它会调整文本域的大小。在JTextField的构造器中设定的宽度并不是用户能输入的字符个数的上限。用户仍然可以输入一个更长的字符串,但是当文本长度超过文本域长度时输入就会滚动。用户通常不喜欢滚动文本域,因此应该尽量把文本域设置得宽一些。如果需要在运行时重新设置列数,可以使用setColumns
方法。
可以在任何时候调用setText方法来改变文本域中的内容。
textField.setText("Hello!");
可以调用getText方法来获取用户键入的文本。这个方法原样返回用户输入的文本。如果想要将文本域中内容的前后空格去掉,可以对getText的返回值应用trim方法:
String text = textField.getText().trim();
如果想改变显示文本的字体,可以使用setFont方法。
javax.swing.JTextField:
- JTextField(int cols):构造一个有给定列数的空JTextField对象
- JTextField(String text, int cols):构造一个有给定列数和初始字符串的JTextField对象
- int getColumns()
- void setColumns(int cols):获取或设置文本域使用的列数
javax.swing.JComponent:
- void revalidate():重新计算组件的位置和大小
- void setFont(Font f):设置这个组件的字体
java.awt.Component:
- void validate():重新计算组件的位置和大小。如果组件是容器,容器中包含的所有组件的位置和大小也会重新计算
- Font getFont():获取组件的字体
3.2、标签和标签组件
标签是容纳文本的组件,它们没有任何的修饰(例如没有边缘),也不能响应用户输入。
可以利用标签标识组件。例如,与按钮不同,文本域没有标识它们的标签。要想用标识符标识这种本身不带标签的组件:
- 用正确的文本构造一个JLabel组件。
- 将它放置在距离需要标识的组件足够近的地方,以便用户看出这个标签所标识的组件。
JLabel的构造器允许指定初始文本和图标,也可以选择内容的排列方式。可以用SwingConstants
接口中的常量来指定排列方式。在这个接口中定义了几个很有用的常量,如LEFT
、RIGHT
、CENTER
、NORTH
、EAST
等。
JLabel类是实现这个接口的众多Swing类之一。因此,
可以指定右对齐标签:
JLabel label = new JLabel("User name:", SwingConstants.RIGHT); //或者 JLabel label = new JLabel("User name:", JLabel.RIGHT);
利用setText和setIcon方法可以在运行期间设置标签的文本和图标。
javax.swing.JLabel:
- JLabel(String text)
- JLabel(Icon icon)
- JLabel(String text, int align)
- JLabel(String text, Icon icon, int align):构造一个标签。align参数是一个SwingConstants常量
- String getText()
- void setText(String text):获取或设置标签的文本
- Icon getIcon()
- void setIcon(Icon icon):获取或设置标签的图标
3.3、密码域
密码域是一种特殊类型的文本域。为了避免有不良企图的人站在一旁看到密码,用户输入的字符不真正显示出来。每个输入的字符都用回显字符(echo character)表示,典型的回显字符是星号(*)。
Swing提供了JPasswordField类来实现这样的文本域。
密码域采用与常规文本域相同的模型来存储数据,但是,它的视图却改为显示回显字符,而不是实际的字符。
javax.swing.JPasswordField:
- JPasswordField(String text, int columns):构造一个新的密码域
- void setEchoChar(char echo):为密码域设置回显字符。这只是一个建议;特定的观感可能坚持使用自己的回显字符。值0会重新设置为默认的回显字符
- char[] getPassword():返回密码域中包含的文本。为了得到更好的安全性,在使用之后应该覆写所返回数组
的内容(密码并不是以String返回,这是因为字符串在被垃圾回收之前会一直驻留在虚拟机中)
3.4、文本区
在JTextArea组件的构造器中,可以指定文本区的行数
和列数。例如:
textArea = new JTextArea(8, 40);
这里参数columns与之前的做法相同,而且出于稳妥的考
虑,应该再增加几列。另外,用户并不受限于输入指定的行数和列数。当输入过长时,文本会滚动。还可以用setColumns方法改变列数,用setRows方法改变行数。这些值只是指示首选大小—布局管理器可能会对文本区进行缩放。
如果文本区的文本超出显示的范围,那么剩下的文本就会被剪裁掉。可以通过开启换行特性来避免裁剪过长的行:
textArea.setLineWrap(true);
换行只是视觉效果,文档中的文本没有改变,并没有在文本中自动插入'\n'
字符。
3.5、滚动窗格
在Swing中,文本区没有滚动条。如果需要滚动条,可以将文本区放在滚动窗格(scroll pane)中。
textArea = new JTextArea(8, 40); JScrollPane scrollPane = new JScrollPane(textArea);
现在滚动窗格管理文本区的视图。如果文本超出了文本区可以显示的范围,滚动条就会自动地出现,删除部分文本后,如果文本能够显示在文本区范围内,滚动条会再次自动地消失。滚动是由滚动窗格内部处理的,编写程序时无须处理滚动事件。
这是一种适用于所有组件的通用机制,而不是文本区特有的。也就是说,要想为组件添加滚动条,只需将它们放入一个滚动窗格中即可。
public class TextComponentFrame extends JFrame { private static final int TEXTAREA_ROWS = 8; private static final int TEXTAREA_COLUMNS = 20; private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 600; public TextComponentFrame() throws HeadlessException { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); //North JTextField textField = new JTextField(); JPasswordField passwordField = new JPasswordField(); JPanel northPanel = new JPanel(); northPanel.setLayout(new GridLayout(2, 2)); northPanel.add(new JLabel("User name:", SwingConstants.RIGHT)); northPanel.add(textField); northPanel.add(new JLabel("Password:", SwingConstants.RIGHT)); northPanel.add(passwordField); add(northPanel, BorderLayout.NORTH); //Center JTextArea textArea = new JTextArea(TEXTAREA_ROWS, TEXTAREA_COLUMNS); JScrollPane scrollPane = new JScrollPane(textArea); add(scrollPane, BorderLayout.CENTER); //South JPanel southPanel = new JPanel(); JButton insertButton = new JButton("Insert"); southPanel.add(insertButton); insertButton.addActionListener(event -> textArea.append("User name:" + textField.getText() + " Password:" + new String(passwordField.getPassword()) + "\n")); add(southPanel, BorderLayout.SOUTH); pack(); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new TextComponentFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.JTextArea:
- JTextArea()
- JTextArea(int rows, int cols)
- JTextArea(String text, int rows, int cols):构造一个新的文本区
- void setColumns(int cols):设置文本区应该使用的首选列数
- void setRows(int rows):设置文本区应该使用的首选行数
- void append(String newText):将给定的文本追加到文本区中已有文本的末尾
- void setLineWrap(boolean wrap):打开或关闭换行
- void setWrapStyleWord(boolean word):如果word是true,超长的行会在单词边界换行。如果为false,超长的行会被截断而不考虑单词边界
- void setTabSize(int c):将制表符(tab stop)设置为c列。注意,制表符不会被转化为空格,但可以让文本对
齐到下一个制表符处。
javax.swing.JScrollPane:
- JScrollPane(Component c):创建一个滚动窗格,用来显示指定组件的内容。当组件内容超过显示范围时,滚动条会自动出现
4、选择组件
4.1、复选框
如果想要接收的输入只是“是”或“否”,就可以使用复选框组件。复选框自动带有标识标签。用户通过点击一个复选框将它选中,再点击则取消选中。当复选框获得焦点时,用户也可以通过按空格键来切换选择。
复选框需要一个紧邻的标签来说明其用途。在构造器中指定标签文本。
bold = new JCheckBOx("Bold");
可以使用setSelected方法来选中或取消选中复选框:
bold.setSelected(true);
isSelected方法将获取每个复选框的当前状态。如果没有选中则为false,如果选中这个复选框则为true。
当用户点击复选框时将触发一个动作事件。与以往一样,可以为复选框关联一个动作监听器。
public class CheckBoxFrame extends JFrame { private JLabel label; private JCheckBox bold; private JCheckBox italic; private static final int FONTSIZE = 24; public CheckBoxFrame() throws HeadlessException { label = new JLabel("The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.BOLD, FONTSIZE)); add(label, BorderLayout.CENTER); ActionListener listener = event -> { int mode = 0; if (bold.isSelected()) { mode += Font.BOLD; } if (italic.isSelected()) { mode += Font.ITALIC; } label.setFont(new Font("Serif", mode, FONTSIZE)); }; JPanel buttonPanel = new JPanel(); bold = new JCheckBox("Bold"); bold.addActionListener(listener); bold.setSelected(true); buttonPanel.add(bold); italic = new JCheckBox("Italic"); italic.addActionListener(listener); buttonPanel.add(italic); add(buttonPanel, BorderLayout.SOUTH); pack(); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new CheckBoxFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.JCheckBox:
- JCheckBox(String label)
- JCheckBox(String label, Icon icon):构造一个复选框,初始没有被选中
- JCheckBox(String label, boolean stete):用给定的标签和初始化状态构造一个复选框
- boolean isSelected()
- void setSelected(boolean state):获取或设置复选框的选择状态
4.2、单选按钮
在很多情况下,我们需要用户只选择几个选项当中的一个。当用户选择另一项的时候,前一项就自动地取消选中。这样一组选项通常称为单选按钮组(Radio Button Group)。
在Swing中实现单选按钮组非常简单。为单选按钮组构造一个ButtonGroup类型的对象。然后,再将JRadioButton类型的对象添加到按钮组中。按钮组对象负责在新按钮被按下时取消前一个选中按钮的选择状态。
注意,按钮组仅仅控制按钮的行为,如果想把这些按钮摆放在一起,需要把它们添加到容器中,和JPanel一样。
public class RadioButtonFrame extends JFrame { private JPanel buttonPanel; private ButtonGroup group; private JLabel label; private static final int DEFAULT_SIZE = 36; public RadioButtonFrame() { label = new JLabel("The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE)); add(label, BorderLayout.CENTER); //添加按钮组 buttonPanel = new JPanel(); group = new ButtonGroup(); addRadioButton("Small", 8); addRadioButton("Medium", 12); addRadioButton("Large", 18); addRadioButton("Extra", 36); add(buttonPanel, BorderLayout.SOUTH); pack(); } public void addRadioButton(String name, int size) { boolean selected = size == DEFAULT_SIZE; JRadioButton button = new JRadioButton(name, selected); group.add(button); buttonPanel.add(button); button.addActionListener(event -> { label.setFont(new Font("Serif", Font.PLAIN, size)); }); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new RadioButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.JRadioButton:
- JRadioButton(String label, Icon icon):构造一个初始没有选中的单选按钮
- JRadioButton(String label, boolean state):用给定的标签和初始状态构造一个单选按钮
javax.swing.ButtonGroup:
- void add(AbstractButton b):将按钮添加到组中
- ButtonModel getSelection():返回被选中的按钮的按钮模型
javax.swing.ButtonModel:
- String getActionCommand():返回按钮模型的动作命令
javax.swing.AbstractButton:
- void setActionCommand(String s):设置按钮及其模型的动作命令
4.3、边框
如果在一个窗口中有多组单选按钮,就需要用可见的方式来指明哪些按钮属于同一组。Swing提供了一组很有用的边框(border)来解决这个问题。可以在任何继承了JComponent的组件上应用边框。最常用的用法是在面板周围放置一个边框,然后用其他用户界面元素(如单选按钮)填充面板。
有几种不同的边框可供选择,但是使用它们的步骤完全一样:
- 调用BorderFactory的静态方法创建边框,如下是几种可选的风格
- 凹斜面
- 凸斜面
- 蚀刻
- 直线
- 蒙版
- 空(在组件外围创建一些空白空间)
- 如果愿意的话,可以给边框添加标题,具体的实现方法是将边框传递给BroderFactory.createTitleBorder
- 如果确实想全力地使用边框,可以调用以下方法将几种边框组合起来:BorderFactory.createCompoundBorder
- 调用JComponent类的setBorder方法将得到的边框添加到组件
把一个带有标题的蚀刻边框添加到一个面板:
Border etched = BorderFactory.createEtchedBorder(); Border titled = BorderFactory.createTitledBorder(etched, "A Title"); panel.setBorder(titled);
java.swing.BorderFactory:
- static Border createLineBorder(Color color)
- static Border createLineBorder(Color color, int thickness):创建一个简单的直线边框
- static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Color color)
- static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Icon tileIcon):创建一个用颜色或重复图标填充的粗边框
- static Border createEmptyBOrder()
- static Border createEmptyBorder(int top, int left, int bottom, int right):创建一个空边框
- static Border createEtchedBorder()
- static Border createEtchedBorder(Color highlight, Color shadow)
- static Border createEtchedBorder(int type)
- static Border createEtchedBorder(int type, Color highlight, Color shadow):创建一个具有3D效果的直线边框。type参数是常量EtchedBorder.RAISED和EtchedBorder.LOWERED之一
- static Border createBevelBorder(int type)
- static Border createBevelBorder(int type, Color highlight, Color shadow)
- static Border createLOweredBevelBorder()
- static Border createRaisedBevelBorder():创建一个具有凹面或凸面效果的边框
- static TitleBorder createTitleBorder(String title)
- static TitleBorder createTitleBorder(Border border)
- static TitleBorder createTitleBorder(Border border, String title)
- static TitleBorder createTitleBorder(Border border, String title, int justification, int position)
- static TitleBorder createTitleBorder(Border border, String title, int justification, int position, Font font)
- static TitleBorder createTitleBorder(Border border, String title, int justification, int position, Font font, Color color):创建一个有指定属性的带标题的边框。justification参数是TitledBorder常量
LEFT
、CENTER
、RIGHT
、LEADING
、TRAILING
或DEFAULT_JUSTIFICATION(左X对齐)
之一,position是ABOVE_TOP
、TOP
、BELOW_TOP
、ABOVE_BOTTOM
、BOTTOM
、BELOW_BOTTOM
或DEFAULT_POSITION(上)
之一。 - static CompoundBorder createCompoundBorder(Border outsideBorder, Border insideBOrder):将两个边框组合成一个新的边框
javax.swing.border.SoftBevelBorder:
- SoftBevelBorder(int type)
- SoftBevelBorder(int type, Color highlight, Color shadow):创建一个带有柔和边角的斜面边框。type参数是BevelBorder.LOWERED和BevelBorder.RAISED之一
javax.swing.border.LineBorder:
- public LineBorder(Color color, int thickness, boolean rounded roundedCorners):用指定的颜色和粗细创建一个直线边框。如果roundedCorners为true,则边框有圆角
javax.swing.JComponent:
- void setBorder(Border border):设置这个组件的边框
4.4、组合框
如果有多个选择项,使用单选按钮就不太适宜了,原因是会占据太多屏幕空间。这时就可以选择组合框。当用户点击这个组件时,会下拉一个选择列表,用户可以从中选择一项。
如果下拉列表框被设置成可编辑(editable),就可以像这
是一个文本域一样编辑当前的选项内容。鉴于这个原因,这种组件被称为组合框(combo box),它将文本域的灵活性与一组预定义的选项组合起来。JComboBox类提供了组合框组件。
在Java7中,JComboBox类是一个泛型类。例如,JComboBox<String>
包含String类型的对象,JComboBox<Integer>
包含整数。
调用setEditable方法可以让组合框可编辑。注意,编辑
只会影响选择的项,而不会改变选择列表的内容。
可以调用getSelectedItem方法获取当前的选项,如果组合框是可编辑的,当前选项可能已经编辑过。不过,对于可编辑组合框,其中的选项可以是任何类型,这取决于编辑器(即由编辑器获取用户输入并将结果转换为一个对象)。如果组合框不是可编辑的,最好调用:
combo.getItemAt(combo.getSelectedIndex());
public class ComboBoxFrame extends JFrame { private JComboBox<String> faceCombo; private JLabel label; private static final int DEFAULT_SIZE = 24; public ComboBoxFrame() { //添加一个标签 label = new JLabel("The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE)); add(label, BorderLayout.CENTER); //添加组合框 faceCombo = new JComboBox<>(); faceCombo.addItem("Serif"); faceCombo.addItem("SansSerif"); faceCombo.addItem("Monospaced"); faceCombo.addItem("Dialog"); faceCombo.addItem("DialogInput"); faceCombo.addActionListener(event -> { label.setFont(new Font(faceCombo.getItemAt(faceCombo.getSelectedIndex()), Font.PLAIN, DEFAULT_SIZE)); }); JPanel comboPanel = new JPanel(); comboPanel.add(faceCombo); add(comboPanel, BorderLayout.SOUTH); pack(); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new ComboBoxFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.JComboBox:
- boolean isEditable()
- void setEditable(boolean b):获取或设置组合框的editable特性
- void addItem(Object item):把一个选项添加到选项列表中
- void insertItemAt(Object item, int index):将一个选项插入到选项列表的指定索引位置
- void removeItem(Object item):从选项列表中删除一个选项
- void removeItemAt(int index):删除指定索引位置的选项
- void removeAllItems():从选项列表中删除所有选项
- Object getSelectedItem():返回当前选择的选项
4.5、滑动条
滑动条允许用户从连续值中进行选择。
JSlider slider = new JSlider(min, max, initialValue);
如果省略最小值、最大值和初始值,其默认值分别为0、100和50。或者如果需要垂直滑动条,可以按照以下方式调用构造器:
JSlider slider = new JSlider(SwingConstants.VERICAL, min, max, initialValue);
当用户滑动滑动条时,滑动条的值就会在最小值和最大值之间变化。当值发生变化时,ChangeEvent就会发送给所有变更监听器。为了得到这些变更的通知,需要调用addChangeListener方法并且安装一个实现了
ChangeListener接口的对象。在这个回调中,可以获取滑
动条的值:
ChangeListener listener = event -> { JSlider slider = (JSlider)event.getSource(); int value = slider.getValue(); ... };
可以通过显示刻度(tick)对滑动条进行修饰:
slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5);
上述滑动条在每20个单位的位置显示一个大刻度标记,每5个单位的位置显示一个小刻度标记。所谓单位是指滑动条值,而不是像素。这些指令只设置了刻度标记的单位数,要想将它们真正显示出来,还需要调用:
slider.setPaintTicks(true);
大刻度和小刻度标记是相互独立的。例如,可以每20个单位设置一个大刻度标记,同时每7个单位设置一个小刻度尺标记,但是这样设置滑动条看起来会显得非常凌乱。
可以强制滑动条对齐刻度(snap to tick)。这样一来,只要用户采用对齐模式完成拖放滑动条的操作,它就会立即自动地移到最接近的刻度。激活这种模式需要调用:
slider.setPaintLabels(true);
public class SliderFrame extends JFrame { private JPanel sliderPanel; private JTextField textField; private ChangeListener listener; public SliderFrame() { sliderPanel = new JPanel(); sliderPanel.setLayout(new GridBagLayout()); //滑动条的通用监听器 listener = event -> { JSlider source = (JSlider)event.getSource(); textField.setText("" + source.getValue()); }; //滑动条1 JSlider slider = new JSlider(); addSlider(slider, "Plain"); //滑动条2 slider = new JSlider(); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); addSlider(slider, "Ticks"); //滑动条3 slider = new JSlider(); slider.setPaintTicks(true); slider.setSnapToTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); addSlider(slider, "Snap to ticks"); //滑动条4 slider = new JSlider(); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); slider.setPaintTrack(false); addSlider(slider, "No track"); //滑动条5 slider = new JSlider(); slider.setPaintTrack(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); slider.setInverted(true); addSlider(slider, "Inverted"); //滑动条6 slider = new JSlider(); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); addSlider(slider, "Labels"); //滑动条7 slider = new JSlider(); slider.setPaintLabels(true); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); Hashtable<Integer, Component> labelTable = new Hashtable<>(); labelTable.put(0, new JLabel("A")); labelTable.put(20, new JLabel("B")); labelTable.put(40, new JLabel("C")); labelTable.put(60, new JLabel("D")); labelTable.put(80, new JLabel("E")); labelTable.put(100, new JLabel("F")); slider.setLabelTable(labelTable); addSlider(slider, "Custom labels"); //滑动条8 slider = new JSlider(); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setSnapToTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(20); labelTable = new Hashtable<>(); labelTable.put(0, new JLabel(new ImageIcon("icon.png"))); labelTable.put(20, new JLabel(new ImageIcon("icon.png"))); labelTable.put(40, new JLabel(new ImageIcon("icon.png"))); labelTable.put(60, new JLabel(new ImageIcon("icon.png"))); labelTable.put(80, new JLabel(new ImageIcon("icon.png"))); labelTable.put(100, new JLabel(new ImageIcon("icon.png"))); slider.setLabelTable(labelTable); addSlider(slider, "Icon labels"); textField = new JTextField(); add(sliderPanel, BorderLayout.CENTER); add(textField, BorderLayout.SOUTH); pack(); } public void addSlider(JSlider slider, String description) { slider.addChangeListener(listener); JPanel panel = new JPanel(); panel.add(slider); panel.add(new JLabel(description)); panel.setAlignmentX(Component.LEFT_ALIGNMENT); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridy = sliderPanel.getComponentCount(); gbc.anchor = GridBagConstraints.WEST; sliderPanel.add(panel, gbc); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new SliderFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.JSlider:
- JSlider()
- JSlider(int direction)
- JSlider(int min, int max)
- JSlider(int min, int max, int initialValue)
- JSlider(int direction, int min, int max, int initialValue):用给定的方向、最大值、最小值和初始值构造一个水平滑动条。direction参数是SwingConstants.HORIZONTAL或SwingConstants.VERTICAL之一。默认为水平。滑动条的最小值、初始值和最大值默认为0、50和100
- void setPaintTicks(boolean b):如果b为true,显示刻度
- void setMajorTickSpacing(int units)
- void setMinorTickSpacing(int nuits):用给定的滑动条单位的倍数设置最大刻度和最小刻度
- void setPaintLabels(boolean b):设置用做刻度标签的组件
- void setSnapToTicks(boolean b):如果b是true,每一次调整后滑块都要对齐到最接近的刻度
- void setPaintTrack(boolean b):如果b是true,显示滑动条滑动的轨迹
5、菜单
位于窗口顶部的莱单栏(menu bar)包括了下拉菜单的名
字。点击一个名字就可以打开包含菜单项(menu item)和子菜单(submenu)的菜单。当用户点击菜单项时,所有的菜单都会被关闭并且将一条消息发送给程序。
5.1、菜单构建
构建菜单是一件非常容易的事情。首先要创建一个菜单栏:
var menuBar = new JMenuBar();
菜单栏是一个可以添加到任何位置的组件。正常情况下会放置在窗体的顶部。可以调用setJMenuBar方法将菜单栏添加到这里:
frame.setJMenuBar(menuBar);
需要为每个菜单创建一个菜单对象:
var editMenu = new JMenu("Edit");
然后将顶层菜单添加到菜单栏中:
menuBar.add(editMenu);
向菜单对象中添加菜单项、分隔符和子菜单:
var pasteItem new JMenuItem("Paste"); editMenu.add(pasteItem); editMenu.addSeparator(); JMenu optionsMenu =...;//a submenu editMenu.add(optionsMenu);
当用户选择菜单时,将触发一个动作事件。需要为每个菜单项安装一个动作监听器:
ActionListener listener =... pasteItem.addActionListener(listener);
JMenu.add(String s)方法可以很方便地将菜单项增加到菜单的尾部,例如:
editMenu.add("Paste");
add方法返回创建的菜单项。可以获取这个菜单项,并添加监听器,如下所示:
JMenuItem pasteItem = editMenu.add("Paste"); pasteItem.addActionListener(listener);
在通常情况下,菜单项触发的命令也可以通过其他用户界面元素(如工具条按钮)激活。要定义一个实现Action接
口的类,为此通常会扩展便利的AbstractAction类,在AbstractAction对象的构造器中指定菜单项标签,并且覆盖actionPerformed方法来获得菜单动作处理器。例如:
var exitAction new AbstractAction("Exit") { //menu item text goes here public void actionPerformed(ActionEvent event){ //action code goes here System.exit(0); } };
然后将动作添加到菜单中:
JMenuItem exitItem = fileMenu.add(exitAction);
这个命令利用动作名将一个菜单项添加到菜单中。这个动作对象将作为它的监听器。上面这条语句是下面两条语句的快捷形式:
var exitItem = new JMenuItem(exitAction); fileMenu.add(exitItem)
javax.swing.JMenu:
- JMenu(String label):用给定标签构造一个菜单
- JMenuItem add(JMenuItem item):添加一个菜单项(或一个菜单)
- JMenuItem add(String label):将一个有给定标签的菜单项添加到菜单中,并返回这个菜单项
- JMenuItem add(Action a):将一个有给定动作的菜单项添加到菜单中,并返回这个菜单项
- void addSeparator():将一个分隔行添加到菜单中
- JMenuItem insert(JMenuItem menu, int index):将一个新菜单项(或子菜单)添加到菜单的指定索引位置
- JMunuItem insert(Action a, int index):将有指定动作的新菜单项增加到菜单的指定索引位置
- void insertSeparator(int index):将一个分隔符添加到菜单中
- void remove(int index)
- void remove(JMenuItem item):从菜单中删除指定的菜单项
javax.swing.JMenuItem:
- JmenuItem(String label):用给定标签构造一个菜单项
- JMenuItem(Action a):为给定动作构造一个菜单项
javax.swing.AbstractButton:
- void setAction(Action a):为了这个按钮或菜单项设置动作
javax.swing.JFrame:
- void setJMenuBar(JMenuBar memubar)**:为这个窗体设置菜单栏
5.2、菜单项中的图标
菜单项与按钮很相似。实际上,JMenuItem类扩展了AbstractButton类。与按钮一样,菜单可以只包含文本标签、只包含图标,或者两者都包含。可以使用JMenuItem(String,Icon)或者JMenuItem(Icon)构造器为菜单指定一个图标,也可以使用JMenuItem类从AbstractButton类继承的setIcon方法指定一个图标。例如:
var cutItem = new JMenuItem("Cut",new ImageIcon("cut.gif"));
在默认情况下,菜单项文本放在图标的右侧。如果喜欢将文本放置在左侧,可以调用JMenuItem类从AbstractButton类继承的setHorizontalTextPosition方法。例如:
cutItem.setHorizontalTextPosition(SwingConstants.LEFT);
这个调用把菜单项文本移动到图标的左侧。也可以为动作增加一个图标:
cutAction.putValue(Action.SMALL ICON,new ImageIcon("cut.gif"));
当使用动作构造菜单项时,Action.NAME值将会作为菜单项的文本,而Action.SMALL ICON将会作为图标。
或者,可以在AbstractAction构造器中设置图标:
cutAction = new AbstractAction("Cut",new ImageIcon("cut.gif")) { public void actionPerformed(ActionEvent event){ ... } };
javax.swing.JMenuItem:
- JMenuItem(String label, Icon icon):用给定的标签和图标构造一个菜单项
javax.swing.AbstractButton:
- void setHorizontalTextPosition(int pos):设置文本相对于图标的水平位置。pos参数是SwingConstants.RIGHT(文本在图标的右侧)或SwingConstants.LEFT
javax.swing.AbstractAction:
- AbstractAction(String name, Icon smallIcon):用给定的名字和图标构造一个抽象动作
5.3、复选框和单选按钮菜单项
复选框和单选按钮菜单项会在菜单名旁边显示了一个复选框或一个单选按钮。当用户选择一个菜单项时,菜单项就会自动地在选择和未选择间进行切换。
除了按钮装饰外,复选框和单选按钮菜单项同其他菜单项的处理一样。例如,可以如下创建复选框菜单项:
var readonlyItem = new JCheckBoxMenuItem("Read-only"); optionsMenu.add(readonlyItem);
单选按钮菜单项与普通单选按钮的工作方式一样,必须将它们加入到按钮组中。当按钮组中的一个按钮被选中时,其他按钮都自动地变为未选中。
var group = new ButtonGroup(); var insertItem = new JRadioButtonMenuItem("Insert"); insertItem.setSelected(true); var overtypeItem = new JRadioButtonMenuItem("Overtype"); group.add(insertItem); group.add(overtypeItem); optionsMenu.add(insertItem) optionsMenu.add(overtypeItem);
使用这些菜单项,不需要立刻得到用户选择菜单项的通知。实际上,可以使用isSelected方法来测试菜单项的当前状态(当然,这意味着应该保留这个菜单项的一个引用,保存在一个实例字段中)。可以使用setSelected方法设置状态。
javax.swing.JCheckBoxMenuItem:
- JCheckBoxMenuItem(String label):用给定的标签构造一个复选框菜单项
- JCheckBoxMenuItem(String label, boolean state):用给定的标签和给定的初始状态(true为选定)构造一个复选框菜单
javax.swing.JRadioButtonMenuItem:
- JRadioButtonMenuItem(String label):用给定的标签构造一个单选按钮菜单项
- JRadioButtonMenuItem(String label, boolean state):用给定的标签和给定的初始状态(true为选定)构造一个单选按钮菜单项
javax.swing.AbstractButton:
- boolean isSelected()
- void setSelected(boolean state):获取或设置这个菜单项的选择状态(true为选中)
5.4、弹出菜单
弹出菜单(pop-up menu)是不固定在菜单栏中随处浮动的菜单。
创建一个弹出菜单与创建一个常规菜单的方法类似,但
是弹出菜单没有标题。
var popup = new JPopupMenu();
然后用常规的方法添加菜单项:
var item = new JMenultem("Cut"); item.addActionListener(listener); popup.add(item);
弹出菜单并不像常规菜单栏那样总是显示在窗体的顶部,必须调用show方法显式地显示弹出菜单。需要指定父组件,并使用父组件的坐标系统指定弹出菜单的位置。例如:
popup.show(panel,x,y);
通常,你可能希望当用户点击某个鼠标键时弹出菜单,这就是所谓的弹出式触发器(pop-up trigger)。在Windows或者Linux中,弹出式触发器是鼠标次键(通常是右键)。要想在用户点击一个组件时弹出菜单,只需要调用方法:
component.setComponentPopupMenu(popup);
偶尔也可能需要把一个组件放在另一个带弹出菜单的组件中。通过调用以下方法,这个子组件可以继承父组件的弹出菜单:
child.setInheritsPopupMenu(true);
javax.swing.JPopuMenu:
- void show(Component c, int x, int y):在组件c上显示弹出菜单,组件c的左上角坐标为(x,y)(c的坐标空间内)
- boolean isPopupTrigger(MouseEvent event):如果鼠标事件是弹出菜单触发器,则返回true
java.awt.event.MouseEvent:
- boolean isPopupTrigger():如果鼠标事件是弹出菜单触发器,则返回true
javax.swing.JComponent:
- JPopupMenu getComponentPopupMenu()
- void setComponentPopupMenu(JPopupMenu popup):获取或设置用于这个组件的弹出菜单
- boolean getInheritsPopupMenu()
- void setInheritsPopupMenu(boolean b):获取或设置inheritsPopupMenu特性。如果这个特性已设置或这个组件的弹出菜单为null,就使用其父组件的弹出菜单
5.5、键盘助记符和加速器
对于有经验的用户来说,通过键盘助记符选择菜单项确实非常便捷。可以通过在菜单项的构造器中指定一个助记字母来为菜单项设置键盘助记符:
var aboutItem = new JMenuItem("About",'A');
键盘助记符会在菜单中自动显示,助记字母下面有一条下划线。例如,在上面的例子中,菜单项中的标签显示为“About”,字母A带有一个下划线。菜单显示时,用户只需要按下“A”键就可以选择这个菜单项(如果助记字母不在菜单字符串中,同样可以按下这个字母选择菜单项,不过助记符不会在菜单中,同样可以按下这个字母选择菜单项,不过助记符不会在菜单中显示。
javax.swing.JMenuItem:
- JMenuItem(String label,int mnemonic):用给定的标签和助记符构造一个菜单项。
- void setAccelerator(KeyStroke k):将k键设置为这个菜单项的加速器。加速器键显示在标签旁边。
javax.swing.AbstractButton:
- void setMnemonic(int mnemonic):设置按钮的助记字符。该字符会在标签中加下划线显示。
- void setDisplayedMnemonicIndex(int index):设置按钮文本中加下划线字符的索引。如果不希望第一个出现的助记字符带下划线,就可以使用这个方法。
5.6、启用和禁用菜单项
有些时候,某个特定的菜单项可能只在某种特定的环境下才能选择。例如,当文档以只读方式打开时,Save菜单项就没有意义。当然,可以使用JMenu.remove方法将这个菜单项从菜单中删掉,但用户会对内容不断变化的菜单感到奇怪。实际上,最好禁用这个菜单项,以免触发暂时不适用的命令。被禁用的菜单项显示为灰色,不允许选择。
启用或禁用菜单项需要调用setEnabled方法:
saveItem.setEnabled(false);
启用和禁用菜单项有两种策略。每次环境发生变化时,就可以对相关的菜单项或动作调用setEnabled。例如:一旦文档以只读方式打开,可以找到并禁用Save和Save As菜单项。另一种方法是在显示菜单之前禁用这些菜单项。为此,必须为“菜单选中”事件注册一个监听器。javax.swing.event包定义了一个MenuListener接口,它包含三个方法:
- void menuSelected(MenuEvent event)
- void menuDeselected(MenuEvent event)
- void menuCanceled(MenuEvent event)
menuSelected方法在菜单显示之前调用,所以可以用这个方法禁用或启用菜单项。下面的代码显示了选中只读复选框菜单项时如何禁用Save和Save As动作。
public void menuSelected(MenuEvent event) { saveAction.setEnabled(!readonlyItem.isSelected()); saveAsAction.setEnabled(!readonlyItem.isSelected()); }
javax.swing.JMenuItem:
- void setEnabled(boolean b):启用或禁用菜单项。
javax.swing.event.MenuListener:
- void menuSelected(MenuEvent e):在菜单被选择但尚未打开时调用
- void menuDeselected(MenuEvent e):在菜单被取消选择并且已经关闭之后调用
- void menuCanceled(MenuEvent e):当菜单被取消时调用。例如,用户点击菜单以外的区域
public class MenuFrame extends JFrame { private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; private Action saveAction; private Action saveAsAction; private JCheckBoxMenuItem readonlyItem; private JPopupMenu popup; class TestAction extends AbstractAction { public TestAction(String name) { super(name); } @Override public void actionPerformed(ActionEvent e) { System.out.println(getValue(Action.NAME) + " selected."); } } public MenuFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JMenu fileMenu = new JMenu("File"); fileMenu.add(new TestAction("New")); JMenuItem openItem = fileMenu.add(new TestAction("Open")); openItem.setAccelerator(KeyStroke.getKeyStroke("ctrl 0")); fileMenu.addSeparator(); saveAction = new TestAction("Save"); JMenuItem saveItem = fileMenu.add(saveAction); saveItem.setAccelerator(KeyStroke.getKeyStroke("ctrl S")); saveAsAction = new TestAction("Save As"); fileMenu.add(saveAction); fileMenu.addSeparator(); fileMenu.add(new AbstractAction("Exit") { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); readonlyItem = new JCheckBoxMenuItem("Read-only"); readonlyItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean saveOk = !readonlyItem.isSelected(); saveAction.setEnabled(saveOk); saveAction.setEnabled(saveOk); } }); ButtonGroup group = new ButtonGroup(); JRadioButtonMenuItem insertItem = new JRadioButtonMenuItem("Insert"); insertItem.setSelected(true); JRadioButtonMenuItem overtypeItem = new JRadioButtonMenuItem("Overtype"); group.add(insertItem); group.add(overtypeItem); TestAction cutAction = new TestAction("Cut"); cutAction.putValue(Action.SMALL_ICON, new ImageIcon("icon.png")); TestAction copyAction = new TestAction("Copy"); copyAction.putValue(Action.SMALL_ICON, new ImageIcon("icon.png")); TestAction pasteAction = new TestAction("Paste"); pasteAction.putValue(Action.SMALL_ICON, new ImageIcon("icon.png")); JMenu editMenu = new JMenu("Edit"); editMenu.add(cutAction); editMenu.add(copyAction); editMenu.add(pasteAction); JMenu optionMenu = new JMenu("Options"); optionMenu.add(readonlyItem); optionMenu.addSeparator(); optionMenu.add(insertItem); optionMenu.add(overtypeItem); editMenu.addSeparator(); editMenu.add(optionMenu); JMenu helpMenu = new JMenu("Help"); helpMenu.setMnemonic('H'); JMenuItem indexItem = new JMenuItem("Index"); indexItem.setMnemonic('I'); helpMenu.add(indexItem); TestAction aboutAction = new TestAction("About"); aboutAction.putValue(Action.MNEMONIC_KEY, new Integer('A')); helpMenu.add(aboutAction); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(helpMenu); popup = new JPopupMenu(); popup.add(cutAction); popup.add(copyAction); popup.add(pasteAction); JPanel panel = new JPanel(); panel.setComponentPopupMenu(popup); add(panel); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new MenuFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
5.7、工具条
工具条是一个按钮条,通过它可以快速访问程序中最常用的命令,工具条的特殊之处在于可以将它随处移动。可以将它拖曳到窗体的四个边框上。释放鼠标按钮后,工具条将会落在新的位置上。
工具条甚至可以完全脱离窗体。这种分离的工具条包含在自己的窗体中,关闭包含分离式工具条的窗体时,工具条会回到原窗体中。
可以将组件添加到工具条:
JToolBar toolbar = new JToolBar(); toolbar.add(blueButton);
JToolBar类还有一个添加Action对象的方法,可以用Action对象填充工具条,如下所示:
toolbar.add(blueAction);
这个动作的小图标将会出现在工具条中。可以用分隔符将按钮分组:
toolbar.addSeparator();
还可以指定工具条的标题,当工具条未锁定时就会显示这个标题:
toolbar new JToolBar(titleString);
在默认情况下,工具条初始为水平的。如果希望工具条初始是垂直的,可以使用以下代码:
toolbar new JToolBar(SwingConstants.VERTICAL)
或者
toolbar = new JToolBar(titleString,SwingConstants.VERTICAL)
按钮是工具条中最常用的组件。不过对于工具条中可以增加哪些组件并没有任何限制。例如,可以在工具条中加入组合框。
5.8、工具提示
工具条有一个缺点,这就是用户常常需要猜测工具条中小图标的含义。为了解决这个问题,用户界面设计者发明了工具提示(tooltip)。当光标在一个按钮上停留片刻时,工具提示就会被激活。工具提示文本显示在一个有颜色的矩形里。当用户移开鼠标时,工具提示就会消失。
在Swing中,可以调用setToolTipText方法将工具提示添
加到任何JComponent上:
exitButton.setToolTipText("Exit");
或者,如果使用Action对象,可以用SHORT_DESCRIPTION关联工具提示:
exitAction.putValue(Action.SHORT DESCRIPTION,"Exit");
javax.swing.JToolBar:
- JToolBar()
- JToolBar(String titleString)
- JToolBar(int orientation)
- JToolBar(String titleString,int orientation):用给定的标题字符串和方向构造一个工具条。orientation可以是SwingConstants.HORIZONTAL(默认)或SwingConstants.VERTICAL
- JButton add(Action a):在工具条中用给定动作的名称、图标、简要说明和动作回调构造一个新按钮,并把这
个按钮增加到工具条末尾 - void addSeparator():将一个分隔符添加到工具条的末尾
javax.swing.JComponent:
- void setToolTipText(String text):设置当鼠标停留在组件上时要作为工具提示显示的文本
6、复杂的布局管理
自从Java 1.0以来,AWT就含有网格包布局(grid bag layout),这种布局将组件按行和列排列。行和列的大小可以灵活改变,而且组件可以跨多行多列。这种布局管理器非常灵活,但也非常复杂。仅仅提到“网格包布局”一词就会让一些Java程序员胆战心惊。
Swig设计者有一个失败的尝试:为了设计一个布局管理器,能够将程序员从使用网格包布局的麻烦中解脱出来,他们提出了一种箱式布局(box layout)。根据BoxLayout类的JDK文档所述:“采用水平和垂直[sic]的不同组合嵌套多个面板可以获得与GridBagLayout类似的效果,而且降低了复杂度。”不过,由于每个箱子是独立放置的,所以不能使用箱式布局排列水平和垂直方向都相邻的组件。
Java 1.4还做了一个尝试:设计网格包布局的一种替代布局一弹性布局(spring layout)。这种布局使用假想的弹簧连接一个容器中的组件。当容器改变大小时,弹簧会伸展
或收缩,从而调整组件的位置。这听起来似乎很枯燥而且让人很困惑,其实也确实如此。弹性布局很快就变得含糊不清。
NetBeans IDE结合了一个布局工具(名为“Matisse”)和一个布局管理器。用户界面设计者可以使用工具将组件拖放到一个容器中,并指出组件的排列方式。工具再将设计者的意图转换成组布局管理器可以理解的指令。与手动编写布局管理代码相比,这样要便捷得多。
6.1、网格包布局
网格包布局是所有布局管理器之母。可以将网格包布局看成是没有任何限制的网格布局。在网格包布局中,行和列的大小可以改变。可以将相邻的单元合并以适应较大的组件(很多字处理器以及HTML为表格提供了类似的功能:可以先建立一个表格,然后根据需要合并相邻的单元格)。组件不需要填充整个单元格区域,而且可以指定它们在单元格内的对齐方式。
考虑上图所示的字体选择器,其中包含下面的组件:
- 两个用于指定字体和字体大小的组合框
- 两个组合框的标签
- 两个用于选择粗体和斜体的复选框
- 一个用于显示示例字符串的文本区
现在将容器分解为由单元格组成的网格,(行和列的大小不必相同)。每个复选框横跨两列,文本区跨四行:
为了向网格包管理器描述这个布局,需要完成以下过程:
- 创建一个GridBagLayout类型的对象。不需要指定底层网格的行数和列数。实际上,布局管理器会根据后面所给的信息猜测行数和列数。
- 将这个GridBagLayout对象设置为组件的布局管理器。
- 对于每个组件,创建一个GridBagConstraints类型的对象。设置GridBagConstraints对象的字段值来指定组件在网格包中如何布局。
- 最后,通过下面的调用为各个组件增加约束:
add(component,constraints);
下面给出所需的示例代码:
var layout = new GridBagLayout(); panel.setLayout(layout); var constraints = new GridBagConstraints(); constraints.weightx 100; constraints.weighty 100; constraints.gridx 0; constraints.gridy 2; constraints.gridwidth 2; constraints.gridheight 1; panel.add(component,constraints);
1)、gridx、gridy、gridwidth和gridheight参数
gridx、gridy、gridwidth和gridheight约束定义了组件在网格中的位置:
- gridx和gridy值指定了所添加组件左上角的行、列位置。
- gridwidth和gridheight值确定组件占据的行数和列数。
网格的坐标从0开始。具体地,gridx=0
和gridy=0
指示最左上角。例如,示例程序中,文本区的gridx=2
,gridy=0
。这是因为这个文本区起始于0行2列(即第3列),它的girdwidth=1, gridheight=4
,因为它占据4行1列。
2)、增量字段
总是需要为网格包布局中的每个区域设置增量字段(weightx和weighty)。如果将增量设置为0,那么这个区域在该方向上永远为初始大小,不会扩大或收缩。
另一方面,如果将所有区域的增量都设置为0,容器就会挤在所分配区域的中间,而不会拉伸来填充空间。
从概念上讲,增量参数的问题在于增量是行和列的属性,而不是各个单元格的属性。但你却需要为单元格指定增量,因为网格包布局并不对外提供行和列。行和列的增量等于每行或每列中单元格增量的最大值。因此,如果想让一行或一列的大小保持不变,就需要将其中所有组件的增量都设置为0。
注意,增量并不实际给出列的相对大小。当容器超过首选大小时,增量会指出“闲散”空间按什么比例分配给各个区域。这么说不太直观。我们建议将所有的增量设置为10,然后运行程序,查看布局情况。调整这个对话框的大小,再来看行和列是如何调整的。如果发现某行或某列不应该扩大,就将那一行或那一列中的所有组件的增量设置为0。也可以调整为其他增量值,但是那么做的意义并不大。
3)、fill和anchor参数
如果不希望组件拉伸至填满整个区域,就需要设置fill约束。这个参数有4个可能的取值:
- GridBag Constraints.NONE
- GridBagConstraints.HORIZONTAL
- GridBagConstraints.VERTICAL
- GridBagConstraints.BOTH
如果组件没有填充整个区域,可以通过设置anchor字段指定它在这个区域中的位置。有效值为:
- GridBagConstraints.CENTER(默认值)
- GridBagConstraints.NORTH
- GridBagConstraints.NORTHEAST
- GridBagConstraints.EAST
4)、填充
可以通过设置GridBagConstraints的insets字段在组件周围增加额外的空白区域。通过设置Insets对象的left、top、right和bottom值指定组件周围的空间大小。这被称作外部填充(external padding)。
ipadx和ipady值可以指定内部填充(internal padding)。这两个值会加到组件的最小宽度和最小高度上,这样可以保证组件不会收缩至其最小尺寸以下。
5)、指定gridx、gridy、gridwidth和gridheight参数的备用方法
AWT文档建议不要将gridx和gridy设置为绝对位置,而应该将它们设置为常量GridBagConstraints.RELATIVE
。然后,按照标准的顺序将组件添加到网格包布局中,即首先在第一行从左向右增加,然后再转到新的一行,依此类推。
还需要为gridheight和gridwidth字段提供适当的值来指定组件所跨的行数和列数。不过,如果组件扩展至最后一行或最后一列,则不需要指定具体的数,而是可以使用常量
GridBagConstraints.REMAINDER,这样会告诉布局管理器这个组件是该行上的最后一个组件。
这种方案看起来是可行的,但似乎有点笨拙。这是因为这样做会对布局管理器隐藏具体的位置信息,并希望它能够重新发现这些信息。
6)、网格包布局技巧
在实际中,利用下面的技巧,可以让网格包布局的使用没那么麻烦:
- 在纸上画出组件布局草图。
- 找出一个网格,小组件分别包含在一个单元格内,更大的组件会跨越多个单元格。
- 用0,1,2,3…标识网格的行和列。现在可以得出gridx、gridy、gridwidth和gridheight的值。
- 对于每个组件,需要考虑以下问题:是否需要水平或者垂直填充它所在的单元格?如果不需要,希望如何对齐?这些就是fill和anchor参数的设置。
- 将所有的增量设置为100。不过,如果希望某行或某列始终保持默认的大小,就将这行或这列中所有组件的weightx和weighty设置为0。
- 编写代码。仔细地检查GridBagConstraints的设置。错误的约束可能会破坏整个布局。
- 编译并运行,你会看到满意的布局。
7)、使用帮助类管理网格包约束
网格包布局最乏味的方面就是要编写代码设置约束。为此,大多数程序员会编写帮助函数或者一个小帮助类。下面将在字体对话框示例的代码后面给出一个帮助类。这个类有以下特性:
- 名字简短:GBC而不是GridBagConstraints。
- 它扩展了GridBagConstraints,因此常量可以使用更短的名字,如GBC.EAST。
- 当添加组件时,使用GC对象,如:
add(component,new GBC(1,2))
有两个构造器可以用来设置最常用的参数:gridx和gridy,或者gridx、gridy、gridwidth和gridheight
add (component,new GBC(1,2,1,4))
对于以xy值对出现的字段,提供了便捷的设置方法:
add(component,new GBC(1,2).setweight(100,100));
设置器方法将返回this,所以可以串链这些方法调用:
add(component,new GBC(1,2).setAnchor(GBC.EAST).setWeight(100,100));
setInsets方法将构造Insets对象。要想获取1个像素的insets,可以调用:
add(component,new GBC(1,2).setAnchor(GBC.EAST).setInsets(1));
8)、完整示例
public class GBC extends GridBagConstraints { public GBC(int gridx, int gridy) { this.gridx = gridx; this.gridy = gridy; } public GBC(int gridx, int gridy, int gridwidth, int gridheight) { this.gridx = gridx; this.gridy = gridy; this.gridwidth = gridwidth; this.gridheight = gridheight; } public GBC setAnchor(int anchor) { this.anchor = anchor; return this; } public GBC setFill(int fill) { this.fill = fill; return this; } public GBC setWeight(double weightx, double weighty) { this.weightx = weightx; this.weighty = weighty; return this; } public GBC setInsets(int distance) { this.insets = new Insets(distance, distance, distance, distance); return this; } public GBC setInsets(int top, int left, int bottom, int right) { this.insets = new Insets(top, left, bottom, right); return this; } public GBC setIpad(int ipadx, int ipady) { this.ipadx = ipadx; this.ipady = ipady; return this; } }
public class FontFrame extends JFrame { public static final int TEXT_ROWS = 10; public static final int TEXT_COLUMS = 20; private JComboBox<String> face; private JComboBox<Integer> size; private JCheckBox bold; private JCheckBox italic; private JTextArea sample; public FontFrame() { GridBagLayout layout = new GridBagLayout(); setLayout(layout); ActionListener listener = event -> updateSample(); JLabel faceLabel = new JLabel("Face:"); face = new JComboBox<>(new String[] {"Serif", "SansSerif", "Monospaced", "Dialog", "DialogInput"}); face.addActionListener(listener); JLabel sizeLabel = new JLabel("Size:"); size = new JComboBox<>(new Integer[]{8, 10, 12, 15, 18, 24, 36, 48}); size.addActionListener(listener); bold = new JCheckBox("Bold"); bold.addActionListener(listener); italic = new JCheckBox("Italic"); italic.addActionListener(listener); sample = new JTextArea(TEXT_ROWS, TEXT_COLUMS); sample.setText("The quick brown fox jumps over the lazy dog"); sample.setEditable(false); sample.setLineWrap(true); sample.setBorder(BorderFactory.createEtchedBorder()); add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST)); add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1)); add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST)); add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1)); add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100)); pack(); updateSample(); } public void updateSample() { String fontFace = (String) face.getSelectedItem(); int fontStyle = (bold.isSelected() ? Font.BOLD : 0) + (italic.isSelected() ? Font.ITALIC : 0); int fontSize = size.getItemAt(size.getSelectedIndex()); Font font = new Font(fontFace, fontStyle, fontSize); sample.setFont(font); sample.repaint(); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new FontFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
9)、API
java.awt.GridBagConstraints:
- int gridx,gridy:指定单元格的起始行和列。默认值为0
- int gridwidth,gridheight:指定单元格的行和列的范围。默认值为1
- double weightx,weighty:指定单元格扩大的容量。默认值为0
- int anchor:表示组件在单元格内的对齐方式。可以选择的绝对位置包括:
- NORTHWEST
- NORTH
- NORTHEAST
- WEST
- CENTER
- EAST
- SOUTHWEST
- SOUTH
- SOUTHEAST
- 或者各个方向上的相对位置:
- FIRST_LINE_START
- LINE_START
- FIRST_LINE_END
- PAGE_START
- CENTER
- PAGE_END
- LAST_LINE_START
- LINE_END
- LAST_LINE_END
- 如果你的应用要本地化为从右向左或者从上向下排列文本,就应该使用后者。默认值为CENTER。
- int fill:指定组件在单元格内的填充行为,取值为NONE、BOTH、HORIZONTAL或者VERTICAL。默认值
为NONE - int ipadx,ipady:指定组件周围的“内部”填充。默认值为0
- Insets insets:指定组件边框周围的“外部”填充。默认为不填充。
- GridBagConstraints(int gridx,int gridy,int gridwidth,int gridheight,double weightx,
double weighty,int anchor,int fill,Insets insets,int ipadx,int ipady):用参数中给定的所有字段值构造GridBagConstraints。这个构造器只用于自动代码生成器,因为它会让你的源代码很难阅读
6.2、定制布局管理器
可以设计你自己的LayoutManager类以一种特殊的方式管理组件。作为一个有趣的例子,可以将容器中的组件排列成一个圆形。
定制布局管理器必须实现LayoutManager接口,并且需要覆盖下面5个方法:
void addLayoutComponent(String s,Component c) void removeLayoutComponent(Component c) Dimension preferredLayoutSize(Container parent) Dimension minimumLayoutSize(Container parent) void layoutContainer(Container parent)
public class CircleLayout implements LayoutManager { private int minWidth = 0; private int minHeight = 0; private int preferredWidth = 0; private int preferredHeight = 0; private boolean sizesSet = false; private int maxComponentWidth = 0; private int maxComponentHeight = 0; @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } @Override public Dimension preferredLayoutSize(Container parent) { setSizes(parent); Insets insets = parent.getInsets(); int width = preferredWidth + insets.left + insets.right; int height = preferredHeight + insets.top + insets.bottom; return new Dimension(width, height); } @Override public Dimension minimumLayoutSize(Container parent) { setSizes(parent); Insets insets = parent.getInsets(); int width = minWidth + insets.left + insets.right; int height = minHeight + insets.top + insets.bottom; return new Dimension(width, height); } @Override public void layoutContainer(Container parent) { setSizes(parent); Insets insets = parent.getInsets(); int containerWidth = parent.getSize().width - insets.left - insets.right; int containerHeight = parent.getSize().height - insets.top - insets.bottom; int xcenter = insets.left + containerWidth / 2; int ycenter = insets.top + containerHeight / 2; int xradius = (containerWidth - maxComponentWidth) / 2; int yradius = (containerHeight - maxComponentHeight) / 2; int radius = Math.min(xradius, yradius); int n = parent.getComponentCount(); for (int i = 0; i < n; i++) { Component c = parent.getComponent(i); if (c.isVisible()) { double angle = 2 * Math.PI * i / n; int x = xcenter + (int)(Math.cos(angle) * radius); int y = ycenter + (int)(Math.sin(angle) * radius); Dimension d = c.getPreferredSize(); c.setBounds(x - d.width / 2, y - d.height / 2, d.width, d.height); } } } public void setSizes(Container parent) { if (sizesSet) { return; } int n = parent.getComponentCount(); preferredWidth = 0; preferredHeight = 0; minWidth = 0; minHeight = 0; maxComponentWidth = 0; maxComponentHeight = 0; for (int i = 0; i < n; i++) { Component c = parent.getComponent(i); if (c.isVisible()) { Dimension d = c.getPreferredSize(); maxComponentWidth = Math.max(maxComponentWidth, d.width); maxComponentHeight = Math.max(maxComponentHeight, d.height); preferredWidth += d.width; preferredHeight += d.height; } } minWidth = preferredWidth / 2; minHeight = preferredHeight / 2; sizesSet = true; } }
public class CircleLayoutFrame extends JFrame { public CircleLayoutFrame() { setLayout(new CircleLayout()); add(new JButton("Yellow")); add(new JButton("Blue")); add(new JButton("Red")); add(new JButton("Green")); add(new JButton("Orange")); add(new JButton("Fuchsia")); add(new JButton("Indigo")); pack(); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new CircleLayoutFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
java.awt.LayoutManager:
- void addLayoutComponent(String name,Component comp):将组件添加到布局中
- void removeLayoutComponent(Component comp):从布局删除一个组件
- Dimension preferredLayoutSize(Container cont):返回这个布局中容器的首选尺寸
- Dimension minimumLayoutSize(Container cont):返回这个布局中容器的最小尺寸
- void layoutContainer(Container cont):在容器中摆放的组件
7、对话框
到目前为止,所有的用户界面组件都显示在应用程序创建的窗体窗口中。如果编写运行在Web浏览器中的applet,这是最常见的情况。但是,如果你要编写应用程序,通常就需要有不同的对话框向用户显示信息或者获取用户提供的信息。
与大多数的窗口系统一样,AWT也分为模式对话框和无模式对话框。所谓模式对话框是指在结束对它的处理之前,不允许用户与应用程序的其余窗口进行交互。模式对话框主要用于在程序继续运行之前获取用户提供的信息。例如,当用户想要读取一个文件时,就会弹出一个模式文件对话框。用户必须给定一个文件名,然后程序才能够开始读操作。只有用户关闭这个模式对话框之后,应用才能够继续执行。
无模式对话框允许用户同时在对话框和应用程序的其他部分输入信息。使用无模式对话框的一个例子就是工具条。只要需要,工具条可以停靠在任何地方,而且用户可以根据需要同时与应用窗口和工具条进行交互。
7.1、选项对话框
Swing有一组现成的简单对话框,足以让用户提供一些信息。JOptionPane有4个用于显示这些对话框的静态方法:
- showMessageDialog:显示一条消息并等待用户点击OK;
- showConfirmDialog:显示一条消息并等待用户确认(如OK/Cancel):
- showOptionDialog:显示一条消息并获得用户在一组选项中的选择;
- showInputDialog:显示一条消息并获得用户输人的一行文本。
对话框有以下组件:
- 一个图标
- 一条消息
- 一个或多个选项按钮
输入对话框有一个用于接收用户输人的额外组件。这可能是一个文本域,用户可以输入任何的字符串,也可能是一个组合框,用户可以从中选择一项。
这些对话框的具体布局和为标准消息类型选择的图标都取决于可插接式观感。左侧的图标取决于下面5种消息类型:
- ERROR_MESSAGE
- INFORMATION_MESSAGE
- WARNING_MESSAGE
- QUESTION_MESSAGE
- PLAIN_MESSAGE
PLAIN_MESSAGE类型没有图标。每个对话框类型都有一个方法,可以用来提供自己的图标,以替代原来的图标。可以为每个对话框类型指定一条消息。这里的消息既可以是字符串、图标、用户界面组件,也可以是其他类型的对象。可以如下显示消息对象:
- String:绘制字符串;
- Icon:显示图标;
- Component:显示组件;
- Object[]:显示数组中的所有对象,依次叠加
- 任何其它对象:调用toString()方法来显示结果字符串
当然,提供字符串消息是目前为止最常见的情况,而提供一个Component会带来更大的灵活性,这是因为可以让paintComponent方法绘制你想要的任何内容。位于底部的按钮取决于对话框类型和选项类型。当调用showMessageDialog和showInputDialog时,只能看到一组标准按钮(分别是OK和OK/Cancel)。当调用showConfirmDialog时,可以在下面四种选项类型中选择:
- DEFAULT_OPTION
- YES_NO_OPTION
- YES_NO_CANCEL_OPTION
- OK_CANCEL_OPTION
使用showOptionDialog时,可以指定一组任意的选项。你要提供一个对象数组作为选项。
每个数组元素会如下显示:
- String:创建一个按钮,使用字符串作为标签;
- Icon:创建一个按钮,使用图标作为标签;
- Component:显示这个组件;
- 其他类型的对象:应用toString方法,然后创建一个按钮,用结果字符串作为标签。
这些方法的返回值如下:
- showMessageDialog:无;
- showConfirmDialog:表示所选项的一个整数;
- show0 ptionDialog:表示所选项的一个整数;
- showInputDialog:用户提供或选择的字符串;
showConfirmDialog和showOptionDialog返回一个整数,表示用户选择了哪个按钮。对于选项对话框来说,这个值就是所选选项的索引值,或者是CLOSED0_PTION(此时用户没有选择选项,而是关闭了对话框)。对于确认对话框,返回值可以是以下值之一:
- OK_OPTION
- CANCEL_OPTION
- YES_OPTION
- NO_OPTION
- CLOSED_OPTION
这些选项看上去让人有些困惑,实际上非常简单。步骤如下:
- 选择对话框的类型(消息、确认、选项或者输入)。
- 选择图标(错误、信息、警告、问题、无或者自定义)。
- 选择消息(字符串、图标、自定义组件或者它们的集合)。
- 对于确认对话框,选择选项类型(默认、Yes/No、Yes/No/Cancel或者OK/Cancel)。
- 对于选项对话框,选择选项(字符串、图标或者自定义组件)和默认选项。
- 对于输入对话框,选择文本域或者组合框。
- 调用JOptionPane API中的相应方法。
例如,对话框显示了一条消息,并请求用户确认或者取消。这是一个确认对话框。图标是一个问题图标,消息是字符串,选项类型是OK_CANCEL_OPTION。调用如下:
int selection = JOptionPane.showConfirmDialog(parent, "Message","Title", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (selection =JOptionPane.OK OPTION){ ... }
javax.swing.JoptionPane:
- static void showMessageDialog(Component parent,Object message,String title,int messageType,
Icon icon) - static void showMessageDialog(Component parent,Object message,String title,int messageType)
static void showMessageDialog(Component parent,Object message) - static void showInternalMessageDialog(Component parent,Object message,String title,int
messageType,Icon icon) - static void showInternalMessageDialog(Component parent,Object message,String title,int
messageType) - static void showInternalMessageDialog(Component parent,object message):显示一个消息对话框或者一个内部消息对话框(内部对话框完全显示在其父组件窗体内)。父组件可以为ul1。显示在对话框中的消息可以是字符串、图标、组件或者它们的一个数组。messageType参数取值为ERROR MESSAGE、INFORMATION MESSAGE、WARNING_MESSAGE、QUESTION MESSAGE和PLAIN MESSAGE之一
- static int showConfirmDialog(Component parent,Object message,String title,int optionType,
int messageType,Icon icon) - static int showConfirmDialog(Component parent,Object message,String title,int optionType,
int messageType) - static int showConfirmDialog(Component parent,Object message,String title,int optionType)
static int showConfirmDialog(Component parent,Object message) - static int showInternalConfirmDialog(Component parent,object message,String title,int
optionType,int messageType,Icon icon) - static int showInternalConfirmDialog(Component parent,Object message,String title,int
optionType,int messageType) - static int showInternalConfirmDialog(Component parent,Object message,String title,int
optionType) - static int showInternalConfirmDialog(Component parent,object message):显示一个确认对话框或者内部确认对话框(内部对话框完全显示在其父组件窗体内)。返回用户选择的选项(取值为OK OPTION、CANCEL OPTION、YES OPTION或NO OPTION);如果
用户关闭对话框将返回CL0 SED OPTI0N。父组件可以为ulL。显示在对话框中的消息可以是字符串、图标、组件或者它们的一个数组。messageType参数取值为ERROR MESSAGE、INFORMATION MESSAGE、WARNING MESSAGE、QUESTION MESSAGE、PLAIN MESSAGE之一。optionType取值为DEFAULT OPTION、YES NO OPTION、YES NO CANCEL OPTION、OK CANCEL OPTION之一。 - static int showoptionDialog(Component parent,Object message,String title,int optionType,
int messageType,Icon icon,Object[]options,Object default) - static int showInternalOptionDialog(Component parent,Object message,String title,int
optionType,int messageType,Icon icon,Object[]options,Object default):显示一个选项对话框或者内部选项对话框(内部对话框完全显示在其父组件窗体内)。返回用户所选选项的索引;或者如果用户取消了对话框则返回CL0SD0TION。父组件可以为null。显示在对话框中的消息可以是字符串、图标、组件或者它们的一个数组。messageType参数取值为ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE、QUESTION_MESSAGE、PLAIN_MESSAGE之一。optionType取值为DEFAULT_OPTION、YES_NO_OPTION、YES_NO_CANCEL_OPTION、OK_CANCEL_OPTION之一。options参数是字符串、图标或者组件的一个数组。 - static Object showInputDialog(Component parent,Object message,String title,int messageType,
Iconicon,Object[]values,Object default) - static String showInputDialog(Component parent,Object message,String title,int messageType)
static String showInputDialog(Component parent,Object message) - static String showInputDialog(Object message)
static String showInputDialog(Component parent,Object message,Object default) - static String showInputDialog(Object message,Object default)
- static Object showInternalInputDialog(Component parent,Object message,String title,int
messageType,Icon icon,Object[]values,Object default)
static String showInternalInputDialog(Component parent,Object message,String title,int
messagelype) - static String showInternalInputDialog(Component parent,Object message):显示一个输入对话框或者内部输入对话框(内部对话框完全显示在其父组件窗体内)。返回用户输入的字符串;或者如果用户取消了对话框则返回ul。父组件可以为ulL。显示在对话框中的消息可以是字符串、图标、组件或者它们的一个数组。messageType参数取值为ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE、QUESTION_MESSAGE、PLAIN_MESSAGE之一。
7.2、创建对话框
要想实现一个对话框,需要扩展JDialog类。这与应用程
序窗口扩展JFrame的过程完全一样。具体过程如下:
- 在对话框构造器中,调用超类JDialog的构造器。
- 添加对话框的用户界面组件。
- 添加事件处理器。
- 设置对话框的大小。
调用超类构造器时,需要提供所有者窗体(owner frame)
对话框标题及模式特征。
所有者窗体控制对话框的显示位置,如果提供null作为所有者,那么这个对话框将属于一个隐藏窗体。模式特征将指定显示这个对话框时阻塞应用程序的哪些其他窗口。无模式对话框不会阻塞其他窗口,而模式对话框将阻塞应用的所有其他窗口(除当前对话框的子窗口外)。用户经常使用的工具条要使用无模式对话框实现。另一方面,如果想强制用户在继续操作之前必须提供一些必要的信息,就应该使用模式对话框。
public class AboutDialog extends JDialog { public AboutDialog(JFrame owner) { super(owner, "About DialogTest", true); add(new JLabel("<html><h1><i>Core Java</i></h1>By Cay Horstmann</html>"), BorderLayout.CENTER); JButton ok = new JButton("OK"); ok.addActionListener(event -> setVisible(false)); JPanel panel = new JPanel(); panel.add(ok); add(panel, BorderLayout.SOUTH); pack(); } }
public class DialogFrame extends JFrame { private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; private AboutDialog dialog; public DialogFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu); JMenuItem aboutItem = new JMenuItem("About"); aboutItem.addActionListener(event -> { if (dialog == null) { dialog = new AboutDialog(DialogFrame.this); } dialog.setVisible(true); }); fileMenu.add(aboutItem); JMenuItem exitItem = new JMenuItem("Exit"); exitItem.addActionListener(event -> System.exit(0)); fileMenu.add(exitItem); } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new DialogFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.JDialog:
- public JDialog(Frame parent, String title, boolean modal):构造一个对话框。在显式显示对话框之前,这个对话框是不可见的。
7.3、数据交互
@Data @NoArgsConstructor @AllArgsConstructor public class User { private String username; private char[] password; }
public class PasswordChooser extends JPanel { private JTextField username; private JPasswordField password; private JButton okButton; private boolean ok; private JDialog dialog; public PasswordChooser() { setLayout(new BorderLayout()); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2, 2)); panel.add(new JLabel("User name:")); panel.add(username = new JTextField("")); panel.add(new JLabel("Password:")); panel.add(password = new JPasswordField("")); add(panel, BorderLayout.CENTER); okButton = new JButton("OK"); okButton.addActionListener(event -> { ok = true; dialog.setVisible(false); }); JButton cancelButton = new JButton("Cancel"); cancelButton.addActionListener(event -> dialog.setVisible(false)); JPanel buttonPanel = new JPanel(); buttonPanel.add(okButton); buttonPanel.add(cancelButton); add(buttonPanel, BorderLayout.SOUTH); } public void setUser(User u) { username.setText(u.getUsername()); } public User getUser() { return new User(username.getText(), password.getPassword()); } public boolean showDialog(Component parent, String title) { ok = false; Frame owner = null; if (parent instanceof Frame) { owner = (Frame)parent; } else { owner = (Frame)SwingUtilities.getAncestorOfClass(Frame.class, parent); } if (dialog == null || dialog.getOwner() != owner) { dialog = new JDialog(owner, true); dialog.add(this); dialog.getRootPane().setDefaultButton(okButton); dialog.pack(); } dialog.setTitle(title); dialog.setVisible(true); return ok; } }
public class DataExchangeFrame extends JFrame { public static final int TEXT_ROWS = 20; public static final int TEXT_COLUMNS = 40; private PasswordChooser dialog = null; private JTextArea textArea; public DataExchangeFrame() { JMenuBar mbar = new JMenuBar(); setJMenuBar(mbar); JMenu fileMenu = new JMenu("File"); mbar.add(fileMenu); JMenuItem connectItem = new JMenuItem("Connect"); connectItem.addActionListener(new ConnectAction()); fileMenu.add(connectItem); JMenuItem exitItem = new JMenuItem("Exit"); exitItem.addActionListener(event -> System.exit(0)); fileMenu.add(exitItem); textArea = new JTextArea(TEXT_ROWS, TEXT_COLUMNS); add(new JScrollPane(textArea), BorderLayout.CENTER); pack(); } private class ConnectAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (dialog == null) { dialog = new PasswordChooser(); } dialog.setUser(new User("yourname", null)); if (dialog.showDialog(DataExchangeFrame.this, "Connect")) { User u = dialog.getUser(); textArea.append("user name = " + u.getUsername() + ", password = " + (new String(u.getPassword())) + "\n"); } } } public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new DataExchangeFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } }
javax.swing.SwingUtilities:
- Container getAncestorofClass(Class c,Component comp):返回属于给定类或其某个子类的给定组件的最内层父容器。
javax.swing.JComponent:
JRootPane getRootPane():获得包含这个组件的根窗格,如果这个组件没有带根窗格的祖先,则返回null。
javax.swing.JRootPane:
void setDefaultButton(JButton button):设置根窗格的默认按钮。要想禁用默认按钮,可以提供null参数来调用这个方法。
javax.swing.JButton:
- boolean isDefaultButton():如果这个按钮是其根窗格的默认按钮,返回true。
7.4、文件对话框
在一个应用中,通常希望可以打开和保存文件。一个好的文件对话框应该可以显示文件和目录,允许用户浏览文件系统,这样一个文件对话框很难编写,人们肯定不愿意从头做起。很幸运,Swing中提供了JFileChooser类,它显示的文件对话框类似于大多数原生应用所用的对话框。JFileChooser对话框总是模式对话框。注意,JFileChooser类并不是JDialog类的子类。需要调用show0 penDialog
显示打开文件的对话框,或者调用showSaveDialog显示保存文件的对话框,而不是调用setVisible(true)。接收文件的按钮会自动地使用标签Open或者Save。也可以调用showDialog方法为按钮提供你自己的标签。图11-33是文件选择器对话框的一个示例。
下面是建立文件对话框并获取用户选择信息的步骤:
- 建立一个JFileChooser对象。与JDialog类的构造器不同,不需要指定父组件。这就允许你在多个窗体中重用一个文件选择器。例如:
var chooser = new JFileChooser();
- 调用setCurrentDirectory方法设置目录。例如,要使用当前的工作目录:
chooser.setCurrentDirectory(new File("."));
需要提供一个File对象。File对象将在卷Ⅱ的第2章中详细介绍。这里只需要知道构造器File(String fileName)能够将一个文件或目录名转化为一个File对象。
- 如果有一个希望用户选择的默认文件名,可以使用setSelectedFile方法指定:
chooser.setSelectedFile(new File(filename));
- 如果允许用户在对话框中选择多个文件,需要调用setMultiSelectionEnabled方法。当然,这是可选的,而且并不常见。
chooser.setMultiSelectionEnabled(true);
- 如果想让对话框只显示某种特定类型的文件(如,所有扩展名为.gif的文件),需要设置文件过滤器,稍后将会讨论文件过滤器。
- 在默认情况下,用户只能在文件选择器中选择文件。如果希望用户选择目录,需要使用setFileSelectionMode方法。调用时可以提供以下参数:JFileChooser.FILES ONLY(默认值),JFileChooser.DIRECTORIES_ONLY或者 JFileChooser.FILES_AND_DIRECTORIES
- 调用showOpenDialog或者showSaveDialog方法显示对话框。在这些调用中必须提供父组件:
int result = chooser.showOpenDialog(parent);
或者
int result = chooser.showSaveDialog(parent);
这些调用的唯一区别是“确认按钮”的标签不同。“确认按钮”就是用户点击来完成文件选择的那个按钮。也可以调用showDialog方法,并将一个显式的文本传递给确认按钮:
int result chooser.showDialog(parent,"Select");
仅当用户确认、取消或者关闭对话框时这些调用才返回。返回值可以是JFileChooser.
APPROVE_OPTION、JFileChooser.CANCEL_OPTION或者JFileChooser.ERROR_OPTION。
8.调用getSelectedFile()或者getSelectedFiles()方法获取用户选择的一个或多个文件。这些方法将返回一个File对象或者一个File对象数组。如果需要知道文件对象名,可以调用
getPath方法。例如:
String filename chooser.getSelectedFile().getPath();
在大多数情况下,这些步骤都很简单。使用文件对话框的主要困难在于指定一个要让用户从中选择文件的文件子集。例如,假设用户应该选择GF图像文件。那么,文件选择器就应该只显示扩展名为,9f的文件。另外,还应该为用户提供反馈信息,来说明所显示的文件属于某个特定文件类别,如“GF图像”。不过,情况有可能会更加复杂。如果用户应该选择JPFG图像文件,扩展名就可以是.jpg或者.jpeg。文件选择器的设计者没有编写代码来实现这种复杂性,而是提供了一种更优雅的机制:要想限制所显示的文件,可以提供一个扩展了抽象类javax.swing…filechooser.FileFilter的对象。文件选择器将各个文件传递给文件过滤器,只有文件过滤器接受的文件才会显示。
有两个子类:一个是可以接受所有文件的默认过滤器,另一个过滤器可以接受给定扩展名的所有文件。不过,很容易编写专用文件过滤器,只需实现FileFilter超类中的两个抽象方法:
- public boolean accept(File f);
- public String getDescription();
第一个方法检测是否应该接受一个文件,第二个方法返回可能在文件选择对话框中显示的文件类型的描述信息。
一旦有了文件过滤器对象,可以调用JFileChooser类的setFileFilter方法,将这个对象安装到文件选择器对象中:
chooser.setFileFilter(new FileNameExtensionFilter("Image files","gif","jpg"));
可以为一个文件选择器安装多个过滤器,如下:
chooser.addChoosableFileFilter(filterl); chooser.addChoosableFileFilter(filter2);
用户可以从文件对话框底部的组合框中选择过滤器。在默认情况下,组合框中总是显示“All files”过滤器。这是一个好主意,因为使用这个程序的用户有可能需要选择一个具有非标准扩展名的文件。不过,如果你想禁用All files过滤器,需要调用:
chooser.setAcceptAllFileFilterUsed(false)
最后,可以为文件选择器显示的每个文件提供特定的图标和文件描述来定制文件选择器。为此,需要提供-一个类对象,这个类要扩展javax.swing.filechooser包中的FileView类。
这显然是一种高级技术。通常情况下,并不需要你来提供文件视图一可插接式观感会为你提供一个视图。不过,如果想为特殊的文件类型显示不同的图标,也可以安装你自己的文件视图。需要扩展FileView类并实现下面5个方法:
Icon getIcon(File f) String getName(File f) String getDescription(File f) String getTypeDescription(File f) Boolean isTraversable(File f)
然后,使用setFileView方法将文件视图安装到文件选择器中。
文件选择器会为希望显示的每个文件或目录调用这些方法。如果方法返回的图标、名字或描述信息为ul1,那么文件选择器会使用当前观感的默认文件视图。这样处理很好,因为这意味着只需要处理那些希望有不同显示的文件类型。
文件选择器调用isTraversable方法来决定用户点击一个目录时是否打开这个目录。请注意,这个方法返回一个Boolean对象,而不是boolean值。看起来似乎有点怪,但实际上很方便一如果只需要使用默认文件视图,则返回ull。文件选择器就会使用默认的文件视图。换句话说,这个方法返回的Boolean.对象能给出3种选择:真(Boolean.TRUE)、假(Boolean.FALSE)和不关心(nuLL)。
示例程序中包含了一个简单的文件视图类。只要一个文件匹配文件过滤器,这个类将会显示一个特定的图标。可以利用这个类为所有图像文件显示一个调色板图标。
class FileIconView extends FileView { private FileFilter filter; private Icon icon; public FileIconView(FileFilter aFilter,Icon anIcon) { filter = aFilter; icon = anIcon; } public Icon getIcon(File f){ if (!f.isDirectory()&&filter.accept(f)) return icon; } else return null; } }
可以调用setFileView方法将这个文件视图安装到文件选择器:
chooser.setFileView(new FileIconView(filter, new ImageIcon("palette.gif")));
文件选择器会在通过filter过滤的所有文件旁边显示调色板图标,而使用默认的文件视图显示所有其他的文件。很自然地,我们使用了文件选择器中设置的过滤器。
javax.swing.JFileChooser:
- JFileChooser():创建一个可用于多个窗体的文件选择器对话框。
- void setCurrentDirectory(File dir):设置文件对话框的初始目录。
- void setSelectedFile(File file)
- void setSelectedFiles(File[]file):设置文件对话框的默认文件选择。
- void setMultiSelectionEnabled(boolean b):设置或清除多选模式。
- void setFileSelectionMode(int mode):设置用户选择模式,只可以选择文件(默认),只可以选择目录,或者文件和目录均可以选择。mode参数的取值可以是JFileChooser.FILES ONLY、JFileChooser.DIRECTORIES ONLY和JFileChooser.FILES AND DIRECTORIES之一。
- int showOpenDialog(Component parent)
- int showSaveDialog(Component parent)
- int showDialog(Component parent,String approveButtonText):显示一个对话框,确认按钮标签为“Open”“Save”或者approveButtonText字符串,并返回APPROVE OPTION、CANCEL OPTION(如果用户选择取消按钮或者关闭了对话框)或者ERROR OPTION(如果发生错误)。
- File getSelectedFile()
- File[]getSelectedFiles():获取用户选择的一个文件或多个文件(如果用户没有选择文件,返回null)。
- void setFileFilter(FileFilter filter):设置文件对话框的文件过滤器。所有让filter.accept返回true的文件都会显示,并且将这个过滤器添加到可选过滤器列表中。
- void addChoosableFileFilter(FileFilter filter):将文件过滤器添加到可选过滤器列表中。
- void setAcceptAllFileFilterUsed(boolean b):在过滤器组合框中包括或者取消“All files”过滤器。
- void resetChoosableFileFilters():清除可选过滤器列表。除非“A1fils”过滤器被显式地清除,否则它仍然保留。
- void setFileview(Fileview view):设置一个文件视图来提供文件选择器显示的有关文件的信息。
- void setAccessory(JComponent component):设置一个附件组件。
javax.swing.filechooser.FileFilter:
- boolean accept(File f):如果文件选择器显示这个文件,返回true。
- String getDescription():返回这个文件过滤器的一个描述,例如,“Image files(.gif- ,.jpeg)”。
javax.swing.filechooser.FileNameExtensionFilter:
- FileNameExtensionFilter(String description,String…extensions):利用给定的描述构造一个文件过滤器,它接受名字以特定方式结尾的所有目录和文件,即有一个点号,后面紧跟给定的某个扩展名字符串。
javax.swing.filechooser.FileView:
- String getName(File f):返回文件f的文件名,或者null。正常情况下这个方法简单地返回f.getName()。
- String getDescription(File f):返回关于文件f的人类可读的一个描述,或者ull。例如,如果f是HTML文档,那么这个方法可能返回它的标题。
- String getTypeDescription(File f):返回关于文件f类型的人类可读的一个描述,或者ull。例如,如果文件f是HTML文档,那么这个方法可能返回字符串"Hypertext document’"。
- Icon getIcon(File f):返回文件f的图标,或者ull。例如,如果f是PEG文件,那么这个方法可能返回缩略图标。
- Boolean isTraversable(File f):如果f是用户可以打开的目录,返回Boolean.TUE。如果一个目录在概念上是复合文档,那么这个方法可能返回Boolean.false.。与所有的FileView方法一样,这个方法有可能返回null,表示文件选择器应该使用默认视图。