Since Java only supports single inheritance, I desire to paint directly on an instance of a JPanel that is a member of the class Panel. I grab an instance of Graphics from the member and then paint whatever I desire onto it.
How can I not inherit from JComponent or JPanel and still utilize getGraphics() for painting on this without overriding public void paintComponent(Graphics g)?
private class Panel {
private JPanel panel;
private Graphics g;
public Panel() {
panel = new JPanel();
}
public void draw() {
g = panel.getGraphics();
g.setColor(Color.CYAN);
g.draw(Some Component);
panel.repaint();
}
}
The panel is added to a JFrame that is made visible prior to calling panel.draw(). This approach is not working for me and, although I already know how to paint custom components by inheriting from JPanel and overriding public void paintComponent(Graphics g), I did not want to inherit from JPanel.
Here are some very simple examples which show how to paint outside paintComponent.
The drawing actually happens on a java.awt.image.BufferedImage, and we can do that anywhere, as long as we're on the Event Dispatch Thread. (For discussion of multithreading with Swing, see here and here.)
Then, I'm overriding paintComponent, but only to paint the image on to the panel. (I also paint a little swatch in the corner.)
This way the drawing is actually permanent, and Swing is able to repaint the panel if it needs to without causing a problem for us. We could also do something like save the image to a file easily, if we wanted to.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
It's also possible to set up a JLabel with an ImageIcon, although personally I don't like this method. I don't think JLabel and ImageIcon are required by their specifications to see changes we make to the image after we've passed it to the constructors.
This way also doesn't let us do stuff like painting the swatch. (For a slightly more complicated paint program, on the level of e.g. MSPaint, we'd want to have a way to select an area and draw a bounding box around it. That's another place we'd want to be able to paint directly on the panel, in addition to drawing to the image.)
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JLabel label = new JLabel(new ImageIcon(img));
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
label.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// label to make sure the
// changes are visible
// immediately.
label.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
label.setPreferredSize(new Dimension(img.getWidth(), img.getHeight()));
label.setBackground(Color.white);
label.setOpaque(true);
label.addMouseListener(drawer);
label.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
label.setCursor(cursor);
frame.add(label, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class SomeComponent extends JComponent {
private Graphics2D g2d;
public void paintComponent(Graphics g) {
g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLACK);
g2d.scale(scale, scale);
g2d.drawOval(0, 0, importance, importance);
}
public Graphics2D getG2d() {
return g2d;
}
public void setG2d(Graphics2D g2d) {
this.g2d = g2d;
}
}
then you can do the following
get the SomeComponent instance in the panel and modify it
Graphics2D x= v.getPanel().get(i).getG2d;
x.setColor(Color.BLUE);
v.getPanel().get(i).setG2d(x);
v.getPanel().repaint();
v.getPanel().revalidate();
V is a class that extends JFrame and contains the panel in it AND
i is instance of SomeComponent
Related
I am trying to create spinning image Animation but something seems to be not working in the code. I am rotating image at various angles and drawing it but at the end I only end up single rotated image than animation. Is this possible to do in Java or do I need switch to C# Unity where I found multiple examples on doing so nothing so far in Java. I am new to Swing so I would really appreciate simplified answer.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rotate extends JPanel {
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 1000);
frame.setVisible(true);
for(int i = 0; i < 300; i++) {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
repaint();
}
}
class MyDrawPanel extends JPanel{
Image image = new ImageIcon(
getClass()
.getResource("wheel.png"))
.getImage();
public void animateCircle(Graphics2D g2d ) {
//g2d = (Graphics2D) g2d.create();
g2d.rotate(Math.toRadians(25), 250, 250);
g2d.drawImage(image, 0, 0, 500, 500, this);
}
#Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
Graphics2D g2d = (Graphics2D) g;
animateCircle(g2d);
}
}
}
I tried moving for loop in the paintComponent() method but it didn't help either.
Here
public void animateCircle(Graphics2D g2d ) {
//g2d = (Graphics2D) g2d.create();
g2d.rotate(Math.toRadians(25), 250, 250);
g2d.drawImage(image, 0, 0, 500, 500, this);
}
You rotation is fixed, so you aren't seeing your image spinning
By changing your value in Math.toRadians(...), you can make it appear to spin
This
for(int i = 0; i < 300; i++) {
rotationStep ++;
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
repaint();
}
}
Is the wrong way to do animation in swing. The right way is to use a javax.swing.Timer
public class MyTimer implements ActionListener {
int rotationStep = 0;
public void actionPerformed(ActionEvent ae) {
// for(int i = 0; i < 300; i++) {
rotationStep ++;
repaint();
}
}
You can do it like this. I reorganized some of your code which you can of course change at your leisure. Most of the critical elements are documented within the code. Major changes include:
eliminating magic numbers - allows alterations to happen in one place
using Rendering hints to eliminate rotating images.
overridding getPreferredSize() in the panel class
computing panel size to allow full rotation within panel.
using a swing timer to control repaint and angle updates
public class Rotate extends JPanel {
BufferedImage image = null;
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
File file = new File("wheel.png");
try {
image = ImageIO
.read(new FileInputStream(new File("H:/Bench.jpg")));
} catch (IOException ioe) {
ioe.printStackTrace();
}
// invoke an instance of the panel with the image
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
// center the frame on the screen
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// start the animation
p.animateCircle();
}
class MyDrawPanel extends JPanel {
double angle = 0;
//increment of the angle of rotation
double inc = Math.toRadians(.1);
int imageWidth;
int imageHeight;
int panelWidth;
int panelHeight;
double ctrX;
double ctrY;
int startX;
int startY;
#Override
public Dimension getPreferredSize() {
return new Dimension(panelWidth, panelHeight);
}
public MyDrawPanel() {
double imageWidth = image.getWidth();
double imageHeight = image.getHeight();
setBackground(Color.WHITE);
// compute panel size to allow full rotation within by computing
//the image's diagonal length
panelWidth = (int)Math.hypot(imageWidth, imageHeight);
panelHeight = panelWidth;
//target location for writing object (upper left corner)
startX = (int)(panelWidth-imageWidth)/2;
startY = (int)(panelHeight-imageHeight)/2;
// center of rotation
ctrX = panelWidth/2;
ctrY = panelHeight/2;
}
// This starts the animation using a swing timer to update the angle and repaint the image
public void animateCircle() {
Timer timer = new Timer(0, (ae)-> {
angle += inc; repaint();
});
timer.setDelay(10);
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// setting the rendering hints allows smooth rotation of images with minimal distortion
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// rotate the graphics context about the center
g2d.rotate(angle, ctrX, ctrY);
// draw the image. Top left at startX and startY
g2d.drawImage(image, startX, startY, this);
}
}
}
Note: If the timer is set to a low value (faster rotation) and the image is too large, the image may not finish the current rotation cycle before the timer re-fires.
I have the following minimal code to draw a line with an arrow head:
package gui;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
public class StateBtn extends JPanel {
private static final long serialVersionUID = -431114028667352251L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// enable antialiasing
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// draw the arrow
Line2D.Double line = new Line2D.Double(0, getHeight()/2, 20, getHeight()/2);
drawArrowHead(g2, line);
g2.draw(line);
// If I call repaint() here (like in my answer below), it works
}
private void drawArrowHead(Graphics2D g2d, Line2D.Double line) {
AffineTransform tx = new AffineTransform();
tx.setToIdentity();
double angle = Math.atan2(line.y2-line.y1, line.x2-line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle-Math.PI/2d));
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0,5);
arrowHead.addPoint(-5,-5);
arrowHead.addPoint(5,-5);
Graphics2D g = (Graphics2D) g2d.create();
g.setTransform(tx);
g.fill(arrowHead);
g.dispose();
}
}
It is created like this:
package gui;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class Main extends JFrame {
private static final long serialVersionUID = 4085389089535850911L;
private JPanel contentPane;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main frame = new Main();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 500);
setLocation(0, 0);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
StateBtn stateBtn = new StateBtn();
stateBtn.setBounds(200,200,35,35);
contentPane.add(stateBtn);
}
}
The line is drawn properly but the arrow head is invisible until I call repaint(). The problem is that the element is a draggable one, so I would have to call repaint() twice every time the position is changed. This would make the code more complex and the GUI would be laggy.
Why can't the arrow head just be drawn together with the line? Is there really no one who can help me?
You've not posted a true MCVE, so it's impossible to know what you could be doing wrong, but there's no need for the kludge you've used in your answer, where you re-call repaint() within paintComponent. If you still need help with your own code, then please post a valid MCVE, code we can compile and run without modification. For an example of what I mean by MCVE, please read the MCVE link and look at the example MCVE that I've posted in my answer below.
Having said this, understand that generally Swing graphics are passive, meaning that you would have your program change its state based on an event, then call repaint() and this suggests to the Swing repaint manager to call paint. There is no guarantee that painting will occur, since repaint requests that have "stacked", that are backing up due to many being called in a short time, may be ignored.
So in your case, we can use your code and modify it to see how this works. Say I give my JPanel a MouseAdapter -- a class that is both a MouseListener and MouseMotionListener, and in this adapter I simply set two Point instance fields, p0 -- for where the mouse was initially pressed, and p1 -- for where the mouse drags or releases. I can set these fields and then call repaint, and let my painting methods use p0 and p1 to draw my arrow. So the mouse adapater could look like so:
private class MyMouse extends MouseAdapter {
private boolean settingMouse = false;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
p0 = e.getPoint();
p1 = null;
settingMouse = true; // drawing a new arrow
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
setP1(e);
settingMouse = false; // no longer drawing the new arrow
}
#Override
public void mouseDragged(MouseEvent e) {
setP1(e);
}
private void setP1(MouseEvent e) {
if (settingMouse) {
p1 = e.getPoint();
repaint();
}
}
}
And then within my painting code, I'd use your code, modified to make it use my p0 and p1 points:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
if (p0 != null && p1 != null) {
Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y);
drawArrowHead(g2, line);
g2.draw(line);
}
}
The whole shebang would look like so:
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class StateBtn extends JPanel {
// constants to size the JPanel
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int AH_SIZE = 5; // size of arrow head -- avoid "magic"
// numbers!
// our start and end Points for the arrow
private Point p0 = null;
private Point p1 = null;
public StateBtn() {
// create and add a label to tell the user what to do
JLabel label = new JLabel("Click Mouse and Drag");
label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 42));
label.setForeground(new Color(0, 0, 0, 50));
setLayout(new GridBagLayout());
add(label); // add it to the center
// create our MouseAdapater and use it as both MouseListener and
// MouseMotionListener
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// only do this if there are points to draw!
if (p0 != null && p1 != null) {
Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y);
drawArrowHead(g2, line);
g2.draw(line);
}
}
private void drawArrowHead(Graphics2D g2d, Line2D.Double line) {
AffineTransform tx = new AffineTransform();
tx.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle - Math.PI / 2d));
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, AH_SIZE); // again avoid "magic" numbers
arrowHead.addPoint(-AH_SIZE, -AH_SIZE);
arrowHead.addPoint(AH_SIZE, -AH_SIZE);
Graphics2D g = (Graphics2D) g2d.create();
g.setTransform(tx);
g.fill(arrowHead);
g.dispose(); // we created this, so we can dispose of it
// we should **NOT** dispose of g2d since the JVM gave us that
}
#Override
public Dimension getPreferredSize() {
// size our JPanel
return new Dimension(PREF_W, PREF_H);
}
private class MyMouse extends MouseAdapter {
private boolean settingMouse = false;
#Override
public void mousePressed(MouseEvent e) {
// if we press the wrong mouse button, exit
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
p0 = e.getPoint(); // set the start point
p1 = null; // clear the end point
settingMouse = true; // tell mouse listener we're creating a new arrow
repaint(); // suggest a repaint
}
#Override
public void mouseReleased(MouseEvent e) {
setP1(e);
settingMouse = false; // no longer drawing the new arrow
}
#Override
public void mouseDragged(MouseEvent e) {
setP1(e);
}
private void setP1(MouseEvent e) {
if (settingMouse) {
p1 = e.getPoint(); // set the end point
repaint(); // and paint!
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
StateBtn mainPanel = new StateBtn();
JFrame frame = new JFrame("StateBtn");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
This code is what I meant by my example of a MCVE. Actually it's a little large for a decent MCVE, but it will do. Please compile and run the code to see that it works. If this doesn't help you, if you still must use a kludge with your repaint calls, then I urge you to create your own MCVE and post it with your question, and then comment to me so I can see it.
An aside, someone questioned if it was OK to create a new Graphics object as you're doing within drawArrowHead(...) method, and yes this not only OK, it's the preferred thing to do when dealing with AffineTransforms, since this way you don't have to worry about down-stream effects that the transform might have on border and child components that might share the original Graphics object. Again this is OK, as long as you follow the rule of disposing Graphics objects that you yourself create, and not disposing Graphics objects given to you by the JVM.
OK, it seems, there is no other way than to call repaint again. I did like this at the end of the paintComponent-method:
if (repaint == false) {
repaint = true;
} else {
repaint = false;
repaint();
}
Hence, it repaints exactly one time. But is there no cleaner solution?
I have some code to draw rectangles. It's used to draw rectangles on a JPanel, to mark boundaries of widgets. Here the code first, after that I'll explain my problem cq. question.
First off, I have a class (WidgetDrawingPanel) which extends JPanel.
public WidgetDrawingPanel(int width, int height) {
/*To make things visible at least*/
widgets.add(new Widget(10,10,100,100, WidgetType.TextField));
widgets.add(new Widget(50,50,100,200, WidgetType.TextField));
this.width = width;
this.height = height;
this.setBackground(Color.BLUE);
addListener(); //adds both MouseMotionListener and MouseListener
}
Below you'll see me reference ch a lot. This is a CoordinateHolder, which holds start and current coordinates of my mouse movement.
private void addListener() {
this.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent arg0) {
ch.currentX = arg0.getX();
ch.currentY = arg0.getY();
System.out.println("dragging " + ch.currentX + ","+ch.currentY);
WidgetDrawingPanel.this.repaint();
}
});
this.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent event) {
ch.endX = event.getX();
ch.endY = event.getY();
try {
checkCoords();
} catch (OutsidePanelException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "drawn Outside Panel");
}
}
#Override
public void mousePressed(MouseEvent event) {
ch = new CoordinateHolder(event.getX(), event.getY());
}
});
}
and, finally, the paintComponent(Grapics) method. There's loop through Widgets, which are actually just already drawn Rects (x, y, w, h attributes), but which a little more information, which is not useful in the drawing part of the application. Everytime you release the mouse, the CoordinateHolder is converted into a Widget, and added to widgets.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Paint");
g.setColor(Color.BLUE);
g.fillRect(0, 0, width, height); //making the whole panel blue
g.setColor(Color.RED);
Graphics2D g2 = (Graphics2D)g;
g2.setStroke(new BasicStroke(3));
for (Widget w : widgets) {
g.drawRect(w.getX(), w.getY(), w.getW(), w.getH());
}
if (ch != null)
g.drawRect(ch.startX, ch.startY, ch.currentX - ch.startX, ch.currentY - ch.startY);
}
This code is working, but I suspect this is highly inefficient and inperformant, as above code continually refreshes the JPanel on mouse drag, which is, say, once every 10ms? I suppose it'll get slow really soon, especially when the user creates a heck of a lot rectangles (which are also continally redrawn, as seen in painComponent(Graphics)).
Question cq. Problem
Is there a better, less resource consuming method, where the user can drag rectangles smoothly?
I read an answer to this Drag rectangle on JFrame in Java, but the author of that answer seems to do it the same as me. But again, that's way inperformant, right? Or should computers be easily able to redraw the component continually, and is this actually a valid approach?
To show lots of non-changing background shapes, draw them to a BufferedImage and then show that BufferedImage in the paintComponent(...) method. So while a shape is being drawn, draw it in paintComponent(...) but once the shape is done being drawn, perhaps on mouseRelease, then draw it in the background BufferedImage.
Note that what will slow your current drawing code the most may be your debugging SOP statements, but I assume that these will be removed from the finished code.
For example:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = 400;
private static final Color DRAWING_COLOR = new Color(255, 100, 200);
private static final Color FINAL_DRAWING_COLOR = Color.red;
private BufferedImage backgroundImg;
private Point startPt = null;
private Point endPt = null;
private Point currentPt = null;
public DrawingPanel() {
backgroundImg = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
Graphics g = backgroundImg.getGraphics();
g.setColor(Color.blue);
g.fillRect(0, 0, PREF_W, PREF_H);
g.dispose();
MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
addMouseMotionListener(myMouseAdapter);
addMouseListener(myMouseAdapter);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, this);
}
if (startPt != null && currentPt != null) {
g.setColor(DRAWING_COLOR);
int x = Math.min(startPt.x, currentPt.x);
int y = Math.min(startPt.y, currentPt.y);
int width = Math.abs(startPt.x - currentPt.x);
int height = Math.abs(startPt.y - currentPt.y);
g.drawRect(x, y, width, height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void drawToBackground() {
Graphics g = backgroundImg.getGraphics();
g.setColor(FINAL_DRAWING_COLOR);
int x = Math.min(startPt.x, endPt.x);
int y = Math.min(startPt.y, endPt.y);
int width = Math.abs(startPt.x - endPt.x);
int height = Math.abs(startPt.y - endPt.y);
g.drawRect(x, y, width, height);
g.dispose();
startPt = null;
repaint();
}
private class MyMouseAdapter extends MouseAdapter {
#Override
public void mouseDragged(MouseEvent mEvt) {
currentPt = mEvt.getPoint();
DrawingPanel.this.repaint();
}
#Override
public void mouseReleased(MouseEvent mEvt) {
endPt = mEvt.getPoint();
currentPt = null;
drawToBackground();
}
#Override
public void mousePressed(MouseEvent mEvt) {
startPt = mEvt.getPoint();
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Is it possible to apply transformations to custom or premade controls in Swing? By one hand transformations are allowed, by other hand there are probably some gaps in implementation with this.
ATTENTION the question is about how to apply transfomations from control's parent, not about how to use transformations at all. I.e. transformation must be issued by the parent, while child should just obey it. So, please hint how to transform standard Swing controls or how to write custom controls which obey PARENT transformation.
Simple example which applies transform to Graphics before drawing childs and which doesn't work:
public class Tester_TransformDuringPaint_01 {
private static Logger log = LoggerFactory.getLogger(Tester_TransformDuringPaint_01.class);
private static class JPanelEx extends JPanel {
private AffineTransform transform = new AffineTransform();
public AffineTransform getTransform() {
return transform;
}
public void setTransform(AffineTransform transform) {
this.transform = transform;
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
AffineTransform savedTransform = g2.getTransform();
g2.transform(transform);
super.paintComponent(g);
g2.drawOval(0, 0, 100, 100);
g2.setTransform(savedTransform);
}
#Override
protected void paintChildren(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
AffineTransform savedTransform = g2.getTransform();
g2.transform(transform);
super.paintChildren(g);
g2.setTransform(savedTransform);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JButton button = new JButton("Button");
button.setBounds(0,20,100,60);
JPanelEx panel = new JPanelEx();
panel.setLayout(null);
panel.setBounds(10, 10, 640, 480);
panel.setBackground(Color.PINK);
panel.setTransform(AffineTransform.getScaleInstance(2, 1));
panel.add(button);
JFrameEx frame = new JFrameEx();
frame.setLayout(null);
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(0.5);
frame.center();
frame.setVisible(true);
}
});
}
}
draws the following:
while left half of the button looks alive, and bigger part looks dead.
This is because different pars of API draws button with different approach.
Modified O'Reilly hack 51
Below is the code based on #lbalazscs's example, which shows that transformations don't work even if they are "in bounds"
public class BackwardsJButton extends JButton {
public BackwardsJButton(String text) {
super(text);
}
public void paint(Graphics g) {
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D) g;
AffineTransform savedTransform = g2.getTransform();
AffineTransform flipTrans = new AffineTransform();
double widthD = (double) getWidth();
//flipTrans.setToTranslation(widthD, 0);
//flipTrans.scale(-2.0, 1);
flipTrans.scale(0.5, 1);
g2.transform(flipTrans);
super.paint(g);
g2.setTransform(savedTransform);
} else {
super.paint(g);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
buildFrame();
}
});
}
private static void buildFrame() {
JFrame f = new JFrame("Test");
f.setLayout(new FlowLayout());
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.add(new BackwardsJButton("BackwardsJLabel"));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
output follows (you may need to resize window and move mouse to see it, because Swing bug is located inside mouse hover code:
You have some innovative ideas how to abuse Swing :)
It is possible to apply affine transform while drawing components, but only if you are happy within the bounds of the component(for example you could mirror the text). If you override paint, you change how the component is drawn, but this will not change its size because the size depends on completely other variables, and you still cannot draw reliably outside its bounds.
I think it is not a good idea to transform the painting of premade components, because even if you succeed graphically, mouse clicks will be expected in the original places.
Note that you need to reset the transformation after you are done, because the same Graphics object will be used to paint other components.
AffineTransform savedTransform = g.getTransform();
g.setTransform(specialTransform);
... your drawing here
g.setTransform(savedTransform);
EDIT: here is a complete running example of a transformed component
import javax.swing.*;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class ScaledButton extends JButton {
public ScaledButton(String text) {
super(text);
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Color savedColor = g2.getColor();
g2.setColor(getBackground());
g2.fill(new Rectangle(0, 0, getWidth(), getHeight()));
g2.setColor(savedColor);
AffineTransform backup = g2.getTransform();
g2.scale(0.5, 1);
super.paintComponent(g);
g2.setTransform(backup);
}
#Override
protected void paintBorder(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
AffineTransform backup = g2.getTransform();
g2.scale(0.5, 1);
super.paintBorder(g);
g2.setTransform(backup);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
buildFrame();
}
});
}
private static void buildFrame() {
JFrame f = new JFrame("Test");
f.setLayout(new FlowLayout());
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.add(new ScaledButton("ScaledButton"));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
I have a paint programme and i have all the buttons and sliders done however i am having a problem with the actual painting itself. When I drag the cursor across the screen instead of an unbroken line I am getting almost a dotted line which i dont want. Here's the code for the MouseListener in the JPanel and BufferedImage:
public void mouseDragged(MouseEvent e) {
Graphics g=buffered.getGraphics();
g.setColor(mycol);
Graphics2D graph=(Graphics2D)g;
BasicStroke stroke=new BasicStroke(30);
graph.setStroke(stroke);
// g.fillRect(xcor, ycor, 20, 20);
/ /varx=e.getX();
ycor=e.getY();
xcor=e.getX();
int bad=xcor;
int good=ycor;
graph.drawLine(xcor, ycor, bad, good);
// buffered.setRGB(xcor, ycor, mycol.getRGB());
repaint();
// g.drawLine(xcor, ycor, x, x)
repaint();
}
Just to justify my comment, I am adding this answer, though a slight
change from the comment is here, which being the use of
mousePressed(...) instead of mouseClicked(...).
One more addition being, since you wanted the Graphics2D object of
the BufferedImage so instead of using getGraphics() always use
createGraphics() which returns the Graphics2D object, hence you
don't really have to worry about the Cast thingy in this.
Please do have a look at the example below :
======================
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.net.URL;
import javax.swing.*;
import javax.imageio.ImageIO;
public class PaintingExample {
private BufferedImage bImage;
private ImageIcon image;
private JLabel imageLabel;
private int xClicked = 0;
private int yClicked = 0;
private int xDragged = 0;
private int yDragged = 0;
private MouseAdapter mouseListener = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent me) {
xClicked = me.getX();
yClicked = me.getY();
}
#Override
public void mouseDragged(MouseEvent me) {
xDragged = me.getX();
yDragged = me.getY();
Graphics2D g2 = bImage.createGraphics();
g2.setColor(Color.WHITE);
BasicStroke stroke=new BasicStroke(30);
g2.setStroke(stroke);
g2.drawLine(xClicked, yClicked, xDragged, yDragged);
g2.dispose();
imageLabel.setIcon(new ImageIcon(bImage));
}
};
public PaintingExample() {
try {
bImage = ImageIO.read(new URL(
"http://i.imgur.com/fHiBMwI.jpg"));
image = new ImageIcon(bImage);
} catch(Exception e) {
e.printStackTrace();
}
}
private void displayGUI() {
JFrame frame = new JFrame("Painting on Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel contentPane = new JPanel();
imageLabel = new JLabel(image);
imageLabel.addMouseListener(mouseListener);
imageLabel.addMouseMotionListener(mouseListener);
contentPane.add(imageLabel);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String... args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintingExample().displayGUI();
}
});
}
}
If I'm understanding your problem correctly, the major issue you are going to have is the number of updates you will receive when the mouse is dragged.
Even if you drag slowly, you will not always be notified of EVERY pixel movement, instead the system waits for a "idle" state (or threshold) to notify you so it "appears" to be a smooth movement.
I was able to put this together by modifying your code slightly
private MouseAdapter mouseListener =
new MouseAdapter() {
private boolean paint = false;
#Override
public void mousePressed(MouseEvent me) {
xClicked = me.getX();
yClicked = me.getY();
xDragged = xClicked;
yDragged = yClicked;
paint = true;
}
#Override
public void mouseReleased(MouseEvent e) {
xClicked = -1;
xClicked = -1;
xDragged = -1;
yDragged = -1;
paint = false;
}
#Override
public void mouseMoved(MouseEvent me) {
}
#Override
public void mouseDragged(MouseEvent me) {
if (paint) {
xClicked = xDragged;
yClicked = yDragged;
xDragged = me.getX();
yDragged = me.getY();
xDragged = me.getX();
yDragged = me.getY();
Graphics2D g2 = bImage.createGraphics();
g2.setColor(Color.WHITE);
g2.drawLine(xClicked, yClicked, xDragged, yDragged);
g2.dispose();
imageLabel.setIcon(new ImageIcon(bImage));
me.getComponent().invalidate();
me.getComponent().repaint();
}
}
};
Basically, the idea is to draw a line from the last "known location" to the current location.
Hope this is in the ball park
Thirty pixels is a very wide line, and I can imagine that when drawn without antialiasing, it's going to look very jagged; that's probably what you're seeing. You might want to try something like
graph.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
On the other hand, maybe you're already getting antialiasing, and you want to turn it off; then
graph.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
One of these is guaranteed to change the appearance of your image; hopefully it will be more to your liking.