Swing GUI 案例 - 5. 面板和布局
经常看到有人说Swing的布局代码冗长而且变态,相对于.net标准的visual方式,不借助可视化工具的swing界面编写确实显得枯燥繁琐。
经过一年多断断续续的尝试,我对swing界面代码的编写也有了些感觉,在此晒一晒。
应用简述:实现如下界面,并提供相关的录入、查询、修改和删除等功能。
分析一下,这个界面的主体应当是三个滚动面板(JScrollPane),一个容纳任务列表(左侧),一个容纳记录列表(右侧),一个容纳文本信息(右下)。(仅含有三个滚动面板的代码请参见这里)
除此之外,上面还有一个菜单,菜单下方还有一个工具条。
菜单谈不上布局,工具条则要求月份控件靠左,按钮控件靠右,中间留白。
主体部分,右侧的两个滚动面板组成一个分割面板(JSplitPane),右侧的分割面板又与左边的滚动面板组成一个大的水平分割的JSplitPane。
由此,可以构思程序代码如下:
1、构建菜单
2、构建工具条
3、构建分割面板
4、构建主面板
由此得到直觉的布局代码:
……
ScrollPane
s_index = new JScrollPane(new JTextArea()) ,
s_recs = new JScrollPane(new JTextArea()) ,
s_info = new JScrollPane(new JTextArea()) ;
JSplitPane
right = new JSplitPane
(JSplitPane.VERTICAL_SPLIT,s_recs,s_info) ,
split = new JSplitPane
(JSplitPane.HORIZONTAL_SPLIT,s_index,right);
split.setPreferredSize(new Dimension(600,400)) ;
right.setDividerLocation(300) ;
split.setDividerLocation(300) ;
JPanel p = new JPanel() ;
p.setLayout(new BorderLayout()) ;
p.add(toolbar(),BorderLayout.PAGE_START) ;
p.add(split,BorderLayout.CENTER) ;
JFrame f = new JFrame("任务人III") ;
f.setJMenuBar(menu_bar()) ;
f.setContentPane(p) ;
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
f.pack() ;
f.setVisible(true)
……
工具栏(一个面板)部分的代码:
JTextField
c_month = new JTextField("0806") ;
JLabel l = new JLabel("当前月份(M):") ;
l.setLabelFor(c_month) ;
l.setDisplayedMnemonic(KeyEvent.VK_M) ;
JButton
reverse = new JButton("逆序") ,
task_new = new JButton("新任务") ,
rec_new = new JButton("新任务") ;
JPanel p = new JPanel() ;
p.setLayout
(new BoxLayout(p,BoxLayout.LINE_AXIS)) ;
p.add(l) ;
p.add(c_month) ;
p.add(Box.createHorizontalGlue()) ;
p.add(reverse) ;
p.add(task_new) ;
p.add(rec_new) ;
试着运行,效果如下:
熟悉visual的同志们看到这个界面,就会嘲笑并判断swing的愚昧落后了。
分析一下,现在界面的主要问题是:
1、面板无边
2、菜单、工具栏和分割面板之间过于紧密
3、工具栏中文本字段太长
4、按钮傻大
为了让swing程序的外观看起来好些,我用了如下一些方法:
1、EmptyBorder(解决1、2)
2、setMaximumSize()(解决3)
3、JButton.setMargin()(解决4)
相关代码如下:
// 设置工具栏的下边框
p.setBorder(new EmptyBorder(0,0,10,0)) ;
// 设置主面板的边框
p.setBorder(new EmptyBorder(10,10,10,10)) ;
// 限制JTextField的长度
c_month.setColumns(3) ;
c_month.setMaximumSize
(c_month.getPreferredSize()) ;
// 创建小按钮
private static JButton make_button
(String label, int key) {
JButton b = new JButton(label) ;
if (key>0) b.setMnemonic(key) ;
b.setMargin(new Insets(0,0,0,0)) ;
return b ;
}
效果:

