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:
Related
When you draw a freehand line with any paint application with whatever brush it ends up stacking together multiple points of that brush to form a brush stroke.
For example a basic pen stroke would stack up 1 pixel as you drag the mouse.
In more advanced applications you have a brush which is just a fancy Shape, say: a star for example, and stroking the canvas with the "star brush" would just cause the paint application to draw multiple stars as you drag your mouse over the canvas.
Please correct me if I'm wrong.
I already have implemented the "brush" ( i.e. a basic circle ) and whenever the user drags their mouse over the canvas while holding the left-mouse-button the application draws a circle for each new mouse position.
My problem is the "undo feature" if you may call it this way.
When I undo an action, my application only deletes the last Shape ( circle ) drawn, while I want it to delete the whole free-hand drawing ( collection of Shapes / circles ) from the user first press of the left-mouse-button to the release.
How do I "pack" a collection a Shape objects into one ?
A problem is also the repainting of all those circles, I want the repainting of maybe 30000 circles to be fast, just like a BufferedImage.
I already use a BufferedImage as the background of my image.
Every Shape that is "older" than 50 gets permanently stored in the BufferedImage background.
Currently I store the last 50 Shape objects in an ArrayList and the 51st ( the oldest ) gets permanently stored in the BufferedImage.
So the user can't undo 50 actions but rather 50 Shapes.
Thank You!
stripped down code sample:
public class GraphicPanel extends JComponent{
private int x = 0;
private int y = 0;
private int compXLen = 100;
private int compYLen = 100;
private boolean isCompFilled = false;
private boolean isAreaToBePainted = false;
private boolean isFocused = false;
private Shape ghost;
private ArrayList<Shape> shapeBuffer = new ArrayList<Shape>();
private BufferedImage img = new BufferedImage( PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB );
private static final int PREF_W = 800;
private static final int PREF_H = 500;
#Override
public void paintComponent( Graphics gPlain ){
super.repaint();
Graphics2D g = (Graphics2D)gPlain;
//paint background
if (img != null){
g.drawImage(img, 0, 0, null);
}
ghost = new Ellipse2D.Double( x-( compXLen/2 ), y-( compYLen/2 ), compXLen, compYLen );
if( isAreaToBePainted ){
//add ghost Shape to ArrayList
add( g, ghost )
}
//paint arrayList
for( Shape s : shapeBuffer ){
g.fill( s );
}
if( isFocused ){
// draw ghost shape
g.draw( ghost );
}
}
/**
* adds circles to arrayList
*/
private void add( Graphics2D g, Shape s ){
//fetch last arrayList element in Shape shp
//add ghost shape at the top of arrayList
Graphics2D g2 = img.createGraphics();
shapeBuffer.add( shp );
g2.fill( shp );
g2.dispose();
}
public void clearArea(){
shapeBuffer = new ArrayList<Shape>();
img = new BufferedImage( PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB );
repaint();
}
private class GraphicPanelMouseListen implements MouseListener, MouseMotionListener{
/**
* #param e Mouse Event
* #since 0.1
*/
#Override
public void mouseClicked( MouseEvent e ){}
public void mousePressed( MouseEvent e ){
x = e.getX();
y = e.getY();
isAreaToBePainted = true;
repaint();
}
public void mouseReleased( MouseEvent e ){}
public void mouseEntered( MouseEvent e ){
isFocused = true;
}
public void mouseExited( MouseEvent e ){
isFocused = false;
repaint();
}
public void mouseDragged( MouseEvent e ){
isAreaToBePainted = true;
x = e.getX();
y = e.getY();
repaint();
}
public void mouseMoved( MouseEvent e ){
x = e.getX();
y = e.getY();
repaint();
}
}//public class GraphicPanelMouseListen implements MouseListener
}//public class GraphicPanel
You can avoid using these lists altogether by storing every finished brush stroke, or any action for that matter, in its own image. You can then layer these in order they were made. It will be fewer of them, you can easily define how many actions in past you want to have while the bottom most one containing everything that didn't fit in the history.
Pixel blit is relatively fast operation, unlike stuff you do with shapes, and you can easily make it faster by using VolatileImage instead of buffered image for collecting all actions in history.
In my opinion is this approach much faster, and less restricting for actions added in the future. You can easily just mark certain layers as invisible and move back and forth through history like in Photoshop for instance, but without any dependency on actual contents of said layers.
I am trying to copy an area around the mouse cursor from my Canvas into an Image using the gc.copyarea method. Afterwards I want to paint that image onto a composite which appears after the mouse has been pressed for 1 second.
Below is my code so far:
In the paintControl Method of my canvas I copy the area to an image:
img = new Image(display, 40, 40);
gc.copyArea(img, mousePosition.x-20, mousePosition.y-20);
In the paintControl Method of my Composite I paint that image:
if (img != null) {
gc.drawImage(img, 0, 0);
}
The problem I am having is that only a very small part of the area is actually copied and painted onto the composite. Of the 40x40 pixel area, only an area of 10x20 pixel area in the bottom left corner is visible in the composite.
Example:
The cursor is next to the top left corner of the compisite. Only a small part of the area around the cursor is painted onto the compisite
Question: How can I copy an area from my canvas and paint it onto another composite correctly? What did I do wrong in my Code?
With some trial and error I managed to get something working that supposedly comes close to what you want.
public class CopyArea {
public static void main( String[] args ) {
CopyArea copyArea = new CopyArea();
copyArea.create();
copyArea.run();
}
private Display display;
private Image sourceImage;
private Point canvasSize;
private Shell shell;
private Canvas source;
private Canvas destination;
private Point snippetOrigin;
private Image snippet;
CopyArea() {
display = new Display();
sourceImage = new Image( display, getClass().getResourceAsStream( "mona-lisa.jpeg" ) );
canvasSize = new Point( sourceImage.getBounds().width, sourceImage.getBounds().height );
shell = new Shell( display );
source = new Canvas( shell, SWT.NONE );
destination = new Canvas( shell, SWT.NONE );
}
void create() {
shell.setLayout( new RowLayout() );
source.setLayoutData( new RowData( canvasSize ) );
source.addPaintListener( new PaintListener() {
#Override
public void paintControl( PaintEvent event ) {
event.gc.drawImage( sourceImage, 0, 0 );
if( snippetOrigin != null ) {
snippet = new Image( display, 40, 40 );
event.gc.copyArea( snippet, snippetOrigin.x, snippetOrigin.y );
destination.redraw();
snippetOrigin = null;
}
}
} );
source.addMouseListener( new MouseAdapter() {
#Override
public void mouseDown( MouseEvent event ) {
snippetOrigin = new Point( event.x, event.y );
source.redraw();
}
} );
destination.setLayoutData( new RowData( canvasSize ) );
destination.addPaintListener( new PaintListener() {
#Override
public void paintControl( PaintEvent event ) {
event.gc.setBackground( display.getSystemColor( SWT.COLOR_WHITE ) );
event.gc.fillRectangle( event.gc.getClipping() );
if( snippet != null ) {
event.gc.drawImage( snippet, 0, 0 );
}
}
} );
}
void run() {
shell.pack();
shell.open();
while( !shell.isDisposed() ) {
if( !display.readAndDispatch() )
display.sleep();
}
display.dispose();
}
}
The sourceImage is just a placeholder for your drawing code. The source Canvas listens to mouse-down events and then draws a 40x40 snippet of its client are to the destination Canvas. Note that I used Canvas in both cases, but Canvas and Composite should also work.
Here is how it works: The mouse-listeners stores the mouse location into snippetOrigin and triggers a repaint of the source. The paint-listener of source takes a partial screen-shot of what it just has drawn if requested (snippetOrigin != null) and then forces destination to redrawitself. The paint-listener of destination simple draws the snippet image if there is any.
The code deos not yet correctly clip the copied image. If you press the mouse in the lower right corner of the source Canvas, some of the shell`s trimming will be copied as well.
For brevity, the code does not dispose of the snippet image before reusing it (and may have other leaks).
If you own the drawing code that paints your equivalent of the source Canvas, I would rather refactor it so that arbitrary portions can be drawn and then call the code with appropriate parameters to draw on the destination widget.
try this code, sample to get a copy of an canvas area and paste in an object
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 50, 50);
function copy() {
var imgData = ctx.getImageData(10, 10, 50, 50);
ctx.putImageData(imgData, 10, 70);
}
in this case the copy was paste at the canvas destination as origin
try this reference
https://www.w3schools.com/tags/canvas_getimagedata.asp
and this it the API reference
https://developer.mozilla.org/es/docs/Web/API/CanvasRenderingContext2D/getImageData
What code should I add so that the rectangles painted before continue to exist on the screen when new ones are printed.Here is the code
import javax.sound.midi.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class MiniMusicPlayer3
{
private boolean fire = false;
private JFrame frame;
public static void main(String args[])
{
MiniMusicPlayer3 mini = new MiniMusicPlayer3();
mini.go();
}
public void go()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600,600);
frame.setVisible(true);
MyDrawPanel boxes = new MyDrawPanel();
frame.getContentPane().add(boxes);
try
{
Sequencer player =MidiSystem.getSequencer();
player.open();
Sequence seq = new Sequence(Sequence.PPQ,4);
Track track = seq.createTrack();
int arr[] ={127};
player.addControllerEventListener(new MyDrawPanel(),arr);
//add notes to the track
for(int i = 5;i<61;i+=4)
{
track.add(makeEvent(144,1,i,100,i));
track.add(makeEvent(176,1,127,0,i));
track.add(makeEvent(128,1,i,100,(i+2)));
}
player.setSequence(seq);
player.setTempoInBPM(220);
player.start();
}
catch(Exception ex)
{
}
}
public MidiEvent makeEvent(int onOff,int one,int note,int vel,int tick)
{
MidiEvent event = null;
try
{
ShortMessage a = new ShortMessage();
a.setMessage(onOff,one,note,vel);
event = new MidiEvent(a,tick);
}
catch(Exception e)
{
}
finally
{
return event;
}
}
class MyDrawPanel extends JPanel implements ControllerEventListener
{
public void controlChange(ShortMessage message)
{
System.out.println("control change happens");
fire = true;
frame.repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(fire)
{
Graphics2D g2d = (Graphics2D)g;
int red = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
int green = (int)(Math.random()*255);
Color color = new Color(red,blue,green);
g2d.setColor(color);
int height = (int)((Math.random()*120)+10);
int width = (int)((Math.random()*120)+10);
int x = (int)((Math.random()*40)+10);
int y = (int)((Math.random()*40)+10);
g2d.fillRect(x, y, width, height);
fire = false;
}
}
}
}
Also why does the code above not let the rectangles persist as opposed to the code below that allows the circles to persist
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Animate
{
private JFrame frame;
private int x=10,y=10;
public static void main(String args[])
{
Animate ballRoll = new Animate();
ballRoll.go();
}
public void go()
{
frame = new JFrame();
frame.setSize(500,500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyRoll ball = new MyRoll();
frame.getContentPane().add(ball);
for(x = 5;x<=350;x++)
{
y=x;
try
{
Thread.sleep(50);
}
catch(Exception e)
{
System.out.println("dsfsd");
}
ball.repaint();
}
}
class MyRoll extends JPanel
{
public void paintComponent(Graphics g)
{
g.setColor(Color.ORANGE);
g.fillOval(x, y, 100, 100);
}
}
}
Replace frame.repaint() with this.repaint() and remove super.paintComponent(g) if you want to persist the previous painting as well but I never suggest you to use this approach. You have to redraw all the objects again in paintComponent().
Please have a look at below sections for detail information about Paint processing in Swing application.
Painting in AWT and Swing
The Paint processing
A Closer Look at the Paint Mechanism
"What code should I add so that the rectangles previously printed persist,instead of getting erased when the new rectangles are painted?"
Create a list of Rectangle2D object (as a class member).
Loop through the list in the paintComponent and paint each rectangle.
When you want to add a new rectangle, well, add a new rectangle to the list and repaint.
If you want different colors (or and other state) for each rectangle, create a wrapper like (See some of the examples below).
"Also why does the code above not let the rectangles persist as opposed to the code below that allows the circles to persist"
No rectangle are not "persisting" per se. What you are seeing are paint artifacts from not calling super.paintComponent which clears the previous paint. You should always call super.paintComponent though, like in your first example. So your best options is to go with the first part of my answer.
See a bunch of examples here and here and here and here and here and here.
The basic premise of all those examples is storing a list of similar object in a list and iterating through the list to paint all the objects. Each object can have its own specific state.
You could also extend your MyDrawPanel class so you keep track of which rectangles to paint plus their colors. So you could add each new rectangle to a list of rectangles and keep track of the rectangle colors using a map. Then you just need to add new rectangles to the list and the new color to the map. Finally, you'll need to loop through the list of rectangles and paint these one by one.
Here is how it could be done:
class MyDrawPanel extends JPanel implements ControllerEventListener
{
// List of all rectangles that needs to be painted
java.util.List<Rectangle> rectangles = new ArrayList<Rectangle>();
// Map over all colors for each rectangle that must be painted
Map<Rectangle, Color> rectangleColors = new HashMap<Rectangle, Color>();
public void controlChange(ShortMessage message)
{
System.out.println("control change happens");
fire = true;
frame.repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(fire)
{
Graphics2D g2d = (Graphics2D)g;
int red = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
int green = (int)(Math.random()*255);
Color color = new Color(red,blue,green);
g2d.setColor(color);
int height = (int)((Math.random()*120)+10);
int width = (int)((Math.random()*120)+10);
int x = (int)((Math.random()*40)+10);
int y = (int)((Math.random()*40)+10);
// Create a new rectangle to paint
Rectangle newRect = new Rectangle(x, y, width, height);
// Store the rectangle in the list over rectangles to paint
rectangles.add(newRect);
// Add the color of the rectangle in the map over rectangle colors
rectangleColors.put(newRect, color);
// Paint all the rectangles using their colors one by one
for (Rectangle rect : rectangles) {
// Get the color of the rectangle
Color rectColor = rectangleColors.get(rect);
// Set the rectangle color
g2d.setColor(rectColor);
// Fill the rectangle with the rectangle color
g2d.fill(rect);
}
fire = false;
}
}
}
There are two common approaches:
as has already been mentioned a couple of times, you keep a List of objects to paint and then iterate through the List in the paintComponent(...) method.
draw to a BufferedImage.
Take a look at Custom Painting Approaches which exams both of these approaches and contains working examples of both.
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
I am trying to make a game within JFrame but have run into a problem. I have created an object consisting of four images strung into one. My problem is, how do i paint this object in a JFrame?
Here is the code:
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.*;
public class t4
{
static boolean running;
public static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public static double width = screenSize.getWidth();
public static double height = screenSize.getHeight();
public static int x = ( 250 );
public static int y = ( 150 );
public static final int sx = (int)width;
public static final int sy = (int)height;
public static void main( String[] args ) throws IOException, InterruptedException
{
Image ur = new ImageIcon("redBlock.gif").getImage();
Image ll = new ImageIcon("redBlock.gif").getImage();
Image ul = new ImageIcon("blueBlock.gif").getImage();
Image lr = new ImageIcon("blueBlock.gif").getImage();
// Create game window...
JFrame app = new JFrame();
app.setIgnoreRepaint( true );
app.setUndecorated( true );
// Add ESC listener to quit...
app.addKeyListener( new KeyAdapter()
{
public void keyPressed( KeyEvent e )
{
if( e.getKeyCode() == KeyEvent.VK_ESCAPE )
running = false;
if((e.getKeyCode()==KeyEvent.VK_LEFT)||(e.getKeyCode()==KeyEvent.VK_KP_LEFT))
x-=10;
if((e.getKeyCode()==KeyEvent.VK_RIGHT)||(e.getKeyCode()==KeyEvent.VK_KP_RIGHT))
x+=10;
if((e.getKeyCode()==KeyEvent.VK_UP)||(e.getKeyCode()==KeyEvent.VK_KP_UP))
y-=10;
if((e.getKeyCode()==KeyEvent.VK_DOWN)||(e.getKeyCode()==KeyEvent.VK_KP_DOWN))
y+=10;
}
});
// Get graphics configuration...
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
// Change to full screen
gd.setFullScreenWindow( app );
if( gd.isDisplayChangeSupported() )
{
gd.setDisplayMode(new DisplayMode( sx, sy, 32, DisplayMode.REFRESH_RATE_UNKNOWN ));
}
// Create BackBuffer...
app.createBufferStrategy( 2 );
BufferStrategy buffer = app.getBufferStrategy();
// Create off-screen drawing surface
BufferedImage bi = gc.createCompatibleImage( sx, sy );
// Objects needed for rendering...
Graphics graphics = null;
Graphics2D g2d = null;
Color background = Color.BLACK;
Random rand = new Random();
// Variables for counting frames per seconds
int fps = 0;
int frames = 0;
long totalTime = 0;
long curTime = System.currentTimeMillis();
long lastTime = curTime;
running = true;
while( running )
{
try
{
// wait(500);
// count Frames per second...
lastTime = curTime;
curTime = System.currentTimeMillis();
totalTime += curTime - lastTime;
if( totalTime > 1000 )
{
totalTime -= 1000;
fps = frames;
frames = 0;
}
++frames;
// clear back buffer...
g2d = bi.createGraphics();
g2d.setColor( background );
g2d.fillRect( 0, 0, sx, sy );
// draw some rectangles...
/* int r = 45;
int g = 232;
int b = 163;
g2d.setColor( new Color(r,g,b) );
int w = ( 250 );
int h = ( 150 );
g2d.fillRect( x+25, y+25, w, h );*/
if(y<775)
{
y++;
}
else
{
y=0;
}
// display frames per second...
g2d.setFont( new Font( "Courier New", Font.PLAIN, 12 ) );
g2d.setColor( Color.GREEN );
g2d.drawString( String.format( "FPS: %s", fps ), 20, 20 );
// Blit image and flip...
graphics = buffer.getDrawGraphics();
graphics.drawImage( bi, 0, 0, null );
graphics.drawImage(ur,x,y,null);
graphics.drawImage(ll,x+50,y+50,null);
graphics.drawImage(ul,x,y+50,null);
graphics.drawImage(lr,x+50,y,null);
if( !buffer.contentsLost() )
buffer.show();
}
finally
{
// release resources
if( graphics != null )
graphics.dispose();
if( g2d != null )
g2d.dispose();
}
}
gd.setFullScreenWindow( null );
System.exit(0);
}
public static void wait(int x) throws InterruptedException
{
Thread.currentThread().sleep(x);
}
}
i want to create an object containing images ur,ll,ul, and lr and be able to draw it on the screen.
This is what you should do:
Modify the class to make it extend javax.swing.JComponent.
Override paintComponent(Graphics).
Create a javax.swing.Timer to manage the frame rate.
Override getPreferredSize().
First (as requested by DavidB) I'll give you an explanation as to why you should do these things, and then I'll show you how.
Explanation
Since you're trying to add your component to a JFrame, you'll need the component's class to be compatible with JFrame's add method (actually, it belongs to Container, but that doesn't matter very much). If you look at the JavaDoc documentation for add, you'll see that it won't accept just any Object; rather, it requires an instance of a Component (or subclass thereof). You could subclass Component instead of JComponent, but Component is more for AWT applications than Swing applications.
In short: Subclass JComponent so that JFrame.add will accept it as a parameter.
Once you've subclassed JComponent, you'll need to actually tell the window manager what to draw. You can put drawing code anywhere, but remember that it won't be invoked (used) unless something actually calls that method. The method that the graphics environment calls to start the painting process is called paintComponent*. If you override this method, then the graphics environment will invoke your custom painting code.
In short: Override paintComponent because that's what the graphics environment cares about.
Since you're most likely going to be animating in your game, you'll want to keep a constant rate of Frames per Second, right? If you don't, there are many factors (computer power, other applications running, drawing complexity, etc.) that could make the frame rate go haywire. To do this, you'll want to call the repaint method a specified number of times per second (once every frame). This is the point of the Swing timer. You give it a block of code and a number of milliseconds, and it will run that code every time the specified interval has been elapsed.
In short: Use a Swing timer so that you can keep the frame rate constant and controlled.
Imagine you have a word processing application. It has a menu bar at the top, the document window in the center, and a toolbar at the bottom. Obviously, you want the menu bar and toolbar to be small, and the document to take up as much space as possible, right? This is why you need to have each component tell you what its size should be, known as its preferred size. Overriding getPreferredSize allows you to return whatever size you want, thus controlling the size of the component.**
In short: Override getPreferredSize so that the window manager and graphics environment get all the sizes right.
* It's not actually paintComponent that's called; it's paint. However, the paint method calls paintComponent, paintBorder, and paintChildren:
This method actually delegates the work of painting to three protected
methods: paintComponent, paintBorder, and paintChildren. They're
called in the order listed to ensure that children appear on top of
component itself. Generally speaking, the component and its children
should not paint in the insets area allocated to the border.
Subclasses can just override this method, as always. A subclass that
just wants to specialize the UI (look and feel) delegate's paint
method should just override paintComponent.
(source: the JavaDoc)
** Overriding getPreferredSize does not actually guarantee that that is the size at which the component will be shown. It merely specifies the size at which it should be shown. Some layout managers will choose to ignore this (such as BorderLayout). However, when you call pack to size the window correctly, it should calculate the preferred size according to this size.
Procedure
Extending JComponent
To make the class extend JComponent, just change the class signature to this:
import javax.swing.JComponent;
public class MyGameDisplay extends JComponent {
...
}
Overriding paintComponent
You'll need to import the java.awt.Graphics class. See this example code for how to use paintComponent:
import javax.swing.JComponent;
import java.awt.Graphics;
public class MyGameDisplay extends JComponent {
// Some code here
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // this line is crucial; see below
g.drawString(100,100,"Hello world");
}
}
Note: Above, I mentioned the necessity of the invocation of super.paintComponent from within the paintComponent method. The reason for this is that it will (among other things) clear all graphics that you've earlier displayed. So, for example, if your program draws a circle moving across the screen, each iteration of the drawing will also contain a trail of circles from the previous drawings unless you call super.paintComponent.
Using a Timer
To get the FPS rate that you want, modify the class to include a Swing timer, as such:
// Include these imports:
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyGameDisplay extends JComponent {
private Timer t;
public MyGameDisplay() {
ActionListener al = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
}
t = new Timer(1000 / 30 /* frame rate */, al);
t.start();
}
}
Override getPreferredSize
The reason for overriding getPreferredSize is so that layout managers will know how to properly size the container.
While writing the actual logic to calculate the size may be difficult, overriding getPreferredSize in itself isn't. Do so like this:
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400); // for example
}
When you're all done, you can just run the following code:
import javax.swing.JFrame;
public class Test {
public static void main(String[] args) {
JFrame frame = new JFrame();
MyGameDisplay mgd = new MyGameDisplay();
frame.add(mgd);
frame.pack();
frame.setVisible(true);
}
}