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