多种通用方法的集合——SwingUtilities

 

技巧:Swing的实用工具SwingUtilities
 

 
             
 
 
 
 

Swing提供了许多的在Swing组件内部使用的实用工具,SwingUtilities类就是其中一个。它提供了许多的涉及计算、转换、访问控制、布局等方面的方法,这些方法已广泛的应用在各种Swing组件当中。当然,我们也可以把它拿来应用到我们自己的程序当中。

下面我们就来了解一个这些实用的功能.

(1)转换方法

MouseEvent convertMouseEvent(Component source, MouseEvent ourceEvent, Component destination)

将一个鼠标事件从一个组件转换到另一个组件上。

Point convertPoint(Component source, int x, int y, Component estination)

Point convertPoint(Component source, Point aPoint, Component destination)

将一个组件上的点坐标转换成另一个组件上的坐标点。

void convertPointFromScreen(Point p, Component c)

将一个屏暮坐标点转换成一个组件的坐标点.

void convertPointToScreen(

Point p, omponent c)

 

将一个组件上的坐标点转换成屏暮坐标。

Rectangle convertRectangle(Component source, Rectangle aRectangle, Component destination)

将一个组件上的矩形坐标转换成另一个组件上的矩形坐标.

(2)计算方法

Rectangle[] computeDifference(Rectangle r1,Rectangle r2)

Rectangle computeIntersection(int x, int y, int width, int height, Rectangle dest)

Rectangle computeUnion(int x, int y, int width, int height, Rectangle dest)

int computeStringWidth(FontMetrics fm, String str)

boolean isRectangleContainingRectangle(Rectangle a, Rectangle b)

上面这些方法计算两个矩形之间的差集、交集、并集,还可确定一个矩形是否包括了另一个矩形,以及计算一个字符串的像素宽度.

(3)鼠标按钮信息

boolean isLeftMouseButton(MouseEvent anEvent)

boolean isMiddleMouseButton(MouseEvent anEvent)

boolean isRightMouseButton(MouseEvent anEvent)

以给定的鼠标事件来判定按下了哪个鼠标按钮。

(4)访问控制方法

Component findFocusOwner(Component c)

取得给定组件的焦点组件.

Component getLocalBounds(Component aComponent)

取得给定组件的范围(Bounds)信息.

Component getRoot(Component c)

JRootPane getRootPane(Component c)

取得给定组件的根窗格.

Window windowForComponent(Component aComponent)

返回一个组件的视窗Window.

(5)组件绘制

String layoutCompoundLabel(FontMetrics fm, String text, Icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap)

String layoutCompoundLabel(JComponent c, FontMetrics fm, String text, Icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int rizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap)void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h)

这两个方法用于重新布局swing按钮和标签上的文本和图标.

void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h)

void paintComponent(Graphics g, Component c, Container p, Rectangle r)

在任一个图形上绘制一个组件。

 

/************************************************************/

 

 1. UIManager , Look and Feel , SwingUtilities 

       最简单的swing程序  

JFrame , UIManager , Look and Feel , SwingUtilities ,javaw进程的结束   

一、尝试了一下,一个最简单的Swing程序需要9行:

  1. import javax.swing.*;  
  2.   
  3. public class taskman {
  4.     public static void main(String[] args) {   
  5.         JFrame f = new JFrame("任务人II") ;  
  6.         f.setSize(300,200) ;  
  7.         f.setVisible(true) ;  
  8.     }  
  9. }   

该程序运行结果如下(win XP,使用经典外观):  

min1

回头看代码,是否可以再精简

1. import语句不能省,要不然程序不认识JFrame;   

2. 构造JFrame对象(f),不能省,这时我们要显示的窗口;   

3. 设置f的大小,如果不设置,大小为0,等于看不到;   

4. setVisible,这个方法将窗口显示出来   

5. 当然,可以删除空行,或将代码压缩到一行,不过那样就不好读了。

二、响应窗口关闭事件

  上述最小程序是不完整的。表现如下:

1、如果在命令行使用java taskman启动程序,关闭窗口之后,命令行的程序并不结束,必须使用CTRL-C来中止程序执行;

2、如果使用javaw taskman启动,关闭窗口之后,系统中对应的javaw进程不能自动结束,必须使用任务管理器找到进程,手动中止,如果没有察觉到这个现象,反复启动这样的程序后,会发现,虽然所有的窗口都已经关闭,但系统中的javaw进程却会越来越多,最终威胁系统的稳定运行;  

