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

Java Swing音乐绘图程序:消除残影且保留历史图形的问题求助

解决MIDI绘图面板残影与保留历史图形的问题

这是《Head First Java》中的示例程序:点击按钮播放MIDI音乐,同步在MyDrawPanel面板绘制矩形。但绘图时会出现窗体组件残影,导致面板偏移。使用super.paintComponent()方法可消除残影,但无法保留之前绘制的图形。现寻求既能消除残影又能保留历史图形的解决方案。

界面说明

  • 初始界面:包含黄色背景的“舞台”标签、灰色绘图面板,右侧配有“播放”按钮
  • 播放后界面:绘图面板出现组件残影,面板位置偏移,已绘制的矩形叠加混乱

原程序代码

import javax.sound.midi.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MiniMusicPlayer5 {

MyDrawPanel panelFigure;

public static void main(String[] args) {
    MiniMusicPlayer5 mini = new MiniMusicPlayer5();
    mini.getGuiPanel();
}

public void getGuiPanel() {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    // 创建舞台面板
    JLabel label = new JLabel("舞台");
    label.setBackground(Color.yellow);
    label.setOpaque(true);
    label.setAlignmentX(JLabel.CENTER_ALIGNMENT);

    JPanel panelDance = new JPanel();
    panelDance.setBackground(Color.gray);
    Dimension d1 = new Dimension(400, 340);
    panelDance.setPreferredSize(d1);
    panelDance.setMaximumSize(d1);
    panelDance.setAlignmentX(JPanel.CENTER_ALIGNMENT);

    panelFigure = new MyDrawPanel(); // 创建绘图面板
    panelDance.add(panelFigure);

    JPanel panelMain = new JPanel();
    panelMain.setLayout(new BoxLayout(panelMain, BoxLayout.Y_AXIS));
    panelMain.add(label);
    panelMain.add(panelDance);
    panelMain.revalidate();
    panelMain.repaint();
    panelMain.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20));

    // 创建按钮面板
    JButton buttonPlay = new JButton("播放");
    buttonPlay.addActionListener(new ButtonPlayListener());

    JPanel panel = new JPanel();
    panel.setBorder(BorderFactory.createEmptyBorder(60, 20, 0, 20));
    panel.add(panelMain);
    panel.add(buttonPlay);

    // 创建窗体
    frame.setContentPane(panel);
    frame.setSize(600, 500);
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
} // End of setUpGui().

