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). }
解决方案
要同时消除残影并保留历史图形,核心思路是维护历史图形数据,每次重绘时先清空面板再重新绘制所有历史图形,具体实现如下:
修改步骤
- 定义内部类存储矩形的颜色、位置和尺寸信息
- 在MyDrawPanel中添加集合保存所有历史矩形数据
- 收到MIDI事件时,生成新矩形数据并加入集合,触发重绘
- 重写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
相关产品推荐
相关产品推荐