为了解决这个问题,我们的最简程序还需要增加一条语句:   

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;

增加这条语句以后的代码如下:   

  1. import javax.swing.*;  
  2.   
  3. public class taskman {  
  4.     public static void main(String[] args) {   
  5.         JFrame f = new JFrame("任务人II") ;  
  6.         f.setSize(300,200) ;  
  7.         f.setDefaultCloseOperation  
  8.         (JFrame.EXIT_ON_CLOSE) ;  
  9.         f.setVisible(true) ;  
  10.     }  
  11. }  

在关闭这个程序产生的窗口时,java将调用System.exit(0),相关的java或javaw进程就会同时结束了。    

三、窗口的外观一致性

响应了窗口关闭事件以后,我们的swing程序对操作系统而言已经是一个“行事检点”的模范程序了,然而看起来窗口的样子还是怪怪的,尤其是在窗口中添加了控件以后,总觉得边框和窗口主面板不协调,查了查文档,知道可以通过UIMagager解决这个问题,即在程序一开始使用如下三条语句: 

  1. String lnf = UIManager.getCrossPlatformLookAndFeelClassName() ;  
  2. UIManager.setLookAndFeel(lnf) ;  
  3. JFrame.setDefaultLookAndFeelDecorated(true) ;   

修改后的程序外观如下:    

  min2  

代码如下:   

  1. import javax.swing.*;  
  2.   
  3. public class taskman {  
  4.     public static void main(String[] args)   
  5.     throws Exception {  
  6.         String lnf = UIManager  
  7.         .getCrossPlatformLookAndFeelClassName() ;  
  8.         UIManager.setLookAndFeel(lnf) ;  
  9.         JFrame.setDefaultLookAndFeelDecorated(true) ;  
  10.           
  11.         JFrame f = new JFrame("任务人II") ;  
  12.         f.setSize(300,200) ;  
  13.         f.setDefaultCloseOperation  
  14.         (JFrame.EXIT_ON_CLOSE) ;  
  15.         f.setVisible(true) ;  
  16.     }  
  17. }   

四、swing的java线程安全要求  

统一了外观以后,再与swing tutorials对照, 发现还有一个关键的东西:SwingUtilities.invokeLater。

查了查资料,发现如果不用这个东西,会有swing线程不安全的问题,使用这个东西后,代码明显复杂了:

  1. import javax.swing.*;  
  2.   
  3. public class taskman {  
  4.     public static void main(String[] args)   
  5.     throws Exception {  
  6.         String lnf = UIManager  
  7.         .getCrossPlatformLookAndFeelClassName() ;  
  8.         UIManager.setLookAndFeel(lnf) ;  
  9.         JFrame.setDefaultLookAndFeelDecorated(true) ;  
  10.           
  11.         SwingUtilities.invokeLater(new Runnable(){  
  12.             public void run() {  
  13.                 make_ui() ;  
  14.             }  
  15.         }) ;  
  16.     }  
  17.       
  18.     static void make_ui() {  
  19.         JFrame f = new JFrame("任务人II") ;  
  20.         f.setSize(300,200) ;  
  21.         f.setDefaultCloseOperation  
  22.         (JFrame.EXIT_ON_CLOSE) ;  
  23.         f.setVisible(true) ;  
  24.     }  
  25. }  

 

 

/************************************************************/

 2. SwingUtilities , invokeLater , 线程安全 

 SwingUtilities 和线程安全

多线程是图形界面程序的本质特征,几乎所有图形界面程序都要响应按钮、文本框、菜单等控件触发的事件,为了完成这些任务,必须使用多线程。

对于不熟悉多线程程序的人来说,需要一段时间来理解和适应多线程的程序设计。

Swing规范要求,在Swing控件可见以后,所有对其外观的修改都应当采用如下的形式:

  1. SwingUtilities.invokeLater(new Runnable(){  
  2.     public void run() {   
  3.                  // ...  
  4.                  // 修改GUI控件的外观  
  5.     }  
  6. }) ;  

并说明,“由于swing不是线程安全的,不采用这种方式可能会出现意想不到的错误...“

对于不太了解Swing的人来说,这个要求真可以说是莫名其妙,在我对此感到莫名其妙的时候,我也比较好奇,会产生什么意想不到的错误呢?

要说明这个问题,可以用一个具体的例子配合实验来说明。 假设我们用swing实现如下一个应用:

名称:通知

说明:这个swing程序将在启动后,访问网络,将网络上以某种加密格式存储的通知信息显示出来。

