java 线程 事件_Swing理解Swing中的事件与线程

talk is cheap , show me the code.Swing中的事件事件驱动

所有的GUI程序都是事件驱动的。Swing当然也是。

GUI程序不同于Command Line程序,一个很大的区别是程序执行的驱动条件:命令行程序是接受用户输入的文本参数,对命令解析,然后通过类似switch的选择来执行不同的功能模块。而GUI程 序就不一样了。GUI程序由界面元素组成,如Button,CheckBox,TextArea,等等。用户操作不同的组件,就会引发不同的事件,然后, 程序编写时注册到UI组件上的事件处理程序得到调用,以此来和用户交互。

82476383_1

82476383_2事件Event

事件有点类似于异常:事件是事件类的对象,它携带了事件相关的信息,异常是异常类的对象,他携带了异常信息。无论是异常,还是事件

发生时,我们的程序都要事先写好相应的代码应对并处理。只不过,对于程序员来说,事件是正派的,而异常则是反派,谁也不希望自己的程序出现异常。

java中,所有的事件类都是EventObject类的子类,所有的事件都有一个成员字段:source用来保存事件源,即引发事件的对象。

public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /* source保存 引发事件的对象的引用*/ protected transient Object source; public EventObject(Object source) { if (source == null) throw new IllegalArgumentException('null source'); this.source = source; } public Object getSource { return source; }

public String toString { return getClass.getName + '[source=' + source + ']'; } }

Swing的事件机制由AWT提供,下面是Swing中常用的高级事件 ActionEvnet类的部分代码。还有其他事件。

public class ActionEvent extends AWTEvent

{ public ActionEvent(Object source, int id, String command, long when, int modifiers) { super(source, id); this.actionCommand = command; this.when = when; this.modifiers = modifiers; }

//....... }事件源EventSource

异常,有引发异常的原因,事件,也有引发事件的对象,这就是事件源。谁引发了事件,谁就是事件源。

比如,Button被点击时引发事件,Button就是事件源,JFrame 状态变化时,JFrame也是事件源。Swing中所有的组件,都有感知自己被操作的能力。

Swing中,事件源一般是一些用户组件,他们能感知用户的操作,并引发相应的事件,最后通知对自己注册的监听器。

事件源都会提供事件的注册接口,所有对某个组件的某个事件感兴趣的其他代码,都可以提前注册到这个组件上,事件发生时,此组件就会调用相应的注册的

事件处理程序。

下面是JButton的父类 AbstractButton的一个方法。

protected void fireActionPerformed(ActionEvent event) { // Guaranteed to return a non-null array Object listeners = listenerList.getListenerList; ActionEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==ActionListener.class) { // Lazily create the event: if (e == null) { String actionCommand = event.getActionCommand; if(actionCommand == null) { actionCommand = getActionCommand; }

e

= new ActionEvent(AbstractButton.this, ActionEvent.ACTION_PERFORMED, actionCommand, event.getWhen, event.getModifiers); } ((ActionListener)listeners[i+1

]).actionPerformed(e);} } }监听者Listener

监听者(有的也叫侦听器):实现了某个监听接口的类对象。某个类实现了一个监听器接口,它就是一个监听者。

当事件发生时,并不是事件源处理事件,而是注册在事件源的上的监听器去处理。事件源只是通知监听器,通知实质是调用所有监听器对象按接口约定实现的的接口方法。

我们知道,对象实现了某个接口,就代表这个对象能做什么。同理,一个对象想成为监听器,它就必须实现相应的监听器接口,表明他有处理某个事件的能力。

监听器实现了监听接口,就必然要实现接口中定义的方法,用来应对事件。

所有的监听器接口都必须扩展自EventListener,它是一个空接口。一个事件往往对应一个监听者接口。

JComponnet类是所有Swing组件的父类。JComponnet 类中有一个 EventListenerList成员,它是一个表,用来存储所有注册的监听者。那也就是说,所有的Swing组件内部都包含一个存储监听者的列表,这也是为什么能向Swing组件中注册监听器的本质。

public abstract class JComponent extends Container implements Serializable,TransferHandler.HasGetTransferHandler

