I have been searching the internet to find examples or how to use java's drag and drop. I have found a few, but all the examples only allow you to drag into a specific location, i.e another list box or text area.
I want to know if there is a way to drag items onto a jpanel or similar container, having the item put anywhere freely on the container.
As long as the target is a supported drop target for the item you are dragging then you can drop it to containers like JPanel.
You control the way the dragged item is displayed at the drop location. If your panel overrides paintComponent() then you can paint the item however you find appropriate.
'is a way to drag items into a jpanel'
You can set a DropTarget to your JPanel.
public class MyDropTarget extends JPanel implements DropTargetListener {
private MyImage image;
private String text;
public MyDropTarget() {
setBackground(new Color(30,60,10));
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED, new Color(30,60,10).brighter(), new Color(30,60,10).darker() ) );
DropTarget dt = new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true, null);
this.setDropTarget( dt );
}
#Override
public void paintComponent( Graphics g ) {
super.paintComponent( g );
if( image != null && image.getImage() != null ) {
g.drawImage( image.getImage(), 0, 0, null );
if(image.isError()){
g.setColor(Color.BLACK);
g.drawString( text, 0, 0 );
}
}
}
public void dragEnter( DropTargetDragEvent dtde ) {
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.RAISED, Color.RED.brighter(), Color.RED.darker() ) );
}
public void dragExit( DropTargetEvent dte ) {
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED, UIManager.getColor( "MenuBar.highlight" ), UIManager.getColor( "MenuBar.shadow" ) ) );
}
public void dragOver( DropTargetDragEvent dtde ) {
}
public void drop( DropTargetDropEvent dtde ) {
try {
text = (String) dtde.getTransferable().getTransferData( DataFlavor.stringFlavor );
image = (MyImage)dtde.getTransferable().getTransferData( DataFlavor.imageFlavor );
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED, new Color(30,60,10).brighter(), new Color(30,60,10).darker() ) );
dtde.dropComplete( true );
repaint();
} catch( UnsupportedFlavorException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
this.setDropTarget( null );
}
public void dropActionChanged( DropTargetDragEvent dtde ) {
}
}
I implemented Drag&Drop in this way:
The quite convenient mechanism for the implementation of Drag&Drop has appeared in Java 6, but it does have its disadvantages. For example, you should explicitly specify a Drop Target, what is not very useful when you need to lay down the object near the Drop Target. Also in the standard implementation there is no guarantee of execution order of listeners’ methods. I'll tell you the concept of implementing a more extensible Drag&Drop.
Initially the mouse listeners (Mouse Listener and MouseMotionListener) should be assigned to all Drag Sources. It’s need to implement 3 methods: a method of mouse click on the object, a method of moving the mouse while holding the mouse button on the object (mouseDragged in MouseMotionListener) and the mouse up method.
The listeners assignment looks as follows:
component.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
//block click right mouse button
if (MouseEvent.BUTTON1 == e.getButton()) {
startDrag(e);
}
}
#Override
public void mouseReleased(MouseEvent e) {
//block click right mouse button
if (MouseEvent.BUTTON1 == e.getButton()) {
endDrag(e);
}
}
});
component.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
drag(e);
}
});
Accordingly when you click on the object Drag&Drop starts, when you move the mouse the object should be moved, when you release the mouse the object should change its position and be moved to a new container. If the object will be moved in the frame of one container, then it is possible to implement only mouseDragged () method, in which the coordinates of the dragged object will be changed:
#Override
public void mouseDragged(MouseEvent e) {
Point mouseLocation = e.getLocationOnScreen();
Component draggedComponent = (Component) e.getSource();
SwingUtilities.convertPointFromScreen(mouseLocation,
draggedComponent.getParent());
draggedComponent.setLocation(mouseLocation);
}
But dragged object coordinates can be set relative to the container in which it is located. Accordingly, when the mouse is moved to another container it is necessary to add a component to a new container and to calculate the new coordinates, etc. This method is not very beautiful and extensible, so I suggest using GlassPane to display the dragged object.
The algorithm looks as follows:
Click on the object.
Get a screenshot of the object (see how to make a screenshot).
Hide the original object.
Draw on glassPane a screenshot of the object, based on the
coordinates of the mouse.
When you move the mouse you need to redraw a screenshot according
to the new coordinates.
When you release the mouse you need to place the object on the
container under which the cursor is located.
Display the original object.
With this approach, we have no any dependences on the container on which the cursor should be placed to make Drop and correspondingly the object can be "Dropped" anywhere.
GlassPane with transparency effect:
public class GhostGlassPane extends JPanel {
private final AlphaComposite composite;
private BufferedImage ghostImage = null;
private Point location = new Point(0, 0);
public GhostGlassPane() {
setOpaque(false);
composite = AlphaComposite.getInstance(AlphaComposite.
SRC_OVER, 0.7f);
}
public void paintComponent(Graphics g) {
if (ghostImage == null)
return;
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(composite);
g2.drawImage(ghostImage, (int) (location.getX()),
(int) (location.getY()), null);
}
}
In this response only concept of implementation is given.
This information is taken from my article: Frequently Asked Questions during Java applet development
Related
I'm developing a simple shape editor in Java, I was drawing everything on BufferedImage scince I want to save drawn image. The user can draw shapes by choosing shape from the menu and then click on image. I started to implement dragging shapes. When I drag a shape, a path of this shape is being created... I assume it's because I draw on BufferedImage so it's like 'temporary'. Or am I wrong? I was thinking of re-write everything, this time not drawing on BufferedImage, just creating Graphics2D, but I have problems with implementing listeners as I need to transfer my Graphics2D there to add shapes there, also I don't know how to call repaint in for eg. my MouseListener class. I was planning to create BufferedImage when user wants to save image, I would create it with help of vector where I store all shapes (or is it better way?). Here's my Panel class:
public class Panel {
public static Graphics2D img2;
public static Graphics2D getIm() {
return this.img2;
}
public Panel(JFrame frame) {
JPanel panel = new JPanel(true) {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
img2 = (Graphics2D)g.create();
img2.dispose();
};
};
RenderingHints.VALUE_ANTIALIAS_ON); // setting rendering to achieve better looking shapes
panel.setBackground(Color.WHITE);
MouseListenerShapes mouseListenerShapes = new MouseListenerShapes();
panel.addMouseListener(mouseListenerShapes);
//MouseMoveAdapter mouseMouseMoveAdapter = new MouseMoveAdapter();
//panel.addMouseMotionListener(mouseMouseMoveAdapter);
//panel.addMouseListener(mouseMouseMoveAdapter);
frame.add(panel);
};
}
I did a lot of reading but still I can't make it right, I need my shapes to be resizable, movable, etc. but at the same time I want to save image afterwards,
The solution I'm going to propose is going to be far from complete, but I hope it will illustrate some concepts that should help you find success. I'm going to build a panel with two movable images, but will do so using patterns that should make this code easier to maintain.
I highly recommend separating your rendering layer (your JComponents) and your model layer (the shapes and their locations) into distinct objects. This is referred to as separation of concerns and will make your life easier in the long run.
First lets define one of your shapes that appears in the screen. I'm going to keep mine very simple and create one with location, size, and a BufferedImage:
class DrawNode
{
private Rectangle bounds;
private BufferedImage image;
public DrawNode()
{
}
public Rectangle getBounds()
{
return bounds;
}
public void setBounds( Rectangle bounds )
{
this.bounds = bounds;
}
public BufferedImage getImage()
{
return image;
}
public void setImage( BufferedImage image )
{
this.image = image;
}
}
Next, let's create a model for holding the collection of your shapes. One neat thing we can do with the model is have it accept a listener that will get invoked whenever parts of our 2D space become "invalidated." Parts of the space can become invalid and will need repainting if a node was in a given area previously and has been moved to a new area. We'll also include some helper methods for determining what shape occupies a given space, etc. These helpers can be greatly optimized in terms of their efficiency, but my simple version will use brute-force iteration:
class DrawPanelModel
{
private final List<DrawNode> nodes;
private final Consumer<Rectangle> invalidAreaListener;
public DrawPanelModel( Consumer<Rectangle> invalidAreaListener )
{
this.invalidAreaListener = invalidAreaListener;
nodes = new ArrayList<>();
}
public void addNode( DrawNode node )
{
nodes.add( node );
}
public Optional<DrawNode> getNodeForPoint( Point p )
{
return nodes.stream()
.filter( node -> node.getBounds().contains( p ))
.findFirst();
}
public Stream<DrawNode> getNodesInRectangle( Rectangle r )
{
return nodes.stream()
.filter( node -> node.getBounds().intersects( r ));
}
public void setNodeLocation( DrawNode node, Point p )
{
Rectangle bounds = (Rectangle)node.getBounds().clone();
bounds.setLocation( p );
setNodeBounds( node, bounds );
}
public void setNodeBounds( DrawNode node, Rectangle bounds )
{
Rectangle old = node.getBounds();
node.setBounds( Objects.requireNonNull( bounds ));
if ( old == null || !old.equals( bounds ))
{
invalidAreaListener.accept( bounds );
if ( old != null ) {
invalidAreaListener.accept( old );
}
}
}
}
Next we need a way to render our shapes/nodes to the screen. We could draw each node every time we call paint(...), but that's not very efficient since we only really need to re-draw the invalid areas. We can leave the rest of the areas alone by making sure the JComponent utilizes double-buffering:
https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#setDoubleBuffered(boolean)
Edit: Slight correction: double buffering will provide benefits mainly when it comes to scrolling if you were to place the component in a scroll pane. I don't think it will affect the behavior of simple repaint events. (Was tired when I wrote this yesterday)
One common technique used to accomplish this in Swing is to use a CellRendererPane in conjunction with a JLabel that you use as a renderer. Whenever we need to paint a node in a specific location, we can assign the desired image and size to the JLabel and have the CellRendererPane render that JLabel during the paint procedure (potentially more than once) in different locations.
Let's create such a JLabel subclass and give it a helper method to initialize its state form a given node:
class ShapeRenderer extends JLabel
{
private static final long serialVersionUID = 1L;
public ShapeRenderer() {
}
public void initFrom( DrawNode node )
{
setIcon( new ImageIcon( node.getImage() ));
setSize( node.getBounds().getSize() );
}
// Methods below are overridden as a performance optimization:
#Override
public void invalidate() {
}
#Override
public void validate() {
}
#Override
public void revalidate() {
}
#Override
public void repaint( long tm, int x, int y, int width, int height ) {
}
#Override
public void repaint( Rectangle r ) {
}
#Override
public void repaint() {
}
#Override
protected void firePropertyChange( String propertyName, Object oldValue, Object newValue ) {
}
#Override
public void firePropertyChange( String propertyName, boolean oldValue, boolean newValue ) {
}
}
At this point we have the JComponent left, let's create a custom one. We'll give it a DrawPanelModel, CellRendererPane, and our ShapeRenderer. It's going to subscribe to the model as a listener, calling repaint(Rectangle) in response to the model invalidating in area whenever a node moves. Keep in mind that repaint(...) does not paint immediately, but rather schedules a paint event to occur at a future point in time. What this implies is that we can actually move several nodes and the Swing framework will provide us with one paint event that will have the union of all of our invalid rectangles specified as the Graphic's clip area. But, our code doesn't really care whether the invalid areas are combined or not. If the framework decides to give us a paint event for each of the areas we invalidate, we can handle that as well:
class DrawPanel extends JComponent
{
private static final long serialVersionUID = 1L;
private final CellRendererPane renderPane;
private final ShapeRenderer renderer;
private final DrawPanelModel model;
public DrawPanel()
{
renderPane = new CellRendererPane();
add( renderPane );
setDoubleBuffered( true );
renderer = new ShapeRenderer();
model = new DrawPanelModel( this::repaint );
DrawMouseListener listener = new DrawMouseListener();
addMouseListener( listener );
addMouseMotionListener( listener );
}
public void addNode( BufferedImage image, Point loc )
{
DrawNode node = new DrawNode();
node.setImage( image );
model.addNode( node );
model.setNodeBounds( node, new Rectangle( loc, new Dimension( image.getWidth(), image.getHeight() )));
}
#Override
public void doLayout()
{
renderPane.setSize( getSize() );
}
private void paintBackground( Graphics2D g )
{
g.setColor( Color.WHITE );
g.fill( g.getClip() );
}
private void paintNodes( Graphics2D g )
{
model.getNodesInRectangle( g.getClipBounds() )
.forEach( node -> paintNode( node, g ));
}
private void paintNode( DrawNode node, Graphics2D g )
{
Rectangle r = node.getBounds();
renderer.initFrom( node );
renderPane.paintComponent( g, renderer, this, r );
}
#Override
public void paintComponent( Graphics aG )
{
Graphics2D g = (Graphics2D)aG.create();
paintBackground( g );
paintNodes( g );
}
class DrawMouseListener extends MouseAdapter
{
private Optional<DrawNode> movingNode;
public DrawMouseListener()
{
movingNode = Optional.empty();
}
#Override
public void mousePressed( MouseEvent e )
{
movingNode = model.getNodeForPoint( e.getPoint() );
}
#Override
public void mouseReleased( MouseEvent e )
{
movingNode = Optional.empty();
}
#Override
public void mouseDragged( MouseEvent e )
{
movingNode.ifPresent( node -> {
model.setNodeLocation( node, e.getPoint() );
} );
}
}
}
Finally, a manual test:
#Test
public void testPanel() throws InvocationTargetException, InterruptedException
{
SwingUtilities.invokeLater( () -> {
// Create frame:
JFrame frame = new JFrame();
frame.setLayout( new GridLayout( 1, 1 ));
// Create draw panel:
DrawPanel drawPanel = new DrawPanel();
frame.add( drawPanel );
// Show frame:
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setSize( new Dimension( 1000, 600 ));
frame.setVisible( true );
// Create first image:
BufferedImage image1 = new BufferedImage( 50, 50, BufferedImage.TYPE_INT_ARGB );
Graphics2D g = image1.createGraphics();
g.setColor( Color.BLUE );
g.fillOval( 0, 0, 50, 50 );
// Add first image to draw panel:
drawPanel.addNode( image1, new Point( 100, 100 ));
// Create second image:
BufferedImage image2 = new BufferedImage( 50, 50, BufferedImage.TYPE_INT_ARGB );
Graphics2D g2 = image2.createGraphics();
g2.setColor( Color.RED );
g2.fillOval( 0, 0, 50, 50 );
// Add second image to draw panel:
drawPanel.addNode( image2, new Point( 200, 100 ));
} );
Thread.sleep( Long.MAX_VALUE );
}
Results:
Say I'm in a Java Swing JFrame. I click my mouse. I want to get the location of the mouse click within the GUI. In java, the line
int mouseX = MouseInfo.getPointerInfo().getLocation.x;
Seems to give the location of the mouse on the entire screen. How would I get it's location relative to the GUI?
From MouseListener methods you can do:
#Override
public void mouseClicked(MouseEvent e) {
int x=e.getX();
int y=e.getY();
System.out.println(x+","+y);//these co-ords are relative to the component
}
Simply add this to your Component by:
component.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
}
});
Reference:
How to Write a Mouse Listener
Take a look at Component.getMousePosition.
Returns the position of the mouse pointer in this Component's coordinate space if the Component is directly under the mouse pointer, otherwise returns null. If the Component is not showing on the screen, this method returns null even if the mouse pointer is above the area where the Component would be displayed. If the Component is partially or fully obscured by other Components or native windows, this method returns a non-null value only if the mouse pointer is located above the unobscured part of the Component.
final Point mousePos = component.getMousePosition();
if (mousePos != null) {
final int mouseX = mousePos.x;
final int mouseY = mousePos.y;
...
}
... or, if you use a MouseListener, you may see my original comment...
Try using MouseEvent.getPoint.
The above will return the mouse point relative to the component to which the listener was bound.
public void mouseClicked(final MouseEvent evt) {
final Point pos = evt.getPoint();
final int x = pos.x;
final int y = pos.y;
}
You can add MouseListener to GUI component whose top left pixel should be threated as [0,0] point, and get x and y from MouseEvent
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.add(panel);
panel.addMouseListener(new MouseAdapter() {// provides empty implementation of all
// MouseListener`s methods, allowing us to
// override only those which interests us
#Override //I override only one method for presentation
public void mousePressed(MouseEvent e) {
System.out.println(e.getX() + "," + e.getY());
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setVisible(true);
MouseEvent has methods getX() and getY() that return the position relative to the source component.
Say I'm in a Java Swing JFrame. I click my mouse. I want to get the location of the mouse click within the GUI. In java, the line
int mouseX = MouseInfo.getPointerInfo().getLocation.x;
Seems to give the location of the mouse on the entire screen. How would I get it's location relative to the GUI?
From MouseListener methods you can do:
#Override
public void mouseClicked(MouseEvent e) {
int x=e.getX();
int y=e.getY();
System.out.println(x+","+y);//these co-ords are relative to the component
}
Simply add this to your Component by:
component.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
}
});
Reference:
How to Write a Mouse Listener
Take a look at Component.getMousePosition.
Returns the position of the mouse pointer in this Component's coordinate space if the Component is directly under the mouse pointer, otherwise returns null. If the Component is not showing on the screen, this method returns null even if the mouse pointer is above the area where the Component would be displayed. If the Component is partially or fully obscured by other Components or native windows, this method returns a non-null value only if the mouse pointer is located above the unobscured part of the Component.
final Point mousePos = component.getMousePosition();
if (mousePos != null) {
final int mouseX = mousePos.x;
final int mouseY = mousePos.y;
...
}
... or, if you use a MouseListener, you may see my original comment...
Try using MouseEvent.getPoint.
The above will return the mouse point relative to the component to which the listener was bound.
public void mouseClicked(final MouseEvent evt) {
final Point pos = evt.getPoint();
final int x = pos.x;
final int y = pos.y;
}
You can add MouseListener to GUI component whose top left pixel should be threated as [0,0] point, and get x and y from MouseEvent
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.add(panel);
panel.addMouseListener(new MouseAdapter() {// provides empty implementation of all
// MouseListener`s methods, allowing us to
// override only those which interests us
#Override //I override only one method for presentation
public void mousePressed(MouseEvent e) {
System.out.println(e.getX() + "," + e.getY());
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setVisible(true);
MouseEvent has methods getX() and getY() that return the position relative to the source component.
Say I'm in a Java Swing JFrame. I click my mouse. I want to get the location of the mouse click within the GUI. In java, the line
int mouseX = MouseInfo.getPointerInfo().getLocation.x;
Seems to give the location of the mouse on the entire screen. How would I get it's location relative to the GUI?
From MouseListener methods you can do:
#Override
public void mouseClicked(MouseEvent e) {
int x=e.getX();
int y=e.getY();
System.out.println(x+","+y);//these co-ords are relative to the component
}
Simply add this to your Component by:
component.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
}
});
Reference:
How to Write a Mouse Listener
Take a look at Component.getMousePosition.
Returns the position of the mouse pointer in this Component's coordinate space if the Component is directly under the mouse pointer, otherwise returns null. If the Component is not showing on the screen, this method returns null even if the mouse pointer is above the area where the Component would be displayed. If the Component is partially or fully obscured by other Components or native windows, this method returns a non-null value only if the mouse pointer is located above the unobscured part of the Component.
final Point mousePos = component.getMousePosition();
if (mousePos != null) {
final int mouseX = mousePos.x;
final int mouseY = mousePos.y;
...
}
... or, if you use a MouseListener, you may see my original comment...
Try using MouseEvent.getPoint.
The above will return the mouse point relative to the component to which the listener was bound.
public void mouseClicked(final MouseEvent evt) {
final Point pos = evt.getPoint();
final int x = pos.x;
final int y = pos.y;
}
You can add MouseListener to GUI component whose top left pixel should be threated as [0,0] point, and get x and y from MouseEvent
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.add(panel);
panel.addMouseListener(new MouseAdapter() {// provides empty implementation of all
// MouseListener`s methods, allowing us to
// override only those which interests us
#Override //I override only one method for presentation
public void mousePressed(MouseEvent e) {
System.out.println(e.getX() + "," + e.getY());
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setVisible(true);
MouseEvent has methods getX() and getY() that return the position relative to the source component.
I've got a problem in Java using a "canvas" class I created, which is an extended JPanel, to draw an animated ring chart. This chart is using a MouseListener to fetch click events.
The problem is that the mouse position does not seem to be accurate, meaning it does not seem to be relative to the "canvas" but instead relative to the window (in the left, upper corner I got about 30px for y coord).
This is my code:
I created a class, that extends JPanel and does have a BufferedImage as member.
public class Canvas extends JPanel {
public BufferedImage buf;
private RingChart _parent;
public Canvas(int width, int height, RingChart parent){
buf = new BufferedImage(width, height, 1);
...
In the paint component method I just draw the buffered image, so I am able to paint on the canvas from 'outside' by painting on the buffered image, which is public.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(buf, null, 0, 0);
}
Now there's a class RingChart which contains a "canvas":
public class RingChart extends JFrame{
public Canvas c;
...
And I create a Graphics2D from the bufferedImage in the canvas class. This g2d is used for painting:
public RingChart(){
c = new Canvas(1500,980,this);
add(c);
setSize(1500, 1000);
setTitle("Hans");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
g2d = (Graphics2D)c.buf.createGraphics();
...
What I now was trying to achieve, was a mouse listener that listened to mouse events happening on the canvas. So when the user clicks on the canvas I could retrieve the position he clicked on, upon the canvas, through the event variable.
So I created a mouse listener:
class MouseHandler implements MouseListener {
#Override
public void mouseClicked(MouseEvent e){
RingChart r = ((Canvas)e.getSource()).getParent();
r.mouseClick(e);
}
...
...and added this mouse listener to the canvas of the RingChart class (myChart is an instance of RingChart and c is the canvas it contains):
...
MouseHandler mouse = new MouseHandler();
myChart.c.addMouseListener(mouse);
...
But as I mentioned above, the mouse position, that's returned when the click event is called, does not seem to be accurate. I think the mistake must be somehow in the way I created that mouseListener or maybe assigned it to the wrong element or something like that. But I've tried quite a couple of things and it didn't change. Can maybe someone tell me, what I've done wrong?
UPDATE:
The code of the function "mouseClick" that is a member of RingChart and is called in the mouse listener:
public void mouseClick(MouseEvent evt){
//evt = SwingUtilities.convertMouseEvent(this, evt, c);
if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
for(Element e : elements){
if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
//do some stuff
}
}
}
}
Again, the hierarchy of my classes:
RingChart --contains a--> Canvas --got a--> MouseListener.
The shapes in this function are shapes that have been painted on the canvas c. Now I want to check, if the user has clicked on one of them. So as I thought, the shapes should be in canvas-coordinates and the event position should be in canvas-coordinates and everything should fit together. But it doesn't.
Now user MadProgrammer told me, to use the ConvertMouseEvent function. But I currently don't see which exact way I should use this sensibly.
UPDATE:
I found a solution: All I had to do is adding the canvas not directly to the JFrame but to the ContentPane of the JFrame instead:
So instead:
public RingChart(){
c = new Canvas(1500,980,this);
add(c);
...
I do:
public RingChart(){
c = new Canvas(1500,980,this);
getContentPane().add(c);
...
Then I give the MouseListener to the ContentPane.
getContentPane().addMouseListener(new MouseHandler());
getContentPane().addMouseMotionListener(new MouseMoveHandler());
I don't know, if this is an elegant solution, but it works.
The mouse event is automatically converted to be relative to the component that it occurred in that is, point 0x0 is always the top left corner of the component.
By using RingChart r = ((Canvas)e.getSource()).getParent(), you've effectively changed the reference, which now means the location is no longer valid.
You need to convert the location so that its coordinates are in the context of the parent component. Take a look at SwingUtilities.convertMouseEvent(Component, MouseEvent, Component)
UPDATE with PICTURES
Lets take this example...
The blue box has a relative position of 50px x 50px to the red box. If you click in the blue box, lets say at 25x25, the mouse coordinates will be relative to the blue box (0x0 will be the top left of the blue box).
If you then pass this event to the red box and try and use the coordinates from it, you will find that the coordinates will now be half way between the top left of the red box and the blue box, because the coordinates are context sensitive.
In order to get it to work, you need to translate the mouse events location from the blue box to the red box, which would make it 75x75
Now, I don't know what you're doing when you pass the mouse event to the RingChart so I'm only guessing that this is the issue you're facing.
UPDATED with Click Code
Okay, lets say, you have a Canvas at 100x100. You click on that Canvas at 50x50. You then pass that value back up the chain.
public void mouseClick(MouseEvent evt){
//evt = SwingUtilities.convertMouseEvent(this, evt, c);
if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
for(Element e : elements){
// Here, we are asking the shape if it contains the point 50x50...
// Not 150x150 which would be the relative position of the click
// in the context to the RingChart, which is where all your objects
// are laid out.
// So even the original Canvas you clicked on will return
// false because it's position + size (100x100x width x height)
// does not contain the specified point of 50x50...
if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
//do some stuff
}
}
}
}
UPDATED
I think you have your references around the wrong way...
public static MouseEvent convertMouseEvent(Component source,
MouseEvent sourceEvent,
Component destination)
I think it should read something like
evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
UPDATE with Code Example
Okay, so, I put this little example together...
public class TestMouseClickPoint extends JFrame {
private ContentPane content;
public TestMouseClickPoint() throws HeadlessException {
setSize(600, 600);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
content = new ContentPane();
add(content);
}
protected void updateClickPoint(MouseEvent evt) {
content.updateClickPoint(evt);
}
protected class ContentPane extends JPanel {
private Point relativePoint;
private Point absolutePoint;
public ContentPane() {
setPreferredSize(new Dimension(600, 600));
setLayout(null); // For testing purpose only...
MousePane mousePane = new MousePane();
mousePane.setBounds(100, 100, 400, 400);
add(mousePane);
}
protected void updateClickPoint(MouseEvent evt) {
absolutePoint = new Point(evt.getPoint());
evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
relativePoint = new Point(evt.getPoint());
System.out.println(absolutePoint);
System.out.println(relativePoint);
repaint();
}
protected void paintCross(Graphics2D g2d, Point p) {
g2d.drawLine(p.x - 5, p.y - 5, p.x + 5, p.y + 5);
g2d.drawLine(p.x - 5, p.y + 5, p.x + 5, p.y - 5);
}
/*
* This is not recommended, but I want to paint ontop of everything...
*/
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
if (relativePoint != null) {
g2d.setColor(Color.BLACK);
paintCross(g2d, relativePoint);
}
if (absolutePoint != null) {
g2d.setColor(Color.RED);
paintCross(g2d, absolutePoint);
}
}
}
protected class MousePane extends JPanel {
private Point clickPoint;
public MousePane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
clickPoint = e.getPoint();
TestMouseClickPoint.this.updateClickPoint(e);
repaint();
}
});
setBorder(new LineBorder(Color.RED));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLUE);
if (clickPoint != null) {
g2d.drawLine(clickPoint.x, clickPoint.y - 5, clickPoint.x, clickPoint.y + 5);
g2d.drawLine(clickPoint.x - 5, clickPoint.y, clickPoint.x + 5, clickPoint.y);
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
new TestMouseClickPoint().setVisible(true);
}
}
Basically, it will paint three points. The point that the mouse was clicked (relative to the source of the event), the unconverted point in the parent container and the converted point with the parent container.
The next thing you need to do is determine the mouse location is actually been converted, failing that. I'd probably need to see a working example of your code to determine what it is you're actually doing.