Java Swing. При наступлении события (воспроизведение ноты 127) вместе с объектом типа Graphics заново рисуются все компоненты фрейма
Пример из Head First Java. Суть программы - при нажатии на кнопку воспроизводятся ноты, в такт которым на панели MyDrawPanel panelFigure рисуются прямоугольники. Но вместе с ними почему-то в панели для рисования рисуются копии компонентов фрейма. В результате панель для рисования сдвигается вниз и вправо. Применяя правило непрозрачности и метод 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);
} // Конец 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();
}
}
} // Конец 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;
} // Конец MidiEvent makeEvent().
// Панель для рисования - это теперь слушатель.
class MyDrawPanel extends JPanel implements ControllerEventListener {
// Присваиваем флагу значение "false" и будем устанавливать "true",
// когда получим cобытие.
boolean msg = false;
public Dimension getPreferredSize() {
return new Dimension(400, 340);
}
// Метод обработки события из интерфейса слушателя.
public void controlChange(ShortMessage event) {
// Получаем событие, присвоив флагу значение "true" и вызвав repaint().
msg = true;
repaint();
} // Конец controlChange(ShortMessage event).
public void paintComponent(Graphics g) {
// Используем флаг msg, чтобы рисование срабатывало только при наступлении
// события ControllerEvent, а другие объекты не могли запустить repaint().
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;
} // Закрываем if.
} // Конец метода paintComponent(Graphics g).
}
}
Ответы (1 шт):
Проблема решена с помощью буферизации с использованием класса BufferedImage. Помогли решить мой вопрос ответы на данные вопросы: https://stackoverflow.com/questions/6132988/painting-in-a-bufferedimage-inside-swing и https://stackoverflow.com/questions/12683533/drawing-a-rectangle-that-wont-disappear-in-next-paint/12683601#12683601. Также помог пример в конце данной статьи - https://coderlessons.com/tutorials/akademicheskii/tsifrovaia-obrabotka-izobrazhenii-s-ispolzovaniem-java/klass-java-bufferedimage. Также пришлось вернуться ко второй версии кода MiniMusicPlayer в книге "Head First Java 2".
Окончательный код:
import javax.sound.midi.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
public class MiniMusicPlayer5 {
private BufferedImage panelFigure;
private JPanel dancePanel;
private RenderingHints renderingHints;
private boolean msg = false;
private DrawFigure drawFigure;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MiniMusicPlayer5().getGuiPanel());
}
public void getGuiPanel() {
Map<RenderingHints.Key, Object> hintsMap = new HashMap<>();
hintsMap.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
hintsMap.put(RenderingHints.KEY_DITHERING,
RenderingHints.VALUE_DITHER_ENABLE);
hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
renderingHints = new RenderingHints(hintsMap);
JFrame frame = new JFrame("Танцы фигур");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder
(60, 20, 0, 20));
JLabel label = new JLabel("Танцпол");
label.setBackground(Color.yellow);
label.setOpaque(true);
label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
// Создаём панель танца фигур.
panelFigure = new BufferedImage
(400, 340, BufferedImage.TYPE_INT_ARGB);
dancePanel = new JPanel();
dancePanel.setBackground(Color.gray);
Dimension d1 = new Dimension(400, 340);
dancePanel.setPreferredSize(d1);
dancePanel.setMaximumSize(d1);
dancePanel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
dancePanel.setOpaque(true);
drawFigure = new DrawFigure();
dancePanel.add(drawFigure, null);
JPanel panelMain = new JPanel();
panelMain.setLayout(new BoxLayout(panelMain, BoxLayout.Y_AXIS));
panelMain.add(label);
panelMain.add(dancePanel);
panelMain.setBorder(BorderFactory.createEmptyBorder
(0, 0, 0, 20));
// Создаём кнопку.
JButton buttonPlay = new JButton("Проиграть");
buttonPlay.addActionListener(new ButtonPlayListener());
panel.add(panelMain);
panel.add(buttonPlay);
frame.setContentPane(panel);
frame.setSize(600, 500);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} // Конец getGuiPanel().
public class ButtonPlayListener implements ActionListener {
public void actionPerformed(ActionEvent ev) {
try {
Sequencer player = MidiSystem.getSequencer();
player.open();
player.addControllerEventListener(drawFigure, 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(new MidiEvent(new ShortMessage(ShortMessage.
NOTE_ON, 1, note, 100), i));
track.add(new MidiEvent(new ShortMessage(ShortMessage.
CONTROL_CHANGE, 1, 127, 0), i));
track.add(new MidiEvent(new ShortMessage(ShortMessage.
NOTE_OFF, 1, note, 100), i + 2));
}
}
player.setSequence(seq);
player.setTempoInBPM(180);
player.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public class DrawFigure extends JPanel implements ControllerEventListener {
public void paintComponent(Graphics g) {
super.paintComponents(g);
Image img = createImage();
g.drawImage(img, 0,0,this);
}
private Image createImage() {
Graphics g = panelFigure.getGraphics();
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHints(renderingHints);
if (msg) {
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) + 20);
int y = (int) ((Math.random() * 170) + 20);
int width = (int) ((Math.random() * 200) + 0);
int height = (int) ((Math.random() * 170) + 0);
g2.fillRect(x, y, width, height);
msg = false;
}
return panelFigure;
}
public Dimension getPreferredSize() {
return new Dimension(400, 340);
}
// Метод обработки события из интерфейса слушателя.
public void controlChange(ShortMessage event) {
// Получаем событие, присвоив флагу значение "true" и вызвав...
// repaint().
msg = true;
dancePanel.repaint();
dancePanel.revalidate();
} // Конец controlChange(ShortMessage event).
}
}