public class ButtonPlayListener implements ActionListener {
    public void actionPerformed(ActionEvent ev) {
        try {
            Sequencer player = MidiSystem.getSequencer();
            player.open();
            player.addControllerEventListener(panelFigure, new int[]{127});
            Sequence seq = new Sequence(Sequence.PPQ, 4);
            Track track = seq.createTrack();

            for (int i = 0; i < 100; i += 4) {
                int note = (int) ((Math.random() * 50) + 1);
                if (note < 38) {
                    track.add(makeEvent(144, 1, note, 100, i));
                    track.add(makeEvent(176, 1, 127, 0, i));
                    track.add(makeEvent(128, 1, note, 100, i + 2));
                }
            }
            player.setSequence(seq);
            player.setTempoInBPM(180);
            player.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
} // End of play().

public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
    MidiEvent event = null;
    try {
        ShortMessage a = new ShortMessage();
        a.setMessage(comd, chan, one, two);
        event = new MidiEvent(a, tick);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return event;
} // End of MidiEvent makeEvent().

// 绘图面板现在是监听器
class MyDrawPanel extends JPanel implements ControllerEventListener {
    // 将标志设为FALSE,收到事件时设为TRUE
    boolean msg = false;

    public Dimension getPreferredSize() {
        return new Dimension(400, 340);
    }

    // 监听器接口的事件处理方法
    public void controlChange(ShortMessage event) {
        // 收到事件,将标志设为True并调用repaint()
        msg = true;
        repaint();
    } // End of controlChange(ShortMessage event).

    public void paintComponent(Graphics g) {
        // 使用MSG标志确保仅在Controllerevent事件触发时绘图
        if (msg) {
            Graphics2D g2 = (Graphics2D) g;

            // 随机颜色
            int red = (int) (Math.random() * 250);
            int green = (int) (Math.random() * 250);
            int blue = (int) (Math.random() * 250);
            g2.setColor(new Color(red, green, blue));

            // 随机图形位置
            int x = (int) ((Math.random() * 200) + 30);
            int y = (int) ((Math.random() * 170) + 90);
            int width = (int) ((Math.random() * 200) + 0);
            int height = (int) ((Math.random() * 170) + 0);
            g2.fillRect(x, y, width, height);
            msg = false;
        } // Closed if.
    } // End of void paintComponent(Graphics g).
}

解决方案

要同时消除残影并保留历史图形,核心思路是维护历史图形数据,每次重绘时先清空面板再重新绘制所有历史图形,具体实现如下:

修改步骤

  1. 定义内部类存储矩形的颜色、位置和尺寸信息
  2. 在MyDrawPanel中添加集合保存所有历史矩形数据
  3. 收到MIDI事件时,生成新矩形数据并加入集合,触发重绘
  4. 重写paintComponent方法,先调用super.paintComponent(g)清除残影,再遍历集合绘制所有历史矩形

修改后的完整代码

import javax.sound.midi.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

public class MiniMusicPlayer5 {

MyDrawPanel panelFigure;

public static void main(String[] args) {
    MiniMusicPlayer5 mini = new MiniMusicPlayer5();
    mini.getGuiPanel();
}

public void getGuiPanel() {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    // 创建舞台面板
    JLabel label = new JLabel("舞台");
    label.setBackground(Color.yellow);
    label.setOpaque(true);
    label.setAlignmentX(JLabel.CENTER_ALIGNMENT);

    JPanel panelDance = new JPanel();
    panelDance.setBackground(Color.gray);
    Dimension d1 = new Dimension(400, 340);
    panelDance.setPreferredSize(d1);
    panelDance.setMaximumSize(d1);
    panelDance.setAlignmentX(JPanel.CENTER_ALIGNMENT);

    panelFigure = new MyDrawPanel(); // 创建绘图面板
    panelDance.add(panelFigure);

    JPanel panelMain = new JPanel();
    panelMain.setLayout(new BoxLayout(panelMain, BoxLayout.Y_AXIS));
    panelMain.add(label);
    panelMain.add(panelDance);
    panelMain.revalidate();
    panelMain.repaint();
    panelMain.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20));

    // 创建按钮面板
    JButton buttonPlay = new JButton("播放");
    buttonPlay.addActionListener(new ButtonPlayListener());

    JPanel panel = new JPanel();
    panel.setBorder(BorderFactory.createEmptyBorder(60, 20, 0, 20));
    panel.add(panelMain);
    panel.add(buttonPlay);

    // 创建窗体
    frame.setContentPane(panel);
    frame.setSize(600, 500);
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
} // End of setUpGui().

public class ButtonPlayListener implements ActionListener {
    public void actionPerformed(ActionEvent ev) {
        try {
            Sequencer player = MidiSystem.getSequencer();
            player.open();
            player.addControllerEventListener(panelFigure, new int[]{127});
            Sequence seq = new Sequence(Sequence.PPQ, 4);
            Track track = seq.createTrack();

            for (int i = 0; i < 100; i += 4) {
                int note = (int) ((Math.random() * 50) + 1);
                if (note < 38) {
                    track.add(makeEvent(144, 1, note, 100, i));
                    track.add(makeEvent(176, 1, 127, 0, i));
                    track.add(makeEvent(128, 1, note, 100, i + 2));
                }
            }
            player.setSequence(seq);
            player.setTempoInBPM(180);
            player.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
} // End of play().

public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
    MidiEvent event = null;
    try {
        ShortMessage a = new ShortMessage();
        a.setMessage(comd, chan, one, two);
        event = new MidiEvent(a, tick);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return event;
} // End of MidiEvent makeEvent().

// 存储矩形信息的内部类
static class RectInfo {
    Color color;
    int x, y, width, height;

    public RectInfo(Color color, int x, int y, int width, int height) {
        this.color = color;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
}

// 绘图面板现在是监听器
class MyDrawPanel extends JPanel implements ControllerEventListener {
    // 保存所有历史矩形的集合
    private List<RectInfo> rectList = new ArrayList<>();

    public Dimension getPreferredSize() {
        return new Dimension(400, 340);
    }

    // 监听器接口的事件处理方法
    public void controlChange(ShortMessage event) {
        // 生成新矩形的随机参数
        int red = (int) (Math.random() * 250);
        int green = (int) (Math.random() * 250);
        int blue = (int) (Math.random() * 250);
        Color color = new Color(red, green, blue);

        int x = (int) ((Math.random() * 200) + 30);
        int y = (int) ((Math.random() * 170) + 90);
        int width = (int) ((Math.random() * 200) + 0);
        int height = (int) ((Math.random() * 170) + 0);
        
        // 添加到历史集合
        rectList.add(new RectInfo(color, x, y, width, height));
        // 触发重绘
        repaint();
    } // End of controlChange(ShortMessage event).

    public void paintComponent(Graphics g) {
        // 调用父类方法清除面板,解决残影问题
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        // 遍历绘制所有历史矩形
        for (RectInfo rect : rectList) {
            g2.setColor(rect.color);
            g2.fillRect(rect.x, rect.y, rect.width, rect.height);
        }
    } // End of void paintComponent(Graphics g).
}

方案说明

  • 通过super.paintComponent(g)强制清除面板内容,彻底解决残影和偏移问题
  • 使用List<RectInfo>存储所有已绘制的矩形数据,每次重绘时重新绘制全部历史图形,实现保留效果
  • 移除了原有的msg标志,因为重绘逻辑改为全量绘制历史图形,无需判断触发条件

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 02:07:07