Java事件处理和事件派发机制

事件处理

GUI程序是事件驱动程序,因此我们需要学习Java的事件处理

常见的事件包括

  • 移动鼠标
  • 单双击鼠标各个按钮
  • 单击按钮
  • 在文本字段输入

Swing通过事件对象来包装事件,程序可以通过事件获取事件的有关信息

事件处理的几个要素

  • 事件源

    • 与用户进行交互的GUI组件,表示事件来自于哪个组件或对象
    • 比如要对按钮被按下这个事件编写程序,按钮就是事件源
    • 提供注册监听器或取消注册监听器的方法
    • 如有事件发生,已注册的监听器就会被通知
    • 一个事件源可以注册多个监听器,每个事件监听器又可以响应多种事件
  • 事件监听器

    • 负责监听事件并做出响应
    • 一旦它监视到事件发生,就会自动调用相应的事件处理程序作出响应
    • 是一个对象,通过事件源的addxxxListener方法被注册到某个事件源上
    • 不同的Swing组件可以注册不同的事件监听器
    • 一个事件监听器中可以包含有多种具体事件的专用处理方法
  • 事件对象

    • 封装了有关已发生的事件的信息
    • 例如按钮被按下就是一个要被处理的事件,当用户按下按钮时,就会产生一个事件对象,事件对象中包含事件的相关信息和事件源
    • 常用的事件对象有ActionEvent,ItemEvent等等,具体可查阅API文档

上面的基本概念很重要

我们要做的是什么?

  1. 为事件源注册一个事件监听器
  2. 实现事件处理方法

接口与适配器

  • 事件监听器接口

    就是一个抽象类,如果要使用它,必须将其定义的方法都实现

  • 事件监听器适配类

    如果你不想实现事件监听器接口的所有类,那就继承实现了事件监听器接口的子类(适配器),然后覆盖你想要单独写的事件就可以了

    当然,如果你这个类已经继承了其他的类了,这时候没办法再继承这个适配器,你还可以使用匿名的内部类(不用起名,直接New一个,然后向上转型就可以)

看一个程序

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;

public class UseInnerClass {
    JFrame f;
    public UseInnerClass()
    {
        f = new JFrame();
        f.setSize(300,150);
        f.show();
        f.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e)
            {
                f.setTitle("点击坐标为("+e.getX()+","+e.getY()+")");
            }
        });
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new UseInnerClass();
    }
}

我们分析一下代码

f.addMouseListener(new MouseAdapter() {
    public void mouseClicked(MouseEvent e)
    {
        f.setTitle("点击坐标为("+e.getX()+","+e.getY()+")");
    }
});

这段代码使用的是内部类的方法,我们前面说过,要注册一个事件监听器,需要一个已经实现事件监听器对象的类,因此我们可以直接定义一个匿名类,匿名类覆盖了想要单独实现的方法同时向上转型成其继承的类

如果你直接在该类继承了适配器或继承了接口并实现了方法,那么你就直接在构造函数那里addxxxListener(this)就可以了,因为你自身的类已经实现了事件监听器接口

