You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing中KeyListener与ChangeListener组合后功能失效问题

问题排查与修复

问题现象

Swing实现的程序中,单独使用KeyPressEvent或ChangeEvent功能正常,但组合后出现两个异常:

  • 红色JLabel无法通过WASD按键移动
  • 显示speed值的JLabel s1 完全不显示

问题原因

  1. 焦点与事件传递问题:直接给JFrame添加KeyListener,但当JSlider获得焦点时,键盘事件会被Slider拦截,不会传递到JFrame,导致WASD按键无响应。
  2. 组件重叠遮挡:s1标签的位置(200,50)和JSlider的区域(位置50,10,大小400x100)重叠,Slider在组件层级上层,完全遮挡了s1。
  3. Swing线程安全问题:在main线程直接创建GUI组件,违反Swing必须在事件调度线程(EDT)中创建和操作组件的规则,可能引发组件显示异常。

修复方案

针对上述问题,做以下修改:

  • 改用Key Bindings替代KeyListener,直接给窗口的根面板绑定按键事件,不受焦点影响
  • 调整s1标签的位置,避免与Slider重叠
  • 使用SwingUtilities.invokeLater在EDT中初始化GUI

修改后的完整代码

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;

class MainWindow extends JFrame implements ChangeListener {

    JLabel j;
    JLabel s1;
    int speed;
    JSlider s;

    MainWindow() {
        this.setSize(500, 500);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle("Todo Application");
        this.setLayout(null);

        // 初始化Slider
        s = new JSlider(0, 25, 10);
        s.setLocation(50, 10);
        s.setSize(400, 50); // 缩小Slider高度,避免遮挡
        s.addChangeListener(this);
        this.add(s);

        speed = s.getValue();

        // 调整s1位置,避免重叠
        s1 = new JLabel("Speed: " + speed);
        s1.setLocation(200, 70);
        s1.setSize(100, 30);
        this.add(s1);

        // 红色标签
        j = new JLabel();
        j.setSize(100, 100);
        j.setBackground(Color.red);
        j.setOpaque(true);
        j.setLocation(200, 200);
        this.add(j);

        // 使用Key Bindings绑定WASD按键
        setupKeyBindings();

        this.setVisible(true); // 最后设置可见,确保所有组件已初始化
    }

    private void setupKeyBindings() {
        InputMap inputMap = this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = this.getRootPane().getActionMap();

        // 绑定W键
        inputMap.put(KeyStroke.getKeyStroke('w'), "moveUp");
        actionMap.put("moveUp", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                j.setLocation(j.getX(), j.getY() - speed);
            }
        });

        // 绑定S键
        inputMap.put(KeyStroke.getKeyStroke('s'), "moveDown");
        actionMap.put("moveDown", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                j.setLocation(j.getX(), j.getY() + speed);
            }
        });

        // 绑定A键
        inputMap.put(KeyStroke.getKeyStroke('a'), "moveLeft");
        actionMap.put("moveLeft", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                j.setLocation(j.getX() - speed, j.getY());
            }
        });

        // 绑定D键
        inputMap.put(KeyStroke.getKeyStroke('d'), "moveRight");
        actionMap.put("moveRight", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                j.setLocation(j.getX() + speed, j.getY());
            }
        });
    }

    @Override
    public void stateChanged(ChangeEvent e) {
        speed = s.getValue();
        s1.setText("Speed: " + speed);
    }
}

public class Main {
    public static void main(String[] args) {
        // 在EDT中初始化GUI
        SwingUtilities.invokeLater(MainWindow::new);
    }
}

关键修改说明

  • Key Bindings替代KeyListener:WHEN_IN_FOCUSED_WINDOW确保只要窗口处于焦点,按键事件就会被捕获,不受单个组件焦点影响。
  • 调整组件位置:缩小Slider高度并下移s1标签,避免重叠遮挡。
  • EDT初始化GUI:SwingUtilities.invokeLater保证GUI操作在正确的线程中执行,避免显示异常。

内容的提问来源于stack exchange,提问作者Sunny Reddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:45:06