{ /** A list of event listeners for this component. */ protected EventListenerList listenerList = new EventListenerList; //....... }

/**

EventListenerList类 这是一个用于保存监听器的一个表类型。这个表可以存储任何类型的EventListener,因为内部是用的一个Object数组存储的。 */ public class EventListenerList implements Serializable { protected transient Object listenerList = NULL_ARRAY; //获取所有监听者的数组 public Object getListenerList { return listenerList; }

/** * 返回监听者的数量*/ public int getListenerCount { return listenerList.length/2; } /** 向监听者列表中添加 “一对” 新的监听者。其实是添加一个监听者, 只不过对于一个监听者需要保存2项:监听者的类 t,和监听者本身 l */ public synchronized void add(Classt, T l) { if (l==null) {return;} if (!t.isInstance(l)) { throw new IllegalArgumentException('Listener ' + l +' is not of type ' + t); } if (listenerList == NULL_ARRAY) { //如果是第一次添加监听者,则 new 一个Object 数组。 listenerList = new Object { t, l }; } else { int i = listenerList.length; Object tmp = new Object[i+2]; System.arraycopy(listenerList, 0, tmp, 0, i); tmp[i] = t; tmp[i+1] = l; listenerList = tmp; } } }

这个时候你再回去看事件源分块中的那段代码,是不是思路清晰许多了呢?

所以,事件源通知监听者,实质是遍历内部的监听者表,将自己作为EventSorece,构造一个事件对象,并调用所有监听者的事件处理程序时,将构造的事件对象传递过去。

如果你还是有点迷糊,下面通过一例子说明下。

下面是一个简单的Swing程序。

监听者:ButtonClickListener 类对象,它实现了监听器接口。一般我们会使用匿名内部类完成监听者的实例化,这里写出成员内部类是为了更清晰。当使用addActionListener方法注册后,ButtonClickListener对象就被存储在Button对象内部的一个EventListenerList列表中了。

事件 :点击Button时生成。

事件源:被点击的Button对象。

import java.awt.Dimension;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;import javax.swing.SwingUtilities;public class SwingDrive { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable { @Override public void run { JFrame frame = new TestFrame('测试'); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); }}class TestFrame extends JFrame{ private static final int FRAME_WIDTH = 530; private static final int FRAME_HEIGHT = 360; /*************View******************/ private JPanel mainPanel = null; private JButton msgButton = null; private JLabel msgLabel = null; public TestFrame(String title) { super(title); initUI; } private void initUI { //内容面板 mainPanel = new JPanel; mainPanel.setPreferredSize(new Dimension(FRAME_WIDTH,FRAME_HEIGHT)); this.setContentPane(mainPanel); //按钮 msgButton = new JButton('我是按钮'); //监听者表示对按钮的点击事件感兴趣,于是注册到按钮上。 msgButton.addActionListener(new ButtonClickListener); //msg显示文本 msgLabel = new JLabel; //将组建添加到窗体的内容面板中 this.add(msgButton); this.add(msgLabel); this.pack; } /*监听者,实现了监听接口*/ private class ButtonClickListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { msgLabel.setText('你点击了按钮'); } }}还有一点疑问

who invoke the fireActionPerformed(ActionEvent event) method?

谁调用了JButton的fireActionPerformed方法呢?

如果你能想到这个问题,说明你已经开始深入了。这是Swing本身的机制,确切说是AWT提供的机制。一个Swing程序中会有一个toolkit线程不断运行着,它监视用户对组件的操作,当组件被点击,获取焦点,被最大化,状态改变等,都会被toolkit线程发现,并将fireXXX发送带EDT中执行,fireXXX的执行,又会导致所有监听器的执行。

先不急,这涉及到Swing线程的知识,请往下看。Swing中的线程

1、主线程,main方法,程序执行的入口。任何程序都必须有的。

2、初始化线程。创建和初始化图形界面。

3、tookit线程:负责捕捉系统事件,如鼠标,键盘等。负责感知组件的操作,并将事件发通知EDT。

4、EDT线程:处理Swing中的各种事件。UI绘制,UI的修改操作,UI的绘制渲染.,监听者的事件处理函数,等。所有的UI操作都必须在EDT线程中执行,不允许在其他线程中。

5、N个后台工作线程:处理耗时任务,如网络资源下载,可能阻塞的IO操作。

初始化线程

public static void main(String [] args){ SwingUtilities.invokeLater(new Runnable{ public void run { //初始化线程逻辑代码在这里执行 } }); }

Swing多线程的执行

82476383_3

图画完后,我才发现图画的有一问题:其中EDT线程和toolkit线程是循环线程,并没有确切的执行终点,也就是不知道这2个线程什么时候执行任务到100%。只要Swing程序没有结束,他们就一直工作,因为用户可能在任何时候执行UI操作。

后台工作线程当执行完任务后就结束了。

一、不要在EDT线程中执行耗时的任务。

一旦EDT线程被阻塞,UI组件就不能及时渲染,更新,使得整个程序失去对用户的响应。用户体验十分糟糕。

Swing本身是设计为单线程操作的,并非线程安全的.这就意味着:所有的UI操作都会必须在EDT线程中进行。内置的组件都是遵守这个约定的,比如一个JButton被按下时,它需要显示为按下的状态,那么,这个渲染为按下的状态,就会以事件的形式发布到EDT线程中去执行。同样,按钮弹起时,需要渲染为普通状态,也会引发事件,并在EDT中处理。

不要让EDT干 '体力活'。很明显,Swing中组件UI的更新,都会形成事件置于事件队列,并等待EDT派发,也就是UI更新依赖EDT线程完成。如果你的事件处理程序太耗时了,那么,UI就很久得不到及时更新,造成界面假死现象。

下面这个程序中,用户点击下载按钮后,真个界面都失去了响应,按钮久久不能弹起,窗口也失去了响应,体验很糟糕。

82476383_4

class BadFrame extends JFrame { public BadFrame { super; initUI; } private JButton downloadButton ; private JPanel mainPane ; private void initUI { mainPane = new JPanel; mainPane.setPreferredSize(new Dimension(430,250)); this.setContentPane(mainPane); downloadButton = new JButton('下载'); this.getContentPane.add(downloadButton); downloadButton.addActionListener(new ActionListener { @Override public void actionPerformed(ActionEvent e) { downloadMovie; } }); this.pack; } //模拟下载任务 private void downloadMovie { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace; } }}

二、不要在非EDT线程中访问UI,操作UI组件。

Swing组件都不是线程安全的,只有把他们的操作限制在一个线程中,才能保证所有的UI的操作都符合预期。这个线程就是EDT线程。那么,怎样将UI操作发送到EDT中执行呢?

通过以下之一。

1 SwingUtilities.invokeLater(new Runnable {2 3 @Override4 public void run {5 6 7 }8 });

9

1 SwingUtilities.invokeAndWait(new Runnable {2 3 @Override4 public void run {5 // TODO Auto-generated method stub6 7 }8 });

9

他们有什么区别?

SwingUtilities.invokeLater调用后立即返回。然后执行第9行后的代码。其他线程和 invokeLater中的参数线程异步执行。互不阻塞。

SwingUtilities.invokeAndWait调用后,必须等到 线程对象 run方法在EDT中执行完了,才返回,然后继续执行第9行后的代码。

下面是一个简单的例子:用户输入2个整数 start ,end,程序计算从start 累加到end 的结果。我依然使用了线程睡眠来模拟耗时任务。因为如果我使用更加贴近现实的例子的话,又会引出更多的知识点。

虽然简单,但说明了如何让Swing更好的工作。

import java.awt.Dimension;import java.awt.GridLayout;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JOptionPane;import javax.swing.JPanel;import javax.swing.JTextField;import javax.swing.SwingUtilities;public class Demo { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable { @Override public void run { MFrame frame = new MFrame; frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); }}class MFrame extends JFrame{ public MFrame { initUI; onButtonClick; } /***************Model*************/ private int start = 0; private int end = 0; private int result = 0; /************View***************/ private JButton calcButton = null; private JTextField startField = null; private JTextField endField = null; private JTextField resultField = null; private JPanel mainpane = null; private void initUI { calcButton =new JButton('计算'); startField = new JTextField; startField.setColumns(5); endField = new JTextField; endField.setColumns(5); resultField = new JTextField; resultField.setColumns(5); resultField.setEditable(false); mainpane = new JPanel(new GridLayout(1, 4,5,0)); mainpane.setPreferredSize(new Dimension(300,50)); mainpane.add(startField); mainpane.add(endField); mainpane.add(resultField); mainpane.add(calcButton); this.setContentPane(mainpane); this.setLocationRelativeTo(null); this.pack; } //为button注册监听者 private void onButtonClick { calcButton.addActionListener(new ActionListener { @Override public void actionPerformed(ActionEvent event) { Thread calcThread = new Thread(new Runnable { @Override public void run { try{ start = Integer.parseInt(startField.getText); end = Integer.parseInt(endField.getText); for (int i = start; i <=end; i++)="" {="" result="" +="i;" 假设计算过程十分耗时,就像挖矿一样。="" thread.sleep(500);="" }="" 耗时任务完成后了,通过swingutilities.invokelater将设置任务到ui的事件发送到edt线程中。="" swingutilities.invokelater(new="" runnable="" {="" @override="" public="" void="" run="" {="" resultfield.settext(result+'');="" }="" });="" }="" catch(numberformatexception="" e)="" {="" joptionpane.showmessagedialog(mframe.this,="" '请输入一个合法的整数',="" '错误',="" joptionpane.error_message);="" }="" catch="" (interruptedexception="" e)="" {="" system.out.println('计算时错误');="" }="" }="" });="" wrok="" thread="" new="" end="" calcthread.start;="" 启用任务线程="" }="" });="">=end;>更优雅的解决办法:SwingWorker线程类

当Swing程序复杂后,自定义线程会让代码越来越庞大,不好理解。于是jdk1.6中引入了SwingWorker线程类,简化了程序员的工作。今天就写到这里,我会在以后的文章中介绍。:)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值