Java: Add MouseListener to custom JComponent - java

If I create a new class which inherits from JComponent, whose paintComponent(Graphics g) method I override, by painting a circle using g, what should I modify in order for a MouseListener to trigger only when I click inside the bounds of the component?
Because in the constructor of my component I added setBounds(...) and then added a MouseListener, but it fires every time I click anywhere inside the container in which my custom component is and not only when I click inside it.
I do not want to check in the mouseClicked() method whether the event happened inside my component or not, I want it to be called only when the click was inside.
Here's my code:
public class Node extends JComponent {
private int x, y, radius;
public Node(int xx, int yy, int r) {
x = xx;
y = yy;
radius = r;
this.setBounds(new Rectangle(x - r, y - r, 2 * r, 2 * r));
setPreferredSize(new Dimension(2 * r, 2 * r));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gr = (Graphics2D) g;
gr.setColor(Color.BLACK);
gr.drawOval(x - radius, y - radius, 2 * radius, 2 * radius);
}
public static void main(String[] args) {
final JFrame f = new JFrame();
f.setSize(new Dimension(500, 500));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel p = new JPanel();
p.setLayout(new BorderLayout());
Node n = new Node(100, 100, 25);
n.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
System.out.println("clicked");
}
});
p.add(n);
f.add(p);
f.setVisible(true);
}
}

Your mouse listener is working correctly as it is only functioning within the bounds of your JComponent. To prove it to yourself, put a Border around your component to see what it actually covers:
public Node(int xx, int yy, int r) {
//. ....
setBorder(BorderFactory.createTitledBorder("Node"));
}
Understand that you're component is being added to a BorderLayout-using container in the default (BorderLayout.CENTER) position, and thus it fills the container. It does not matter that you set the bounds of your component (something that you should not be doing) or set its preferred size (also something that usually should be avoided).
For my money, I'd make my Node a logical class, one that implements the Shape interface, not a class that extends a JComponent, and then I could use the Shape's contains(Point p) method whenever I need to know if my Node has been clicked.

Related

How to paint components in layers in Java?

I have big problem with coding graphic part of my app, where I need to have components one on top of each other:
First I have JFrame (with fixed size)
In it I have two JPanel components. I want them to have colour background.
That's the easy part.
On one of the JPanel components I want to draw fixed shapres - rectangles, lanes, etc. Here I have problem, that I have two classes: one extends JPanel and is background for this part and second extends JComponent and represents element I draw (there is several elements). I don't know how to draw the elements in the JPanel - I tried several methods and nothing showed up. It's important to me that the JComponents should be drawn and conected only with this JPanel, not with whole frame.
On top of that I want to have moving shapes. It's easy when I have only frame and let's say rectangle, because I only change position and call repaint() method, but how to do this to make the moving shapes be connected to and be inside JPanel and to left previous layers in their place?
For my tries I created few classes with rectangles:
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class Main{
public Main() {
JFrame frame = new JFrame();
frame.setSize(1200, 900);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
JPanel background = new JPanel();
background.setBackground(Color.lightGray);
GreenRect gr = new GreenRect();
gr.setPreferredSize(new Dimension(500,800));
background.add(gr, BorderLayout.WEST);
RedRect rr = new RedRect();
rr.setPreferredSize(new Dimension(500,800));
background.add(rr, BorderLayout.EAST);
frame.add(background);
}
public static void main(String[] args) {
new Main();
}
}
class GreenRect extends JPanel {
ArrayList<BlackRect> r = new ArrayList<>();
ArrayList<MovingRec> m = new ArrayList<>();
public GreenRect() {
setBackground(Color.green);
addRec(10,10);
addRec(50,50);
addRec(100,100);
addRec(1000,1000);
}
public void addRec(int x, int y) {
r.add(new BlackRect(x,y));
}
}
class RedRect extends JPanel {
public RedRect() {
setBackground(Color.red);
}
}
class BlackRect extends JComponent {
int x, y;
int w = 100, h = 100;
public BlackRect (int x, int y){
this.x = x;
this.y = y;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponents(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, w, h);
}
}
class MovingRec extends JComponent {
int x, y;
int w = 20, h = 20;
public MovingRec (int x, int y){
this.x = x;
this.y = y;
}
public void paintComponent(Graphics g) {
super.paintComponents(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLUE);
g2d.fillRect(x, y, w, h);
}
public void update() {
x += 5;
y += 5;
}
}
and now I have problems with points 3 and 4, because I can't place black rectangles on background and moving rectangles on the top.
I will be grateful for all help :)
You do not need to (and shouldn’t) extend BlackRect and MovingRect from JComponent.
For example, BlackRect could be a simple object, like:
class BlackRect {
int x, y;
int w = 100, h = 100;
public BlackRect(int x, int y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, w, h);
}
}
You should override GreenRect’s paint method, to paint rectangles on that panel:
public GreenRect extends JPanel {
// Existing members
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (BlackRect black_rect : r) {
black_rect.paint(g2d);
}
// Also paint list of moving rectangles here
}
}
When GreenRect.repaint() is called, it will paint its background, and all rectangles from the r (and m list when you add that code). If the m rectangles have had their positions updated, they will be drawn at their new positions, so they will appear to be moving. Since moving rectangles are drawn last, they would appear “on top”.
Use a Swing Timer to drive the animation. When the timer expires, it should move all of the moving rectangles slightly (ie, call MovingRec.update()), and call repaint() on GreenRect.

deleting previously clicked rectangle in java for thread [duplicate]

As the title says, I'm having a hard time trying to draw some rectangles (filled) in JApplet.
The exact goal is to have a 50x50 table and when you click on a targeted cell, to make it filled (possibly done by drawing a filled rectangle). I have done the maths about the coordinates of the starting point, but for some reason I can't draw the new rectangle in the MouseClicked method. Any suggestions?
public class Main extends JApplet {
public static final int DIMX = 800;
public static final int DIMY = 800;
public static final int ratio = 16;
Graphics g;
boolean drawing;
public int cX;
public int cY;
public Main() {
JPanel MainFrame = new JPanel();
MainFrame.setPreferredSize(new Dimension(400, 800));
MainFrame.setBackground(Color.LIGHT_GRAY);
JPanel Table = new JPanel();
Table.setPreferredSize(new Dimension(800, 800));
Table.setBackground(Color.LIGHT_GRAY);
add(MainFrame, BorderLayout.EAST);
add(Table, BorderLayout.WEST);
addMouseListener(new clicked());
}
public void paint(Graphics g) {
super.paintComponents(g);
g.setColor(Color.black);
for (int i = 0; i <= 800; i += 16) {
g.drawLine(0, i, 800, i);
g.drawLine(i, 0, i, 800);
// g.fillRect(cX, cY, 16, 16);
}
}
public static void main(String[] args) {
JFrame win = new JFrame("Retarded Bullshit");
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setPreferredSize(new Dimension(1216, 840));
win.setContentPane(new Main());
win.pack();
win.setVisible(true);
}
public class clicked extends JApplet implements MouseListener {
public int cX;
public int cY;
Graphics g;
#Override
public void mouseClicked(MouseEvent e) {
// Point a = e.getLocationOnScreen();
int cellX = e.getX();
int cellY = e.getY();
if (cellX < 800 && cellX > 0 && cellY < 800 && cellY > 0) {
cX = cellX / 16 + 1;
cY = cellY / 16 + 1;
JOptionPane.showMessageDialog(null, "" + cX + " " + cY);
}
This is a relatively simple concept (no offense).
To start with, don't mix your code with JApplet and JFrame. If you want to use your application in these two mediums, separate the logic into a separate component (like JPanel) which you can easily add to either. You really shouldn't add a top level container to another top level container (adding an applet to a frame) - it's messy.
Avoid overriding the paint methods of top level containers (like JApplet), instead, use a custom component (like JPanel) instead and override it's paintComponent method.
In your example, you should be calling super.paint rather then super.paintComponents. paint does important work, you don't want to skip it - but you should be using JComponent#paintComponent
MouseListeners should added to the components that you are interested in managing mouse events. Because clicked is never added to any containers, it will never recieve mouse events.
Take a look at
How to write mouse listeners
Performing Custom Painting
2D Graphics
Painting in AWT and Swing (because every Swing developer should have an understanding of this)
public class SimplePaint03 {
public static void main(String[] args) {
new SimplePaint03();
}
public SimplePaint03() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new PaintPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PaintPane extends JPanel {
private List<Shape> grid;
private List<Shape> fill;
public PaintPane() {
grid = new ArrayList<>(5);
fill = new ArrayList<>(5);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
for (Shape shape : grid) {
if (shape.contains(e.getPoint())) {
if (fill.contains(shape)) {
fill.remove(shape);
} else {
fill.add(shape);
}
}
}
repaint();
}
});
int colWidth = 200 / 50;
int rowHeight = 200 / 50;
for (int row = 0; row < 50; row++) {
for (int col = 0; col < 50; col++) {
grid.add(new Rectangle(colWidth * col, rowHeight * row, colWidth, rowHeight));
}
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
for (Shape cell : fill) {
g2d.fill(cell);
}
g2d.setColor(Color.BLACK);
for (Shape cell : grid) {
g2d.draw(cell);
}
}
}
}
Additional
Information from one paint cycle to another is not maintained. You are required to repaint the component exactly the way you want it to appear. This means you will need to maintain a list of click points that can be repainted at any time.
Start by reading the Swing tutorial on Custom Painting.
Custom painting is done by overriding the paintComponent() method of a JPanel or JComponent(). Then you add the panel to the JApplet.
If you only want to paint certain squares then you are going to need a List to keep track of which cells to paint. Then every time you repaint the component you will need to loop through the List and paint the cells.
Your MouseListener would not extend JApplet. When you click on a cell you would update the List from above to indicate that the cell needs to be painted. Then you would invoke repaint() on the panel so that your painting code will be invoked.
You may also want to look at Custom Painting Approaches which gives two different ways to do this type of painting depending on your exact requirement.

JFrame not displaying anything

I cannot seem to get a rectangle to be displayed in the JFrame. Per the parameters of this project, I have to have this class implement the Icon interface. When I run the code as is, I get the JFrame, but nothing shows up inside. It should display a black square. I'm assuming the problem has something to do with how I am initializing the graphics instance variable. I don't have much experience working with GUI graphics, so I'm not entirely clear on how to do this correctly.
And yes, I do know that the getIconWidth and getIconHeight methods are redundant since I am using constants, but I have to have these methods in order to implement the interface.
public class MugDisplay extends JFrame implements Icon {
private int width;
private int height;
private JPanel panel;
private Graphics graphics;
private static final int ICON_WIDTH = 100;
private static final int ICON_HEIGHT = 100;
public MugDisplay() {
this.configureGui();
this.panel = new JPanel();
this.panel.setLayout(new BorderLayout());
this.add(this.panel, BorderLayout.CENTER);
this.graphics = this.getGraphics();
int xPos = (this.panel.getWidth() - this.getIconWidth()) / 2;
int yPos = (this.panel.getHeight() - this.getIconHeight()) / 2;
this.paintIcon(this.panel, this.graphics, xPos, yPos);
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.fillRect(x, y, ICON_WIDTH, ICON_HEIGHT);
}
#Override
public int getIconWidth() {
return ICON_WIDTH;
}
#Override
public int getIconHeight() {
return ICON_HEIGHT;
}
private void configureGui() {
this.setPreferredSize(new Dimension(600, 600));
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().setLayout(new BorderLayout());
this.pack();
this.setVisible(true);
}
}
In the interest of having a MCVE, here is the driver class that calls this class.
public class Main {
public static void main(String[] args) {
MugDisplay md = new MugDisplay();
md.setVisible(true);
}
}
this.graphics = this.getGraphics() is not how custom painting works in Swing. What you should do is create a panel, override its paintComponent method, make the call to super, and then do your painting. For instance:
panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int xPos = ...
paintIcon(this, g, xPos, yPos);
}
}
Calling getGraphics will provide you with a short-lived Graphics object that can soon become invalid, which is why you should opt for overriding paintComponent instead, which will always give you a usable Graphics object. See Performing Custom Painting.
On a separate note, it looks like you're calling setVisible(true) before you're finished adding the necessary components to the JFrame. To ensure that your components show up, call setVisible(true) after adding them all to the frame.

Why does drawImage fail when I am using it inside a Jpanel

I am doing the initial section of a simple platform game in Java. I have created a class called entity which extends JPanel and successfully added it to the window.
import javax.swing.*;
import java.awt.*;
/**
* Created by bw12954 on 27/05/16.
*/
public abstract class Entity extends JPanel {
private final SpriteSheet sprites;
private Point location;
private Dimension dimensions;
public Entity(int x, int y, int w, int h, SpriteSheet sprites)
{
location = new Point(x, y);
dimensions = new Dimension(w, h);
this.sprites = sprites;
}
public Entity(int x, int y, int w, int h)
{
this(x, y, w, h, null);
}
#Override
public Dimension getPreferredSize()
{
return dimensions;
}
public void setLocation(int x, int y)
{
location.setLocation(x, y);
}
/* Some code removed here for brevity */
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(sprites.get(),
(int)location.getX(),
(int)location.getY(),
null);
}
}
If I add this directly to the JFrame as below, then the graphic shows up on the window as I would expect it to (note that Player is a very simple subclass of Entity)
public class Window {
private JFrame window;
public Window()
{
SwingUtilities.invokeLater(this::run);
}
private void run()
{
try {
window = new JFrame();
window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
window.setLocationByPlatform(true);
window.setUndecorated(true);
Player p = new Player(0,0);
window.add(p);
window.setExtendedState(JFrame.MAXIMIZED_BOTH);
window.setVisible(true);
} catch (IOException e) {
// TODO handle exception
e.printStackTrace();
}
}
}
However - when I create a class called World which also extends JPanel, add that to the window instead, then use the add() method in its constructor to add a new Player to it, it doesn't appear. Interestingly, if I add setBackground() to the constructor for Player/Entity, I can see a coloured square where the entity SHOULD be. Its just that drawImage doesn't appear to work.
If anyone else has any idea what is going on here, it will be greatfully appreciated!
If I add this directly to the JFrame as below, then the graphic shows up on the window as I would expect it to
The default layout manager of the content pane of the frame is a BorderLayout. Any component added to the frame without a constraint will be added to the "CENTER" which means the component is automatically resized to fill the entire space.
However - when I create a class called World which also extends JPanel, add that to the window instead, then use the add() method in its constructor to add a new Player to it, it doesn't appear.
The default layout manager of a JPanel is a FlowLayout, which respects the preferred size of any component added to it and will reset the location of the component based on the rules of the layout manager.
Interestingly, if I add setBackground() to the constructor for Player/Entity, I can see a coloured square where the entity SHOULD be. Its just that drawImage doesn't appear to work
Probably because your preferred size calculation is incorrect and the image is truncated.
location = new Point(x, y);
dimensions = new Dimension(w, h);
The above code will only work if the Point is (0, 0). The more general case code should be:
location = new Point(x, y);
dimensions = new Dimension(x + w, y + h);
because you need to consider where you actually paint the image realative to the component.
So when you do this you should see the image, however you will not see the image in the proper location because the layout manager will override the location.
So it you want to continue with this approach of using components you will need to use a null layout, which means you will manually need to use the setSize(...) and setLocation(...) of each component. In this case the size of the component will be the (w, h) and you will draw the image using:
g.drawImage(sprites.get(), 0, 0, this);
Note however if you use the component approach there is no need to even create a custom component. You could just use a JLabel with an ImageIcon. You would assign the Icon when you create the label.

Java - Learning inheritance, using Graphics2D, object is redrawn when resizing the frame

I am working on a lab to practice inheritance, in which we are to create a horizontal ellipse as "Shape1" and then create a "Shape2" which extends Shape1 which draws it's superclass Shape1, and then draws a vertical ellipse over top to create a new looking shape. The shape is displaying fine in terms of inheritance and looks (color/location etc) however when running the program, the frame width is set to 1000, and the height is set to 700, but If I drag the frame by the corner to enlarge it, the shape is drawn over and over again as I keep dragging the frame larger. Ideally the shape should just stay where it is relative to the frame size. I think this is happening because while I drag the frame larger, the draw method is being called over and over again by the system, but I am not sure where this is happening or how to fix it. Any suggestions?
All classes are displayed below:
Shape1:
public class Shape1 {
private double x, y, r;
protected Color col;
private Random randGen = new Random();
public Shape1(double x, double y, double r) {
this.x = x;
this.y = y;
this.r = r;
this.col = new Color(randGen.nextFloat(), randGen.nextFloat(), randGen.nextFloat());
}
public double getX() {
return this.x;
}
public double getY() {
return this.y;
}
public double getR() {
return this.r;
}
public void draw(Graphics2D g2){
//Create a horizontal ellipse
Ellipse2D horizontalEllipse = new Ellipse2D.Double(x - 2*r, y - r, 4 * r, 2 * r);
g2.setPaint(col);
g2.fill(horizontalEllipse);
}
}
Shape2:
public class Shape2 extends Shape1 {
public Shape2(double x, double y, double r) {
super(x, y, r);
}
public void draw(Graphics2D g2) {
//Create a horizontal ellipse
Ellipse2D verticalEllipse = new Ellipse2D.Double(super.getX() - super.getR(),
super.getY() - 2*super.getR(),
2 * super.getR(), 4 * super.getR());
super.draw(g2);
g2.fill(verticalEllipse);
}
}
ShapeComponent:
public class ShapeComponent extends JComponent {
//Instance variables here
private Random coordGen = new Random();
private final int FRAME_WIDTH = 1000;
private final int FRAME_HEIGHT = 700;
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Shape2 myShape = new Shape2(1 + coordGen.nextInt(FRAME_WIDTH), 1 + coordGen.nextInt(FRAME_HEIGHT), 20);
//Draw shape here
myShape.draw(g2);
}
}
ShapeViewer(Where the JFrame is created):
public class ShapeViewer {
public static void main(String[] args) {
final int FRAME_WIDTH = 1000;
final int FRAME_HEIGHT = 700;
//A new frame
JFrame frame = new JFrame();
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setTitle("Lab 5");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ShapeComponent component = new ShapeComponent();
frame.add(component);
//We can see it!
frame.setVisible(true);
}
}
because while I drag the frame larger, the draw method is being called over and over again by the system,
Correct, all components are repainted when the frame is resized.
Any suggestions?
Painting code should be based on properties of your class. If you want the painting to be a fixed size then you define the properties that control the painting and set these properties outside the painting method.
For example, you would never invoke Random.nextInt(...) in the painting method. This means the value will change every time the component is repainted.
So the Shape should be created in the constructor of your class and its size would be defined there, not each time you paint it.

Categories

Resources