原来的4个问题解决了,但发现按钮显得很拥挤,三个滚动面板也不太匀称,这可以通过添加水平柱子(Box.createHorizontalStrut())和调整分隔条位置(<JScrollPane>.setDividerLocation)来实现。
完整源码如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class taskman5 {
public static void main(String[] args)
throws Exception {
String lnf = UIManager
.getCrossPlatformLookAndFeelClassName() ;
UIManager.setLookAndFeel(lnf) ;
JFrame.setDefaultLookAndFeelDecorated(true) ;
JDialog.setDefaultLookAndFeelDecorated(true) ;
SwingUtilities.invokeLater(new Runnable(){
public void run() {
make_ui() ;
}
}) ;
}
private static void make_ui () {
JScrollPane
s_index = new JScrollPane(new JTextArea()) ,
s_recs = new JScrollPane(new JTextArea()) ,
s_info = new JScrollPane(new JTextArea()) ;
JSplitPane
right = new JSplitPane
(JSplitPane.VERTICAL_SPLIT,s_recs,s_info) ,
split = new JSplitPane
(JSplitPane.HORIZONTAL_SPLIT,s_index,right);
split.setPreferredSize(new Dimension(600,350)) ;
right.setDividerLocation(250) ;
split.setDividerLocation(200) ;
JPanel p = new JPanel() ;
p.setLayout(new BorderLayout()) ;
p.add(toolbar(),BorderLayout.PAGE_START) ;
p.add(split,BorderLayout.CENTER) ;
p.setBorder(new EmptyBorder(10,10,10,10)) ;
JFrame f = new JFrame("任务人III") ;
f.setJMenuBar(menu_bar()) ;
f.setContentPane(p) ;
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
f.pack() ;
f.setVisible(true) ;
}
static private JMenuBar menu_bar() {
JMenu
file = new JMenu("文件(F)") ,
task = new JMenu("任务(T)") ,
view = new JMenu("视图(V)") ;
file.setMnemonic(KeyEvent.VK_F) ;
task.setMnemonic(KeyEvent.VK_T) ;
view.setMnemonic(KeyEvent.VK_V) ;
JMenuBar bar = new JMenuBar() ;
bar.add(file) ;
bar.add(task) ;
bar.add(view) ;
return bar ;
}
static private JPanel toolbar() {
JTextField
c_month = new JTextField("0806") ;
c_month.setColumns(3) ;
c_month.setMaximumSize
(c_month.getPreferredSize()) ;
JLabel l = new JLabel("当前月份(M):") ;
l.setLabelFor(c_month) ;
l.setDisplayedMnemonic(KeyEvent.VK_M) ;
JButton
reverse = make_button("逆序",-1) ,
task_new = make_button("新任务",-1) ,
rec_new = make_button("新任务",-1) ;
JPanel p = new JPanel() ;
p.setLayout
(new BoxLayout(p,BoxLayout.LINE_AXIS)) ;
p.add(l) ;
p.add(c_month) ;
p.add(Box.createHorizontalGlue()) ;
p.add(reverse) ;
p.add(button_margin()) ;
p.add(task_new) ;
p.add(button_margin()) ;
p.add(rec_new) ;
p.setBorder(new EmptyBorder(0,0,10,0)) ;
return p ;
}
private static JButton make_button
(String label, int key) {
JButton b = new JButton(label) ;
if (key>0) b.setMnemonic(key) ;
b.setMargin(new Insets(0,0,0,0)) ;
return b ;
}
private static Component button_margin() {
return Box.createHorizontalStrut(5) ;
}
}
效果如下:
这个外观已经可以作为原型使用了。
回顾上述步骤,使用纯代码手段设定整体布局,我经历了一个3步的求精过程。这样形成代码,虽然比可视化工具效率要低些,但却令java代码更好读,并且对窗口组件有了更好的程序控制。
相关:
UIManager , Look and Feel , SwingUtilities