界面示例:  

ok

上图中"用户需要的数据"就是通知内容。

经过研究,我们决定使用JTextArea作为显示通知的控件,从网络上拿到通知文本以后,使用如下JTextArea指令将通知内容显示出来:

data.setText(str) ;

一个看起来很简单的应用。 可是在我们进行了实现,并做了一些优化以后,却发现时不常的,我们的通知内容不能显示出来,初步调查显示,在无法显示通知时,java控制台会出现一条异常:

  err

这也许就是传说中的意想不到的错误。

下面我们将使用最多不超过80行的本地程序来模拟这个错误的呈现。

最初我们做了如下实现:

  1. import javax.swing.*;  
  2.   
  3. public class taskman_serial {  
  4.       
  5.     taskman_serial() {  
  6.         // 从网上获取通知内容     
  7.         String str = "这是用户需要的数据" ;     
  8.           
  9.         JTextArea data = new JTextArea(str) ;  
        JFrame frame =  new JFrame( "任务人II") ;  
            frame.setSize( 300200) ;  
    1.         frame.setDefaultCloseOperation  
    2.         (JFrame.EXIT_ON_CLOSE) ;  
    3.         frame.setContentPane(data) ;  
    4.           
    5.         frame.setVisible(true) ;  
    6.     }  
    7.   
    8.     public static void main(String[] args)   
    9.     throws Exception{  
    10.         String lnf = UIManager     
    11.         .getCrossPlatformLookAndFeelClassName() ;     
    12.         UIManager.setLookAndFeel(lnf) ;     
    13.         JFrame.setDefaultLookAndFeelDecorated(true) ;     
    14.          
    15.         new taskman_serial() ;  
    16.     }  
    17. }  

    运行这个程序,看起来完美的实现了设计要求。

    但是,在用户使用了一段时间以后,却提出了一个设计要求之外的需求:

    有时候,获取网络上的通知信息需要十几秒甚至更长的时间,而在这段时间内,屏幕一直没有反映,让人感觉程序没有启动,结果重复的双击图标,一会儿出现了一大堆通知窗口。

    ok,我们改进程序如下:

    1、启动后立即显示程序窗口,通知区域显示”正在获取通知“;

    2、装载通知以后,再将通知显示出来

    看来肯定需要线程来帮忙了,

    1、我们分别启动两个线程,一个获取数据,一个创建gui,

    2、GUI创建完成以后马上显示出来,而得到数据以后,再更新数据区域,

    3、为了使两个线程都可以访问JTextArea,我们把它定义为taskman的属性变量

    为了模拟耗时的网络操作,我们编写了子程序op,用于模拟访问网络消耗的毫秒数:

    1. void op(long millis) {   
    2.     try {   
    3.         Thread.sleep(millis) ;  
    4.     } catch (Exception exc) {}  
    5. }  

    完整代码如下:

    1. import javax.swing.*;  
    2.   
    3. public class taskman_thread {  
    4.     JTextArea data ;  
    5.       
    6.     taskman_thread() {  
    7.         Thread t_ui = new Thread() {   
    8.             public void run() {   
    9.                 make_ui() ;  
    10.             }  
    11.         } ;  
    12.         Thread t_load = new Thread() {   
    13.             public void run() {   
    14.                 load_data() ;  
    15.             }  
    16.         } ;  
    17.           
    18.         t_ui.start() ;  
    19.         t_load.start() ;  
    20.     }  
    21.   
    22.     void make_ui() {   
    23.         data = new JTextArea("正在获取数据...") ;  
    24.         JFrame frame = new JFrame("任务人II") ;  
    25.         frame.setContentPane(data) ;  
    26.         frame.setDefaultCloseOperation  
    27.         (JFrame.EXIT_ON_CLOSE) ;  
    28.         frame.setSize(300200) ;  
    29.         frame.setVisible(true) ;  
    30.     }  
    31.       
    32.     void load_data() {   
    33.         // 用3000毫秒时间访问网络,将得到的数据显示在控件中  
    34.         op(3000) ;  
    35.         data.setText("这是用户需要的数据") ;  
    36.     }  
    37.       
    38.     void op(long millis) {   
    39.         try {   
    40.             Thread.sleep(millis) ;  
    41.         } catch (Exception exc) {}  
    42.     }  
    43.   
    44.     public static void main(String[] args)   
    45.     throws Exception{  
    46.         String lnf = UIManager     
    47.         .getCrossPlatformLookAndFeelClassName() ;     
    48.         UIManager.setLookAndFeel(lnf) ;     
    49.         JFrame.setDefaultLookAndFeelDecorated(true) ;     
    50.   
    51.         new taskman_thread() ;  
    52.     }  
    53. }  

    经运行,达到预期要求,程序在启动后立即显示了GUI画面,并在稍后完成了网络访问,刷新了通知区域

    噩梦是在一个管理需求后开始的,需求说,程序需要在启动后,要到中心服务器获取用户的权限,如果用户可以发布通知,界面上要有发布通知的控件,否则,界面仍然和原来一样。

     只须在子程序make_ui()的首部增加语句op(4000),就可以模拟这个问题:

    1. void make_ui() {   
    2.     op(4000) ;  
    3.     // 获取用户权限信息  
    4.      data = new JTextArea("正在获取数据...") ;  
    5.     JFrame frame = new JFrame("任务人II") ;  
    6.     frame.setContentPane(data) ;  
    7.     frame.setDefaultCloseOperation  
    8.     (JFrame.EXIT_ON_CLOSE) ;  
    9.     frame.setSize(300200) ;  
    10.     frame.setVisible(true) ;  
    11. }  

    修改其中op()的参数,如果是2000,肯定不会有问题,如果大于3000,就会出问题。

    问题显而易见,由于make_ui的执行时间延长了,当得到了数据但JTextArea还未完成初始化时,就会出现问题。

    好了,如果是这样,该用SwingUtilities了,是否应该这样写代码:

    1. taskman_ok() {  
    2.     Thread t_ui = new Thread() {   
    3.         public void run() {   
    4.             make_ui() ;  
    5.         }  
    6.     } ;  
    7.     Thread t_load = new Thread() {   
    8.         public void run() {   
    9.             load_data() ;  
    10.         }  
    11.     } ;  
    12.       
    13.     SwingUtilities.invokeLater(t_ui);  
    14.     SwingUtilities.invokeLater(t_load) ;  
    15. }  

    运行一下结果正常了,时间长了很多,原来4秒可以完成通知的下载和显示,现在需要7秒了,make_ui()和load_data()实际上又变成了串行执行。

    再改进,仅把与GUI相关的指令序列放进SwingUtilities,这回OK了,既可以最快的打开界面,又可以并行的获取数据了。

    完整代码如下:

    1. import javax.swing.*;  
    2.   
    3. public class taskman_ok {  
    4.     JTextArea data ;  
    5.       
    6.     taskman_ok() {  
    7.         Thread t_ui = new Thread() {   
    8.             public void run() {   
    9.                 make_ui() ;  
    10.             }  
    11.         } ;  
    12.         Thread t_load = new Thread() {   
    13.             public void run() {   
    14.                 load_data() ;  
    15.             }  
    16.         } ;  
    17.           
    18.         SwingUtilities.invokeLater(t_ui);  
    19.         t_load.start() ;  
    20.     }  
    21.   
    22.     void make_ui() {   
    23.         op(4000) ;  
    24.         // 获取用户权限信息  
    25.          data = new JTextArea("正在获取数据...") ;  
    26.         JFrame frame = new JFrame("任务人II") ;  
    27.         frame.setContentPane(data) ;  
    28.         frame.setDefaultCloseOperation  
    29.         (JFrame.EXIT_ON_CLOSE) ;  
    30.   
    31.         frame.setSize(300200) ;  
    32.         frame.setVisible(true) ;  
    33.     }  
    34.       
    35.     void load_data() {   
    36.         // 用3000毫秒时间访问网络,将得到的数据显示在控件中  
    37.          op(3000) ;  
    38.         SwingUtilities.invokeLater(new Runnable(){  
    39.             public void run() {   
    40.                 data.setText("这是用户需要的数据") ;  
    41.             }  
    42.         }) ;  
    43.     }  
    44.       
    45.     void op(long millis) {   
    46.         try {   
    47.             Thread.sleep(millis) ;  
    48.         } catch (Exception exc) {}  
    49.     }  
    50.   
    51.     public static void main(String[] args)   
    52.     throws Exception{  
    53.         String lnf = UIManager     
    54.         .getCrossPlatformLookAndFeelClassName() ;     
    55.         UIManager.setLookAndFeel(lnf) ;     
    56.         JFrame.setDefaultLookAndFeelDecorated(true) ;     
    57.   
    58.         new taskman_ok() ;  
    59.     }  
    60. }  

     

     

     

     

     

     

     

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值