一个乐谱软件中JPanel添加Graphics的正确方法
The right way to add Graphics to a JPanel in a music notation software
我一直在开发一个乐谱软件,我想用它来训练我的音乐学生视奏。
我想知道我采取的方法是否正确,或者是否有更好的方法。
我已经成功地使用支持音乐符号的 unicode 和字体制作谱号、音符和五线谱对象,使用如下代码:
public static void spaceStaff(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Bravura", Font.PLAIN, 32);
g2.setFont(font);
g2.drawString("\uD834\uDD1A", note.spacing-2, staffDistance);
note.spacing = note.spacing + 16;
}
然后我有一个名为 Surface 的 class 将笔记绘制到 JPanel 上:
public class Surface extends JPanel {
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
staff.spaceStaff(g);
clef.drawGclef(g);//not given in example code above
note.drawCrotchet(g, note.B1);//not given in example code above
}
}
我正在使用此代码启动应用程序并显示音符:
public class SightreadHelper extends JFrame {
public SightreadHelper(){
initUI();
}
private void initUI() {
JButton button = new JButton("add notes"); //explanation for this button below
button.setSize(150, 75);
button.setVisible(true);
add( button );
button.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
repaint();//this doesn't work as I expect: explanation below
}
});
Surface srf = new Surface();
add(new Surface());
setSize(700, 1000);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
SightreadHelper srh = new SightreadHelper();
srh.setVisible(true);
}
});
}
}
该按钮应该在现有注释的末尾添加注释,但它没有。
我是否使用正确的程序来做我想做的事?非常感谢你的帮助。
我看到了这个问题,但对我没有帮助:Repaint Applets in JAVA without losing previous contents
总的来说,您的方法可能会奏效,但如果您倾向于将其推送到应用程序,那么它是结构简单才能正常工作的方法。您在这里缺少数据模型。您的软件直接绘制音符,而没有表示内部状态的模型。
您应该看看 Model View Controller 抽象模式。并首先开发一个数据模型,这与渲染笔记无关。
然后您开始为单个元素开发可视化,例如笔记。您将根据数据模型告诉您的内容在面板中组合组件并呈现它们。这就是您应该在 doPaintComponent 方法中执行的操作。
当您点击添加注释按钮时,应该会发生以下步骤:
为您的模型添加备注
你的模型创建了一个 ui 事件,结果调用重绘方法,甚至可能重绘你的 ui 的布局方法,理想情况下只重绘需要重绘的部分.
如果您在渲染过程中没有看到某些东西,请确保首先检查以下内容:
- 你的渲染代码是被awt线程调用的吗?
- 你的组件可见吗?
- 你画的位置对吗?
您或许应该阅读一个或多个关于开发自定义 Swing 组件的教程,例如 Oracle Tutorial Custom Swing component。
我知道答案可能在短期内对您没有真正帮助 运行,但我相信在尝试以某种方式编写代码之前先了解 UI 开发的概念会更好 运行。
可视化音乐sheet的class,它应该嵌入到应用程序中,而不是在其上启动一个框架。它仅用于演示 MVC 编码模式。
可视化严格描绘了模型 (NoteSequence) 在说什么。
package com.musicsheet;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class MusicSheetComponent extends JPanel implements PropertyChangeListener {
private NoteSequence noteSequence;
public MusicSheetComponent(NoteSequence noteSequence) {
this.noteSequence = noteSequence;
this.noteSequence.addNoteSequenceChangedListener(this);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
NoteSequence noteSequence = new NoteSequence(); // the model
f.setLayout(new BorderLayout()); // how should the screen be layouted
f.add(new MusicSheetComponent(noteSequence), BorderLayout.CENTER); // the sheet component is the view, it renders whatever the model
// tells
f.add(new JButton(new AbstractAction("Add note") {
@Override
public void actionPerformed(ActionEvent e) {
noteSequence.addNote(new NoteSequence.Note((int)(Math.random()*5))); // add a note on a random line
}
}), BorderLayout.SOUTH);
f.setSize(320, 240);
f.setVisible(true);
});
}
@Override
protected void paintComponent(Graphics g2d) {
super.paintComponent(g2d);
Graphics2D g = (Graphics2D) g2d; // graphics2d has more functions
int w = getWidth();
int h = getHeight();
int lines = 5;
int spacing = h / lines;
paintSheetBackground(g, w, h, spacing);
drawNotes(g, spacing);
}
private void paintSheetBackground(Graphics2D g, int w, int h, int spacing) {
g.setColor(Color.white);
g.fillRect(0, 0, w, h);
g.setColor(Color.black);
for (int i = 0; i < 5; i++) {
final int y2 = i * spacing;
g.drawLine(0, y2, w, y2);
}
}
private void drawNotes(Graphics2D g, int heigtSpacing) {
int xSpacing = 30;
int x = 0;
for (NoteSequence.Note note : noteSequence.getNotes()) {
int y = (int) (note.getLine()*heigtSpacing);
g.fillOval(x, y-3, 7, 6); // -3 because note should sit on line
x+= xSpacing;
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (NoteSequence.PROP_NOTE_ADDED.equals(evt.getPropertyName()) || NoteSequence.PROP_NOTE_REMOVED.equals(evt.getPropertyName())) {
repaint();
}
}
}
接下来,class 模拟音乐 sheet 或音符序列。
package com.musicsheet;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The model class, that holds all notes.
*/
public class NoteSequence {
public static final String PROP_NOTE_ADDED = "noteAdded";
public static final String PROP_NOTE_REMOVED = "noteRemoved";
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private final List<Note> notes = new ArrayList<>();
public List<Note> getNotes() {
return Collections.unmodifiableList(notes);
}
/**
* is called by the UI or a button listener when a note should be added to this music sheet / notesequence
* @param note
*/
public void addNote(Note note) {
this.notes.add(note);
propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this, PROP_NOTE_ADDED, null, note));
}
public void removeNote(Note note) {
this.notes.remove(note);
propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this, PROP_NOTE_REMOVED, note, null));
}
void addNoteSequenceChangedListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
// not really needed atm
void removeNoteSequenceChangedListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
/**
* a single note.
*/
public static class Note {
private float line; // where does the note sit
private float timestampSinceBeginning; // not used, but you have to know WHEN a note is played
// ... more properties, e.g. name, or modifiers or whatever
public Note(float line) { // this is certainly to easy, since notes can sit in between lines,
// i did not try to think deeply into it, to define a better model
this.line = line;
}
public float getLine() {
return line;
}
public float getTimestampSinceBeginning() {
return timestampSinceBeginning;
}
}
}
我一直在开发一个乐谱软件,我想用它来训练我的音乐学生视奏。
我想知道我采取的方法是否正确,或者是否有更好的方法。
我已经成功地使用支持音乐符号的 unicode 和字体制作谱号、音符和五线谱对象,使用如下代码:
public static void spaceStaff(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Bravura", Font.PLAIN, 32);
g2.setFont(font);
g2.drawString("\uD834\uDD1A", note.spacing-2, staffDistance);
note.spacing = note.spacing + 16;
}
然后我有一个名为 Surface 的 class 将笔记绘制到 JPanel 上:
public class Surface extends JPanel {
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
staff.spaceStaff(g);
clef.drawGclef(g);//not given in example code above
note.drawCrotchet(g, note.B1);//not given in example code above
}
}
我正在使用此代码启动应用程序并显示音符:
public class SightreadHelper extends JFrame {
public SightreadHelper(){
initUI();
}
private void initUI() {
JButton button = new JButton("add notes"); //explanation for this button below
button.setSize(150, 75);
button.setVisible(true);
add( button );
button.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
repaint();//this doesn't work as I expect: explanation below
}
});
Surface srf = new Surface();
add(new Surface());
setSize(700, 1000);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
SightreadHelper srh = new SightreadHelper();
srh.setVisible(true);
}
});
}
}
该按钮应该在现有注释的末尾添加注释,但它没有。
我是否使用正确的程序来做我想做的事?非常感谢你的帮助。 我看到了这个问题,但对我没有帮助:Repaint Applets in JAVA without losing previous contents
总的来说,您的方法可能会奏效,但如果您倾向于将其推送到应用程序,那么它是结构简单才能正常工作的方法。您在这里缺少数据模型。您的软件直接绘制音符,而没有表示内部状态的模型。
您应该看看 Model View Controller 抽象模式。并首先开发一个数据模型,这与渲染笔记无关。
然后您开始为单个元素开发可视化,例如笔记。您将根据数据模型告诉您的内容在面板中组合组件并呈现它们。这就是您应该在 doPaintComponent 方法中执行的操作。
当您点击添加注释按钮时,应该会发生以下步骤:
为您的模型添加备注
你的模型创建了一个 ui 事件,结果调用重绘方法,甚至可能重绘你的 ui 的布局方法,理想情况下只重绘需要重绘的部分.
如果您在渲染过程中没有看到某些东西,请确保首先检查以下内容:
- 你的渲染代码是被awt线程调用的吗?
- 你的组件可见吗?
- 你画的位置对吗?
您或许应该阅读一个或多个关于开发自定义 Swing 组件的教程,例如 Oracle Tutorial Custom Swing component。
我知道答案可能在短期内对您没有真正帮助 运行,但我相信在尝试以某种方式编写代码之前先了解 UI 开发的概念会更好 运行。
可视化音乐sheet的class,它应该嵌入到应用程序中,而不是在其上启动一个框架。它仅用于演示 MVC 编码模式。 可视化严格描绘了模型 (NoteSequence) 在说什么。
package com.musicsheet;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class MusicSheetComponent extends JPanel implements PropertyChangeListener {
private NoteSequence noteSequence;
public MusicSheetComponent(NoteSequence noteSequence) {
this.noteSequence = noteSequence;
this.noteSequence.addNoteSequenceChangedListener(this);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
NoteSequence noteSequence = new NoteSequence(); // the model
f.setLayout(new BorderLayout()); // how should the screen be layouted
f.add(new MusicSheetComponent(noteSequence), BorderLayout.CENTER); // the sheet component is the view, it renders whatever the model
// tells
f.add(new JButton(new AbstractAction("Add note") {
@Override
public void actionPerformed(ActionEvent e) {
noteSequence.addNote(new NoteSequence.Note((int)(Math.random()*5))); // add a note on a random line
}
}), BorderLayout.SOUTH);
f.setSize(320, 240);
f.setVisible(true);
});
}
@Override
protected void paintComponent(Graphics g2d) {
super.paintComponent(g2d);
Graphics2D g = (Graphics2D) g2d; // graphics2d has more functions
int w = getWidth();
int h = getHeight();
int lines = 5;
int spacing = h / lines;
paintSheetBackground(g, w, h, spacing);
drawNotes(g, spacing);
}
private void paintSheetBackground(Graphics2D g, int w, int h, int spacing) {
g.setColor(Color.white);
g.fillRect(0, 0, w, h);
g.setColor(Color.black);
for (int i = 0; i < 5; i++) {
final int y2 = i * spacing;
g.drawLine(0, y2, w, y2);
}
}
private void drawNotes(Graphics2D g, int heigtSpacing) {
int xSpacing = 30;
int x = 0;
for (NoteSequence.Note note : noteSequence.getNotes()) {
int y = (int) (note.getLine()*heigtSpacing);
g.fillOval(x, y-3, 7, 6); // -3 because note should sit on line
x+= xSpacing;
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (NoteSequence.PROP_NOTE_ADDED.equals(evt.getPropertyName()) || NoteSequence.PROP_NOTE_REMOVED.equals(evt.getPropertyName())) {
repaint();
}
}
}
接下来,class 模拟音乐 sheet 或音符序列。
package com.musicsheet;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The model class, that holds all notes.
*/
public class NoteSequence {
public static final String PROP_NOTE_ADDED = "noteAdded";
public static final String PROP_NOTE_REMOVED = "noteRemoved";
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private final List<Note> notes = new ArrayList<>();
public List<Note> getNotes() {
return Collections.unmodifiableList(notes);
}
/**
* is called by the UI or a button listener when a note should be added to this music sheet / notesequence
* @param note
*/
public void addNote(Note note) {
this.notes.add(note);
propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this, PROP_NOTE_ADDED, null, note));
}
public void removeNote(Note note) {
this.notes.remove(note);
propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this, PROP_NOTE_REMOVED, note, null));
}
void addNoteSequenceChangedListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
// not really needed atm
void removeNoteSequenceChangedListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
/**
* a single note.
*/
public static class Note {
private float line; // where does the note sit
private float timestampSinceBeginning; // not used, but you have to know WHEN a note is played
// ... more properties, e.g. name, or modifiers or whatever
public Note(float line) { // this is certainly to easy, since notes can sit in between lines,
// i did not try to think deeply into it, to define a better model
this.line = line;
}
public float getLine() {
return line;
}
public float getTimestampSinceBeginning() {
return timestampSinceBeginning;
}
}
}