Java Swing GUI – 在鼠标点击时从不同类中重新绘制图形

huangapple go评论108阅读模式
英文:

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&#39;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(&quot;test&quot;);
    jFrame.setSize(300, 350);
    jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Main main = new Main();
    main.addMouseListener(new EventAdapter());

    label = new JLabel(&quot;&quot;);
    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&#39;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(() -&gt; {
PaintPanel mainPanel = new PaintPanel();
mainPanel.addMouseListener(new EventAdapter(mainPanel));
JFrame frame = new JFrame(&quot;PaintPanel&quot;);
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 &quot;housekeeping&quot; 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&#39;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(() -&gt; {
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&lt;ColorEllipse&gt; 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 -&gt; view.repaint());
}
public void setVisible(boolean b) {
JFrame frame = new JFrame(&quot;PaintPanel&quot;);
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>

huangapple
  • 本文由 发表于 2023年5月6日 21:07:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76189063.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定