英文:
Java Swing GUI - repaint graphics from a different class on mouse click
问题
以下是您要翻译的内容:
"假设我们有两个类 - Main 和 EventAdapter。问题是如何实现一个程序,当鼠标光标点击时,在不同类中的 mouseClicked 方法中绘制一个 Area 对象的位置。
public class Main extends JComponent{
Main() {
repaint();
}
public void paint(Graphics g) {
Graphics2D gr = (Graphics2D) g;
// 使用 mouseClicked 方法中获得的 Area 对象填充图形对象
gr.setPaint(Color.red);
if (area != null)
gr.fill(area);
}
private static void createAndShowGUI() {
JFrame jFrame = new JFrame("test");
jFrame.setSize(300, 350);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Main main = new Main();
main.addMouseListener(new EventAdapter());
label = new JLabel("");
label.setHorizontalAlignment(JLabel.CENTER);
jFrame.add(label, BorderLayout.NORTH);
jFrame.add(area, BorderLayout.CENTER);
jFrame.setVisible(true);
}
public static void main(String[] args) {
createAndShowGUI();
}
}
class EventAdapter extends MouseAdapter {
private int x, y;
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
x = e.getX();
y = e.getY();
Shape shape = new Ellipse2D.Float(x, y, 60, 60);
Area area = new Area(shape);
Main main2 = new Main();
main2.repaint();
}
}
```"
请注意,由于代码中可能存在其他变量和定义,我只提供了您要翻译的代码部分的翻译。
<details>
<summary>英文:</summary>
Let's say we have two classes - Main and EventAdapter. The question is how to implement a program that paints an Area object at the place where mouse cursor is clicked, if mouseClicked method is located in a different class.
public class Main extends JComponent{
Main() {
repaint();
}
public void paint(Graphics g) {
Graphics2D gr = (Graphics2D) g;
// fill graphics object with an Area object obtained in mouseClicked method
gr.setPaint(Color.red);
if (area != null)
gr.fill(area);
}
private static void createAndShowGUI() {
JFrame jFrame = new JFrame("test");
jFrame.setSize(300, 350);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Main main = new Main();
main.addMouseListener(new EventAdapter());
label = new JLabel("");
label.setHorizontalAlignment(JLabel.CENTER);
jFrame.add(label, BorderLayout.NORTH);
jFrame.add(area, BorderLayout.CENTER);
jFrame.setVisible(true);
}
public static void main(String[] args) {
createAndShowGUI();
}
}
class EventAdapter extends MouseAdapter {
private int x, y;
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
x = e.getX();
y = e.getY();
Shape shape = new Ellipse2D.Float(x, y, 60, 60);
Area area = new Area(shape);
Main main2 = new Main();
main2.repaint();
}
}
I am familiar with OOP, yet the concept of graphical interfaces seems a bit different to me. I have read Oracle documentations, but haven't found any information based on calling repaint method from a different class, so I hope anyone here will help me figure this out.
</details>
# 答案1
**得分**: 2
Creating a *new* Main instance is not going to solve your problem. The problem is that you are changing the state of the wrong instance, and instead you will need to change the state of the currently visible Main instance, not create a new one:
```java
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
x = e.getX();
y = e.getY();
Shape shape = new Ellipse2D.Float(x, y, 60, 60);
Area area = new Area(shape);
Main main2 = new Main(); // not going to work
main2.repaint();
}
}
So instead, if this is a very simple program, then perhaps pass an instance of Main into EventAdapter and change its state via a setter method. If the program needs to scale up though, if it is a small part of a much larger and more complex program (or will be in the future), then this solution would be brittle, and instead you will want to separate model from view, a la Model-View-Controller, to allow for separation of concerns, easier testing of separate classes, and easier debugging.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class PaintPanel extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Color E_FILL_COLOR = Color.PINK;
private static final int E_RADIUS = 40;
private static final Stroke E_STROKE = new BasicStroke(4f);
private static final Color E_BORDER_COLOR = Color.RED.darker();
private List<Ellipse2D> ellipses = new ArrayList<>();
public PaintPanel() {
}
@Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
int width = Math.max(superSize.width, PREF_W);
int height = Math.max(superSize.height, PREF_H);
return new Dimension(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(E_STROKE);
for (Ellipse2D ellipse2d : ellipses) {
g2.setColor(E_FILL_COLOR);
g2.fill(ellipse2d);
g2.setColor(E_BORDER_COLOR);
g2.draw(ellipse2d);
}
g2.dispose();
}
public void addEllipse(int x, int y) {
Ellipse2D ellipse = new Ellipse2D.Double(x - E_RADIUS, y - E_RADIUS, 2 * E_RADIUS, 2 * E_RADIUS);
ellipses.add(ellipse);
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
PaintPanel mainPanel = new PaintPanel();
mainPanel.addMouseListener(new EventAdapter(mainPanel));
JFrame frame = new JFrame("PaintPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
}
class EventAdapter extends MouseAdapter {
private PaintPanel paintPanel;
public EventAdapter(PaintPanel paintPanel) {
this.paintPanel = paintPanel; // pass in the painting component
}
@Override
public void mousePressed(MouseEvent e) {
// change the state of the painting component
paintPanel.addEllipse(e.getX(), e.getY());
}
}
Also note that:
- The drawing is done within the paintComponent method of a JPanel.
- The super painting method, here super.paintComponent is called within the override so that "housekeeping" painting can be done.
- Avoid setting anything's size if possible and instead let components size themselves with hints given by the programmer.
- This code allows for the creating and drawing of multiple circles by holding and filling an ArrayList of Ellipse2D objects and then iterating through the list within the paintComponent method.
As per MadProgrammer's suggestions, here is a more M-V-C version:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class PaintPanelMain {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
ConcretePpView view = new ConcretePpView();
PpModel model = new PpModel();
PpController controller = new PpController(view, model);
controller.setVisible(true);
});
}
}
class ConcretePpView implements PpView {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Stroke E_STROKE = new BasicStroke(4f);
private JPanel paintPanel = new PaintPanel();
private PpModel model;
public ConcretePpView() {
}
@Override
public void setModel(PpModel model) {
this.model = model;
}
public PpModel getModel() {
return model;
}
@Override
public void addEventAdapter(EventAdapter eventAdapter) {
paintPanel.addMouseListener(eventAdapter);
}
@Override
public void repaint() {
paintPanel.repaint();
}
@Override
public JComponent getViewComponent() {
return paintPanel;
}
private class PaintPanel extends JPanel {
@Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
int width = Math.max(superSize.width, PREF_W);
int height = Math.max(superSize.height, PREF_H);
return new Dimension(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (model != null) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(E_STROKE);
for (ColorEllipse ellipse : model.getEllipses()) {
g2.setColor(ellipse.getFillColor());
int r = ellipse.getRadius();
int x = ellipse.getX() - r;
int y = ellipse.getY() - r;
g2.fillOval(x, y, 2 * r, 2 * r);
g2.setStroke(E_STROKE);
g2.setColor(ellipse.getBorderColor());
g2.drawOval(x, y, 2 * r, 2 * r);
}
g2
<details>
<summary>英文:</summary>
Creating a *new* Main instance is not going to solve your problem. The problem is that you are changing the state of the wrong instance, and instead you will need to change the state of the currently visible Main instance, not create a new one:
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
x = e.getX();
y = e.getY();
Shape shape = new Ellipse2D.Float(x, y, 60, 60);
Area area = new Area(shape);
Main main2 = new Main(); // not going to work
main2.repaint();
}
}
So instead, if this is a very simple program, then perhaps pass an instance of Main into EventAdapter and change its state via a setter method. If the program needs to scale up though, if it is a small part of a much larger and more complex program (or will be in the future), then this solution would be brittle, and instead you will want to separate model from view, a la Model-View-Controller, to allow for separation of concerns, easier testing of separate classes, and easier debugging.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class PaintPanel extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Color E_FILL_COLOR = Color.PINK;
private static final int E_RADIUS = 40;
private static final Stroke E_STROKE = new BasicStroke(4f);
private static final Color E_BORDER_COLOR = Color.RED.darker();
private List<Ellipse2D> ellipses = new ArrayList<>();
public PaintPanel() {
}
@Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
int width = Math.max(superSize.width, PREF_W);
int height = Math.max(superSize.height, PREF_H);
return new Dimension(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(E_STROKE);
for (Ellipse2D ellipse2d : ellipses) {
g2.setColor(E_FILL_COLOR);
g2.fill(ellipse2d);
g2.setColor(E_BORDER_COLOR);
g2.draw(ellipse2d);
}
g2.dispose();
}
public void addEllipse(int x, int y) {
Ellipse2D ellipse = new Ellipse2D.Double(x - E_RADIUS, y - E_RADIUS, 2* E_RADIUS, 2* E_RADIUS);
ellipses.add(ellipse);
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
PaintPanel mainPanel = new PaintPanel();
mainPanel.addMouseListener(new EventAdapter(mainPanel));
JFrame frame = new JFrame("PaintPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
}
class EventAdapter extends MouseAdapter {
private PaintPanel paintPanel;
public EventAdapter(PaintPanel paintPanel) {
this.paintPanel = paintPanel; // pass in the painting component
}
@Override
public void mousePressed(MouseEvent e) {
// change the state of the painting component
paintPanel.addEllipse(e.getX(), e.getY());
}
}
Also note that
* The drawing is done within the paintComponent method of a JPanel
* The super painting method, here super.paintComponent is called within the override so that "housekeeping" painting can be done
* Avoid setting anythings size if possible and instead let components size themselves with hints given by the programmer.
* This code allows for the creating and drawing of multiple circles by holding and filling an ArrayList of Ellipse2D objects and then iterating through the list within the paintComponent method.
_______________________________________________________
As per MadProgrammer's suggestions, here is a more M-V-C version:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class PaintPanelMain {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
ConcretePpView view = new ConcretePpView();
PpModel model = new PpModel();
PpController controller = new PpController(view, model);
controller.setVisible(true);
});
}
}
class ConcretePpView implements PpView {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Stroke E_STROKE = new BasicStroke(4f);
private JPanel paintPanel = new PaintPanel();
private PpModel model;
public ConcretePpView() {
}
@Override
public void setModel(PpModel model) {
this.model = model;
}
public PpModel getModel() {
return model;
}
@Override
public void addEventAdapter(EventAdapter eventAdapter) {
paintPanel.addMouseListener(eventAdapter);
}
@Override
public void repaint() {
paintPanel.repaint();
}
@Override
public JComponent getViewComponent() {
return paintPanel;
}
private class PaintPanel extends JPanel {
@Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
int width = Math.max(superSize.width, PREF_W);
int height = Math.max(superSize.height, PREF_H);
return new Dimension(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (model != null) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(E_STROKE);
for (ColorEllipse ellipse : model.getEllipses()) {
g2.setColor(ellipse.getFillColor());
int r = ellipse.getRadius();
int x = ellipse.getX() - r;
int y = ellipse.getY() - r;
g2.fillOval(x, y, 2 * r, 2 * r);
g2.setStroke(E_STROKE);
g2.setColor(ellipse.getBorderColor());
g2.drawOval(x, y, 2 * r, 2 * r);
}
g2.dispose();
}
}
}
}
class ColorEllipse {
private Color fillColor;
private Color borderColor;
private int radius;
private int x;
private int y;
public ColorEllipse(Color fillColor, Color borderColor, int radius, int x, int y) {
this.fillColor = fillColor;
this.borderColor = borderColor;
this.radius = radius;
this.x = x;
this.y = y;
}
public Color getFillColor() {
return fillColor;
}
public Color getBorderColor() {
return borderColor;
}
public int getRadius() {
return radius;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
class PpModel {
public static final String ELLIPSE = "ellipse";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private List<ColorEllipse> ellipses = new ArrayList<>();
public void addEllipse(ColorEllipse ellipse) {
ellipses.add(ellipse);
pcSupport.firePropertyChange(ELLIPSE, null, ellipse);
}
public List<ColorEllipse> getEllipses() {
return ellipses;
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(propertyName, listener);
}
}
class PpController {
private PpView view;
private PpModel model;
public PpController(PpView view, PpModel model) {
this.view = view;
this.model = model;
view.setModel(model);
view.addEventAdapter(new EventAdapter(this));
model.addPropertyChangeListener(PpModel.ELLIPSE, e -> view.repaint());
}
public void setVisible(boolean b) {
JFrame frame = new JFrame("PaintPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getViewComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void addEllipse(int x, int y) {
float h = (float) Math.random();
Color fillColor = Color.getHSBColor(h, 1f, 1f);
Color borderColor = fillColor.darker().darker();
int radius = 20 + (int) (Math.random() * 40);
model.addEllipse(new ColorEllipse(fillColor, borderColor, radius, x, y));
}
}
interface PpView {
void setModel(PpModel model);
void repaint();
void addEventAdapter(EventAdapter eventAdapter);
JComponent getViewComponent();
}
class EventAdapter extends MouseAdapter {
private PpController controller;
public EventAdapter(PpController ppController) {
this.controller = ppController;
}
@Override
public void mousePressed(MouseEvent e) {
controller.addEllipse(e.getX(), e.getY());
}
}
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论