An example from "Head First Java". The essence of the program is that when you click on the button, notes are played, in time with which rectangles are drawn on the My Draw Panel panel Figure panel. But for some reason, copies of the frame components are drawn together with them in the drawing panel. As a result, the drawing panel moves down and to the right.
Code starting program
Frame after playback
Code:
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);
// We create a dance panel.
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(); // Create a drawing panel.
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));
// Create a button panel.
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);
// We create a frame.
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().
// Panel for drawing is now a listener.
class MyDrawPanel extends JPanel implements ControllerEventListener {
// We assign the flag the value of "FALSE" and we will install "TRUE" when we get a state.
boolean msg = false;
public Dimension getPreferredSize() {
return new Dimension(400, 340);
}
// The method of processing an event from the listener's interface.
public void controlChange(ShortMessage event) {
// We get an event, assigning the flag the value of "True" and calling the repaint().
msg = true;
repaint();
} // End of controlChange(ShortMessage event).
public void paintComponent(Graphics g) {
// We use the MSG flag so that drawing is triggered only when the Controllerevent event occurs, and other objects could not launch repaint().
if (msg) {
Graphics2D g2 = (Graphics2D) g;
// Random color.
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));
// Random position of the figure.
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).
}
By applying the opacity rule and the super.paintComponent() method, the artifacts disappear, but the shapes on the screen are drawn one at a time, without saving the previous ones. I need the previous drawings to be saved. How do I remove artifacts while saving previous shapes?
The problem is solved by buffering using the BufferedImage class. The answers to these questions helped solve my question: Painting in a BufferedImage inside Swing and Drawing a rectangle that won't disappear in next paint. The example at the end of this article also helped - https://coderlessons.com/tutorials/akademicheskii/tsifrovaia-obrabotka-izobrazhenii-s-ispolzovaniem-java/klass-java-bufferedimage. I also had to go back to the second version of the Mini Music Player code in the book "Head First Java 2". Thanks to all the people who tried to help!
The final code:
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).
}
}