事件处理

在Swing中,按钮点击、鼠标移动、键盘输入等事件处理是与用户交互的关键部分。Swing的事件处理机制遵循的是观察者模式(Observer Pattern),通过事件源、事件对象和事件监听器来实现。不过实际上Swing的事件处理部分是借用AWT的实现,因此我们可能发现很多的类其实位于AWT包下。这篇笔记我们对Swing的事件处理机制进行详细介绍。

EventListener 事件监听器

Swing中,事件源就是发出事件的对象,例如对于按钮,点击按钮就会发出按钮点击事件。事件监听器则需要注册到事件源的,事件监听器通常是一个回调函数,告知当此事件发生时,程序该怎么做。我们简单看一下ActionListener这个接口。

public interface ActionListener extends EventListener {
    public void actionPerformed(ActionEvent e);
}

我想经验丰富的同学一看这个就知道监听器怎么写了,其实actionPerformed就是一个回调函数,事件发生时就会被回调,传入一个事件对象,我们根据这个事件对象作出相应判断即可。我们可以实现这个接口实现自己的监听器类,或者直接使用匿名类。除了ActionListener,Swing中还有许多其它的监听器实现GUI界面的各种功能,具体可以参考文档。

EventObject 事件对象

事件被封装在事件对象中,事件对象有很多,如ActionEventWindowEvent等,事件对象又可以划分成不同的继承层次,所有事件派生自java.util.EventObject

JButton btnHello = new JButton("hello");
btnHello.addActionListener((e)->{
    System.out.println("button clicked!");
});

事件对象还有一些其他的实用方法:

  • Object getSource() 返回发生事件的对象的引用。
  • String getActionCommand() 返回这个事件的动作字符串。如果事件来自按钮,默认动作字符串是按钮标签,除非使用setActionCommand()对按钮对象的动作字符串进行修改。

Action 动作

有时候多个事件源(控件)需要绑定同一种命令(操作),多个控件绑定同一个监听器是可以实现的,但是Swing再次封装了一层,提供了Action接口用来封装命令,并把它连接到多个事件源。具体使用Action的好处请往下看。我们先大致看下Action接口有什么内容。

public interface Action extends ActionListener {
    public static final String DEFAULT = "Default";
    public static final String NAME = "Name";
    public static final String SHORT_DESCRIPTION = "ShortDescription";
    public static final String LONG_DESCRIPTION = "LongDescription";
    public static final String SMALL_ICON = "SmallIcon";
    public static final String ACTION_COMMAND_KEY = "ActionCommandKey";
    public static final String ACCELERATOR_KEY="AcceleratorKey";
    public static final String MNEMONIC_KEY="MnemonicKey";
    public static final String SELECTED_KEY = "SwingSelectedKey";
    public static final String DISPLAYED_MNEMONIC_INDEX_KEY = "SwingDisplayedMnemonicIndexKey";
    public static final String LARGE_ICON_KEY = "SwingLargeIconKey";
    public Object getValue(String key);
    public void putValue(String key, Object value);
    public void setEnabled(boolean b);
    public boolean isEnabled();
    public void addPropertyChangeListener(PropertyChangeListener listener);
    public void removePropertyChangeListener(PropertyChangeListener listener);
}

这个接口实际上并不复杂,我们一组一组看。

首先,Action接口继承了ActionListener,那它就由ActionPerformed()回调函数。除此之外,还定义了上述的一些属性的方法。

  • setEnabled()/isEnabled() 启用禁用这个动作/检查动作是否可用,不可用按钮会显示为灰色
  • getValue()/putValue() 存储/读出动作键属性值对,有一些Swing预定义的键值对可以使用:
  • NAME 动作名称,显示在按钮和菜单上
  • SMALL_ICON 存储小图标,显示在按钮,菜单或工具栏中
  • SHORT_DESCRIPTION 动作的简短说明
  • LONG_DESCRIPTION 详细说明,Swing组件不使用这个值
  • MNEMONIC_KEY 快捷键缩写,显示在菜单栏中
  • ACCELERATOR_KEY 存储加速击键的地方,Swing组件不使用这个值
  • DEFAULT 常用的综合属性,Swing组件不使用这个值
  • addPropertyChangeListener()/removePropertyChangeListener() 属性变更监听器

当动作被赋予一个控件时,动作属性就会自动被显示出来。这比多个控件编写相同的动作名、图标、说明等等简洁多了。

实际上,我们不需要自己实现Action接口,Swing提供了一个AbstractAction实现了这个接口,我们继承这个类,并重写ActionPerformed()就好了。

一个自定义Action的例子代码如下。

class MyAction extends AbstractAction {
    public MyAction(String name, Icon icon) {
        putValue(Action.NAME, name);
        putValue(Action.SMALL_ICON, icon);
        putValue(Action.SHORT_DESCRIPTION, "test description");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("hello");
    }
}

调用这个Action创建按钮例子代码如下。

JButton btn = new JButton(new MyAction("btn", icon));

使用适配器模式

上面介绍的Listener接口如果有不止一个回调方法,那么显然我们创建类时就需要实现其全部方法,比如WindowListener用于监听窗体的生命周期,非常麻烦,我们实现如何优雅的实现此类Listner呢?其实Swing还提供了一系列适配器,比如对应WindowListener就有WindowAdaptor

package com.gacfox.demo;

import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class MainFrame extends JFrame {
    public MainFrame() {
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowDeactivated(WindowEvent e) {
                System.out.println("窗口失去了焦点");
            }
        });
        this.setSize(300, 200);
    }
}

此时虽然不能使用lambda表达式,但是我们创建的匿名类只用实现一个方法即可。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap