Image mapping like feature in Java swing, Points vs GeneralPath - java

My gaol is to draw several images using 2D graphics in a paintComponent() method. However, I'm not sure how I could add a MouseListener such that each image would know if it was selected.
My solution thus far is too simply record the coordinates of the mouse click, and see if they are contained within the boundaries of each of the images. However this will be difficult with images that have more complex boundaries.
Another option would be too create simple shapes and place them over the images, but again images with more complex boundaries will be difficult. In another discussion on SO found
here, someone mentioned using GeneralPath to draw more complex shapes. I have never played with is, but this seems encouraging.
Of these 2 options, what seems to be the best solution, or are there other recommendations

Are you images painted on top of one another or are they drawn separately.
If they are drawn separately, then you should do the custom painting on a JComponent. Then you can do the drawing by using the GeneralPath. You will also need to implement the contains(...) method by checking if the mouseclick is contained in the GeneralPath. If the contains() method is implemented properly then the MouseListener will respond properly.
Here is a simpler example:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class RoundButton extends JButton {
public RoundButton(String label) {
super(label);
// These statements enlarge the button so that it
// becomes a circle rather than an oval.
Dimension size = getPreferredSize();
size.width = size.height = Math.max(size.width, size.height);
setPreferredSize(size);
// This call causes the JButton not to paint the background.
// This allows us to paint a round background.
setContentAreaFilled(false);
}
// Paint the round background and label.
protected void paintComponent(Graphics g) {
if (getModel().isArmed()) {
// You might want to make the highlight color
// a property of the RoundButton class.
g.setColor(Color.lightGray);
} else {
g.setColor(getBackground());
}
g.fillOval(0, 0, getSize().width-1, getSize().height-1);
// This call will paint the label and the focus rectangle.
super.paintComponent(g);
}
// Paint the border of the button using a simple stroke.
protected void paintBorder(Graphics g) {
g.setColor(getForeground());
g.drawOval(0, 0, getSize().width-1, getSize().height-1);
}
// Hit detection.
Shape shape;
public boolean contains(int x, int y) {
// If the button has changed size, make a new shape object.
if (shape == null || !shape.getBounds().equals(getBounds())) {
shape = new Ellipse2D.Float(0, 0, getWidth(), getHeight());
}
return shape.contains(x, y);
}
// Test routine.
public static void main(String[] args) {
// Create a button with the label "Jackpot".
JButton button = new RoundButton("Jackpot");
button.setBackground(Color.green);
button.setBounds(0, 0, 100, 100);
JButton button2 = new RoundButton("Jackpot2");
button2.setBackground(Color.red);
button2.setBounds(50, 50, 100, 100);
// Create a frame in which to show the button.
JFrame frame = new JFrame();
frame.getContentPane().setBackground(Color.yellow);
frame.getContentPane().setLayout(null);
frame.getContentPane().add(button);
frame.getContentPane().add(button2);
// frame.getContentPane().setLayout(new FlowLayout());
frame.setSize(200, 200);
frame.setVisible(true);
MouseListener mouseListener = new MouseAdapter() {
public void mouseEntered( MouseEvent e )
{}
public void mouseExited( MouseEvent e )
{}
public void mouseClicked( MouseEvent e )
{
System.out.println( "clicked " );
}
public void mousePressed( MouseEvent e )
{
System.out.println( "pressed " );
}
public void mouseReleased( MouseEvent e )
{
System.out.println( "released " );
}
};
button.addMouseListener( mouseListener );
}
}

Related

JPanel shows only the first custom JComponent added

I wrote a program that draws triangles on the screen. However only the first triangle is shown. How can I make multiple custom JComponents visible?
I already tried to create something like a draw() method but then I can't perform any actions on this object like i. e. I would like the color of the triangle to change whenever I click on it. For this I would need a MouseListener but it won't work with the draw() method.
View.java file:
package test;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class View extends JPanel {
public View()
{
setPreferredSize(new Dimension(300, 300));
add(new Triangle(20, 50, Color.red)); //this one will react to mouseClicked
add(new Triangle(100, 200, Color.pink)); //this one doesn't appear
}
public static void main(String []args)
{
JFrame frame = new JFrame("Trianlge test");
frame.add(new View());
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Triangle p3 = new Triangle(60, 120, Color.blue); //this one won't react to mouseClicked()
p3.draw(g);
}
}
Triangle.java file:
package test;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.GeneralPath;
import javax.swing.JComponent;
public class Triangle extends JComponent implements MouseListener{
private int x,y;
private Color c;
public Triangle(int x, int y, Color c)
{
this.x = x;
this.y = y;
this.c = c;
setPreferredSize(new Dimension(100, 100));
addMouseListener(this);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
GeneralPath path = new GeneralPath();
g2d.setColor(c);
path.moveTo(x, y);
path.lineTo(x, y);
path.lineTo(x+50, y);
path.lineTo(x, y-50);
path.closePath();
g2d.fill(path);
repaint();
}
public void draw(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
GeneralPath path = new GeneralPath();
g2d.setColor(c);
path.moveTo(x, y);
path.lineTo(x, y);
path.lineTo(x+50, y);
path.lineTo(x, y-50);
path.closePath();
g2d.fill(path);
repaint();
}
#Override
public void mouseClicked(MouseEvent e) {
c = Color.cyan;
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
JFrame frame = new JFrame();
First of all that statement in your View class is completely unnecessary. You would not create a JFrame instance in the constructor of a component. Also your code never references the variable which is a good indication it is not needed.
However, the main problem is your concept of creating custom components is wrong:
setPreferredSize(new Dimension(100, 100));
You attempt to set the preferred size of the component.
add(new Triangle(100, 200, Color.pink)); //this one doesn't appear
But then you attempt to do you custom painting at (100, 200) which is outside the size of the component. So the painting logic clipped at the size of component so you don't see anything being painted.
Custom painting should be done relative to (0, 0) of the component, not relative to the parent component.
If you you want to randomly position components on the parent panel then you need to:
set the parent panel to use a null layout
set the location of each component you add to the panel
set the size of each component you add to the panel.
basically you need to take over the functionality of the layout manager.
Other problems with your current painting code:
Don't invoke repaint() in a painting method. This will essentially cause an infinite painting loop. If you need animation you use a Swing Timer to schedule the animation.
Don't invoke paintComponent(...) directly. Swing will invoke paintComponent() when a component needs to be repainted.
However, I would suggest that if you want to paint Shapes on a panel, Then you forget about creating custom components. Instead you keep an ArrayList of the Shapes you want to paint and then in the paintComponent() method of the panel you iterate through the ArrayList to paint each shape.
For an example of this approach take a look at the Draw On Component example found in Custom Painting Approaches.
Note:
If you really want to be able to handle mouse events then you need to use a Shape object to represent your shapes to do proper hit detection. If you just display your shape as a component, then the mouse hit will be detected if you click anywhere in the rectangular area of the component, not just the triangle part that you actually paint. The Shape class has a contains(...) method you can use to determine if you actually click in the Shape or not.
Check out Playing With Shapes for more information on this concept.
Set a border to Triangle components like this:
public Triangle(int x, int y, Color c)
{
this.x = x;
this.y = y;
this.c = c;
setPreferredSize(new Dimension(100, 100));
addMouseListener(this);
// Set this border to see the boundaries of this component.
// When you are done, you may remove this.
setBorder(BorderFactory.createLineBorder(Color.black));
}
Then you can better understand the bounds of the components.
Pink triangle is not visible because it is outside component's boundary.
p3 triangle does not react to mouse clicks because it is just a drawing. Only components react to mouse and other events.
Notice that components are rectangle in shape. So, the mouse listener you have added works anywhere on the component; not only on the area of triangle.
You are drawing triangles in two ways in this program.
1. By adding Triangle components. (Like "add(new Triangle(20, 50, Color.red));")
2. By drawing p3 in paintComponent() method.
From software designing perspective, better to stick to one approach. Otherwise it can be confusing and error prone.

what is Alternative of Paint and repaint function?

Is there any function which can be replaced with paint() and repaint()in java.
I have a scenario.
There is a triangle (Triangle 1). when user will click in triangle another triangle (Triangle 2) will appear and 1st (Triangle 1) will be removed from screen. (coded using JFrame, paint() and repaint())
I have achieved it so far. but problem is when I minimize or change size of window with mouse the output window, it just paint again Triangle 1 rather than Triangle 2. OR Clear the whole screen if i call g2d.clearRect(0, 0, 1000, 1000);
triangle.reset();
Note: These both functions are to remove previous triangle (Triangle 1).
Is there any function that state should not be changed on minimize or when window size changed ?
or can we override repaint() or anything helping according to scenario.
Here is the Working code. Execute it, Click in triangle then minimize and see again. You will get the problem more clearly.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Triangle_shape extends JFrame implements ActionListener {
public static JButton btnSubmit = new JButton("Submit");
public Triangle_shape() {
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setLayout(new BorderLayout());
frame.add(new TrianglePanel(), BorderLayout.CENTER);
frame.add(btnSubmit, BorderLayout.PAGE_END);
frame.pack();
frame.repaint();
frame.setTitle("A Test Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
throw new UnsupportedOperationException("Not supported yet.");
}
public static class TrianglePanel extends JPanel implements MouseListener {
private Polygon triangle, triangle2;
public TrianglePanel() {
//Create triangle
triangle = new Polygon();
triangle.addPoint(400, 550); //left
triangle.addPoint(600, 550); //right
triangle.addPoint(500, 350); //top
//Add mouse Listener
addMouseListener(this);
//Set size to make sure that the whole triangle is shown
setPreferredSize(new Dimension(300, 300));
}
/**
* Draws the triangle as this frame's painting
*/
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.draw(triangle);
}
//Required methods for MouseListener, though the only one you care about is click
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
/**
* Called whenever the mouse clicks. Could be replaced with setting the
* value of a JLabel, etc.
*/
public void mouseClicked(MouseEvent e) {
Graphics2D g2d = (Graphics2D) this.getGraphics();
Point p = e.getPoint();
if (triangle.contains(p)) {
System.out.println("1");
g2d.clearRect(0, 0, 1000, 1000);
triangle.reset();
g2d.setColor(Color.MAGENTA);
triangle2 = new Polygon();
triangle2.addPoint(600, 550); // left
triangle2.addPoint(700, 350); //top
triangle2.addPoint(800, 550); //right
g2d.draw(triangle2);
} else {
System.out.println("Triangle dont have point");
}
}
}
}
paint() and repaint() work fine, but you are not using them as they are designed to be used. The window system doesn't keep a persistent image of what a component looks like. It expects your component to be able to repaint its entire appearance on demand (such as when the window is resized or un-minimized).
If you grab a Graphics object with getGraphics() and draw something on the component, the stuff you draw will indeed be lost if/when the entire component needs to be repainted. So you should not do that, but instead make sure your paintComponent method has all the information it needs to completely repaint the component.
If you only want one triangle on the screen at once, do not create a separate variable triangle2. Just replace the one triangle you have by altering your mouse click handler as follows:
public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
if (triangle.contains(p)) {
triangle = new Polygon();
triangle.addPoint(600, 550); // left
triangle.addPoint(700, 350); //top
triangle.addPoint(800, 550); //right
repaint();
} else {
System.out.println("Point not in triangle");
}
}
Your paintComponent method should call super.paintComponent to ensure the background is painted, but otherwise you do not need to change it:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.draw(triangle);
}
Alternatively if you are trying to keep multiple triangles on the screen, for example, to add a new one at every click, you should add them into a list of shapes which will be drawn whenever the component needs to be repainted:
private final List<Shape> shapes = new ArrayList<>();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
for (Shape s : shapes)
g2d.draw(s);
}
Then to control the set of shapes that is on screen, manipulate the contents of the list, and call repaint();.
For instance, to add a new shape to the ones on screen:
Polygon triangle = new Polygon();
triangle.addPoint(200, 300);
triangle.addPoint(200, 200);
triangle.addPoint(300, 200);
shapes.add(triangle);
repaint();
To remove all shapes from the screen:
shapes.clear();
repaint();
You should also make sure that you switch to the Swing thread at the start of the program, because it is not safe to interact with Swing components from the main thread. In main:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
.
.
.
frame.setVisible(true);
}
});
}
Your mouseClicked method should not create a new Triangle object. After resetting triangle just add new points to it. Also don't draw in this method but call repaint:
public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
if (triangle.contains(p)) {
System.out.println("1");
triangle.reset(); // change the current triangle
triangle.addPoint(600, 550); // new left
triangle.addPoint(700, 350); // new top
triangle.addPoint(800, 550); // new right
repaint(); // force repainting
} else {
System.out.println("Triangle dont have point");
}
}
Now if you want many triangles you should have a collection of Polygons. Like this :
public static class TrianglePanel extends JPanel implements MouseListener {
private Vector<Polygon> triangles;
public TrianglePanel() {
n = 0;
// Create an empty collection
triangles = new Vector<Polygon>();
//Create first triangle
Polygon triangle = new Polygon();
triangle.addPoint(400, 550); //left
triangle.addPoint(600, 550); //right
triangle.addPoint(500, 350); //top
// Add the triangle to the collection
triangles.add(triangle);
//Add mouse Listener
addMouseListener(this);
//Set size to make sure that the whole triangle is shown
setPreferredSize(new Dimension(300, 300));
}
/**
* Draws the triangles as this frame's painting
*/
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Polygon p : triangles) // Draw all triangles in the collection
g2d.draw(p);
}
//Required methods for MouseListener, though the only one you care about is click
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
/**
* Called whenever the mouse clicks. Could be replaced with setting the
* value of a JLabel, etc.
*/
public void mouseClicked(MouseEvent e) {
Graphics2D g2d = (Graphics2D) this.getGraphics();
Point p = e.getPoint();
// Do what you want with p and the collection
// For example : remove all triangles that contain the point p
ListIterator<Polygon> li = triangles.listIterator();
while (li.hasNext()) {
if (li.next().containsâ„—) li.remove();
}
// Do what you want to update the list
// For example: Add a new triangle...
Polygon triangle = new Polygon();
triangle.addPoint(600+n, 550); // left
triangle.addPoint(700+n, 350); //top
triangle.addPoint(800+n, 550); //right
triangles.add(triangle); // add the new triangle to the list
n += 10; // next new triangle will be "moved" right
repaint();
}
private int n;
}

Why is this drawing a box before the button is pressed?

I'm relatively new to java and I'm just trying to code a GUI that draws a box when a button is pressed. My problem is, my program draws the box before the button is pressed and I don't know how to fix this.
This is my controller class:
import java.awt.*;
import javax.swing.*;
import java.awt.event.* ;
public class TestController extends JFrame {
private JButton enterButton;
private JPanel buttonHolder;
private Container contentPane;
private TestView view;
public TestController() {
contentPane = getContentPane();
enterButton = new JButton("Enter");
buttonHolder = new JPanel();
buttonHolder.setPreferredSize(new Dimension (600, 100));
contentPane.add(buttonHolder, BorderLayout.SOUTH);
buttonHolder.add(enterButton);
view= new TestView();
view.setPreferredSize(new Dimension (125, 125));
contentPane.add(view, BorderLayout.CENTER);
enterButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
view.repaint();
}
});
}
public static void main(String[] args) {
TestController bc = new TestController() ;
bc.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
bc.pack();
bc.setVisible(true) ;
}
}
This is my view class:
import java.awt.* ;
import java.awt.geom.*;
import javax.swing.* ;
public class TestView extends JPanel {
public TestView() {}
public void paintComponent( Graphics g ){
super.paintComponent( g );
Graphics2D g2= ( Graphics2D ) g;
Rectangle2D rect= new Rectangle2D.Double(0, 0, 30, 30);
g2.setPaint( Color.CYAN );
g2.fill( rect );
}
}
You have many ways to do it.
You can for example set the visibility of TestView to false until the button is pressed.
view.setVisible(false);
and in your button's action listener:
view.setVisible(true);
Why did you have your problem:
Every Visual object you create is visible by default.
When you added your object to the board, it was drawn because of that.
This call showed your object: contentPane.add(view, BorderLayout.CENTER);
First of all, let go of the illusion that you control the paint process in Swing, you don't. Swing uses a passive repaint process which is controlled by the RepaintManager, it is this objects responsibility to decide what and when something should be repainted.
paintComponent is called as part of the repaint chain on your behalf by the RepaintManager and may be called for any number of reasons (many outside of your direct control).
Your code is doing exactly what you told it to.
If you want to change the state of the components painting, then you should probably use some kind of state variable to tell the paintComponent if it should paint the rectangle or not...
private boolean paintRect = false;
public void paintComponent( Graphics g ){
super.paintComponent( g );
if (paintRect) {
Graphics2D g2= ( Graphics2D ) g;
Rectangle2D rect= new Rectangle2D.Double(0, 0, 30, 30);
g2.setPaint( Color.CYAN );
g2.fill( rect );
}
}
You would then need to provide some kind of access to the state variable in the TreeView class.
public void setPaintRect(boolean paint) {
paintRect = paint;
repaint();
}
Now, in your actionPerformed method, you would simply need to set the state...
enterButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
view.setPaintRect(true); // or what ever you want.
}
});
On a side note. Your TreeView should be overriding getPreferredSize and returning a suitable size hint for other layout managers. You've gotten away with it by using BorderLayout and manually setting the size of the frame, but TreeView's default size is 0x0.
Take a look at
Performing Custom Painting
Painting in AWT and Swing
For more details

Button inside swing Rectangle2D.double

We have a swing application which displays a lot of rectangles. We use Rectangle2D.double class to draw the rectangles on a JPanel.
My requirement is this. Upon clicking the rectangle, I have to pick an image from the local filesystem and show it in a popup window or a frame.
My question is how can I provide a hyperlink or button inside that Rectangle2D.double rectangle.
Please let me know.
Thanks
-Jad.
I hope this is what you mean:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class RectButton extends JPanel {
Rectangle2D.Double rect;
public RectButton() {
this.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
Point point = e.getPoint();
System.out.println(checkRectArea(point));
// Do whatever else you want here.
}
});
}
public boolean checkRectArea(Point point) {
return rect.contains(point);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
rect = new Rectangle2D.Double(10, 10, 50, 50);
g2.draw(rect);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
RectButton r = new RectButton();
frame.add(r);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
}
}
This program draws a rectangle and print true if you clicked inside the rectangle, false otherwise.
You want to put a MouseListener on the panel, that will catch all clicks anywhere on the panel. You can then get the location of the click from the event and determine which rectangle the click happened in, and then call up the code appropriate for that event and location.

Make a button round

I'm trying to make a JButton round at the (x,y) coordinates of (150,210). I want the button to be an oval of the size (40,40). And the buttons background color red. It doesn't need text, so I should get a button to whatever size I want, right?
I am using a panel, and I set the setLayout to null:
setLayout(null)
I checked Google, and it brought me here. How can I do this?
JDC Tech Tips: August 26, 1999: Creating Round Swing Buttons
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class RoundButton extends JButton {
public RoundButton(String label) {
super(label);
// These statements enlarge the button so that it
// becomes a circle rather than an oval.
Dimension size = getPreferredSize();
size.width = size.height = Math.max(size.width,
size.height);
setPreferredSize(size);
// This call causes the JButton not to paint
// the background.
// This allows us to paint a round background.
setContentAreaFilled(false);
}
// Paint the round background and label.
protected void paintComponent(Graphics g) {
if (getModel().isArmed()) {
// You might want to make the highlight color
// a property of the RoundButton class.
g.setColor(Color.lightGray);
} else {
g.setColor(getBackground());
}
g.fillOval(0, 0, getSize().width-1,
getSize().height-1);
// This call will paint the label and the
// focus rectangle.
super.paintComponent(g);
}
// Paint the border of the button using a simple stroke.
protected void paintBorder(Graphics g) {
g.setColor(getForeground());
g.drawOval(0, 0, getSize().width-1,
getSize().height-1);
}
// Hit detection.
Shape shape;
public boolean contains(int x, int y) {
// If the button has changed size,
// make a new shape object.
if (shape == null ||
!shape.getBounds().equals(getBounds())) {
shape = new Ellipse2D.Float(0, 0,
getWidth(), getHeight());
}
return shape.contains(x, y);
}
// Test routine.
public static void main(String[] args) {
// Create a button with the label "Jackpot".
JButton button = new RoundButton("Jackpot");
button.setBackground(Color.green);
// Create a frame in which to show the button.
JFrame frame = new JFrame();
frame.getContentPane().setBackground(Color.yellow);
frame.getContentPane().add(button);
frame.getContentPane().setLayout(new FlowLayout());
frame.setSize(150, 150);
frame.setVisible(true);
}
}

Categories

Resources