f.addMouseListener(new MouseAdapter(this)

事件派发机制

前面讲到的事件处理,一个事件源触发事件后,事件监听器就会执行相应的代码,那么这一个过程是如何实现的?

还有一个问题,我们在对一个事件源组件进行处理时,这时不应该有其他的代码对其进行处理,就比如我们修改一个文本框,如果好几个事件同时修改,那就乱套了,我们是如何保证事件是串行处理的?

Java主要由一个线程,事件派发线程来实现

事件派发机制–事件派发线程

  • Swing中的组件是非线程安全的,在Swing中专门提供了一个事件派发线程EDT用于对组件的安全访问

    • 用来执行组件事件处理程序的线程,依次从系统事件队列取出事件并处理,一定要执行完上一个事件的处理程序后,才会执行下一个事件
    • 事件监听器的方法都是在事件派发线程中执行的,如ActionListener的actionPerformed方法

事件派发机制–由事件派发线程启动GUI

  • 可以调用invokeLater或invokeAndWait请事件分发线程以运行某段代码

    • 要将这段代码放入一个Runnable对象的run方法中,并将该Runnable对象作为参数传递给invokeLater
    • invokeLater是异步,不用等代码执行完就返回
    • invokeAndWait是同步的,要等代码执行完才返回,调用时要避免死锁

看一个程序

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class CardLayoutDemo implements ItemListener{
    private static String BUTTONPANEL = "JPanel with JButton";
    private static String TEXTPANEL = "JPanel with JTextField";
    private JPanel cards;
    public void addComponentToPane(Container pane)
    {
        JPanel comboBoxPane = new JPanel();
        String[] comboBoxItems = {BUTTONPANEL,TEXTPANEL};
        JComboBox cb = new JComboBox(comboBoxItems);
        cb.setEditable(false);
        cb.addItemListener(this);
        comboBoxPane.add(cb);
        
        JPanel card1 = new JPanel();
        card1.add(new JButton("Button1"));
        card1.add(new JButton("Button2"));
        card1.add(new JButton("Button3"));
        
        JPanel card2 = new JPanel();
        card2.add(new JTextField("TextField",20));
        
        cards = new JPanel(new CardLayout());
        cards.add(card1, BUTTONPANEL);
        cards.add(card2, TEXTPANEL);
        
        pane.add(comboBoxPane, BorderLayout.PAGE_START);
        pane.add(cards,BorderLayout.CENTER);
        
    }
    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("CardLayoutDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        CardLayoutDemo demo = new CardLayoutDemo();
        demo.addComponentToPane(frame.getContentPane());
        
        frame.pack();
        frame.setVisible(true);
    }
    @Override
    public void itemStateChanged(ItemEvent evt) {
        // TODO Auto-generated method stub
        CardLayout cl = (CardLayout)(cards.getLayout());
        cl.show(cards, (String)evt.getItem());
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

我们分析一下关键部分的代码

public class CardLayoutDemo implements ItemListener

这段代码表示该类自身实现了ItemListener接口

public void addComponentToPane(Container pane)
{
    JPanel comboBoxPane = new JPanel();
    String[] comboBoxItems = {BUTTONPANEL,TEXTPANEL};
    JComboBox cb = new JComboBox(comboBoxItems);
    cb.setEditable(false);
    cb.addItemListener(this);
    comboBoxPane.add(cb);
    
    JPanel card1 = new JPanel();
    card1.add(new JButton("Button1"));
    card1.add(new JButton("Button2"));
    card1.add(new JButton("Button3"));
    
    JPanel card2 = new JPanel();
    card2.add(new JTextField("TextField",20));
    
    cards = new JPanel(new CardLayout());
    cards.add(card1, BUTTONPANEL);
    cards.add(card2, TEXTPANEL);
    
    pane.add(comboBoxPane, BorderLayout.PAGE_START);
    pane.add(cards,BorderLayout.CENTER);   
}

这段代码基本上全是使用一个中间容器来容纳原子组件,然后再将中间容器加到顶层容器中,关于Swing层次这一部分内容可以看这篇博客Swing层次结构

里面这一行代码cb.addItemListener(this);就是注册事件监听器

这一行代码cards = new JPanel(new CardLayout());实现了CardLayout布局;

public void itemStateChanged(ItemEvent evt) {
    // TODO Auto-generated method stub
    CardLayout cl = (CardLayout)(cards.getLayout());
    cl.show(cards, (String)evt.getItem());
}

这一段代码就是实现事件处理函数,首先定义一个CradLayout cl得到cards的布局,然后再通过事件源evt得到要显示的内容evt.getItem()

javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run()
    {
        createAndShowGUI();
    }
});

这一段代码就是将我们的代码放到事件派发线程里面去执行了
在这里插入图片描述
在这里插入图片描述
参考:
清华大学Java教程p95-p96

猜你喜欢

转载自blog.csdn.net/jump_into_zehe/article/details/106770419