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?
Related
I am trying to relocate a rectangle but for I cannot figure out why it stays in the same location.
It creates a red rectangle but does not change color or move to a new location.
Here is my code:
package grap_prj.dom.shenkar;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class graphic_main extends JPanel{
static Rectangle rec = new Rectangle ();
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
rec.setSize(10, 10);
rec.setLocation(10, 10);
g2d.setColor(Color.RED);
g2d.drawRect((int)rec.getX(),(int)rec.getY(), 10, 10);
g2d.fillRect((int)rec.getX(),(int)rec.getY(), 10, 10);
}
public static void update_ui (Graphics g)
{
System.out.println("in update");
rec.setLocation(50, 50);
Graphics2D g2d = (Graphics2D) g;
g2d.drawRect((int)rec.getX(),(int)rec.getY(), 10, 10);
g2d.fillRect((int)rec.getX(),(int)rec.getY(), 10, 10);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Simple Graphics");
frame.add(new graphic_main());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
graphic_main.update_ui(frame.getGraphics());
frame.revalidate();
}
}
Update:
I have made a few changes in the code, but still the same situation. I change the location but a new rectangle is added instead of moving the existing one.
You are continuosly setting location at 10,10 so the rectangle will always be drawn at 10,10.
After setting location 50,50 you aren't drawing anything. Next step you will set 10,10 again.
Whenever you override paintComponent(), you need to call super.paintComponent().
You are also calling repaint() from repaint(). You need to decide on some action that will cause it to repaint.
You should never call update() or repaint() inside of a paintComponent(...) method. Ever. This risks recursion or ineffective uncontrolled animation.
Don't change the state of your object inside of a paint or paintComponent method. You don't have full control over when or even if these methods get called.
Don't forget to call the super's method inside your paintComponent override to allow the JPanel to do its housekeeping graphics including erasing old dirty pixels.
Even though you change the Graphics context's Color to blue, it will change right back to red anytime the paintComponent is called. So your color change is futile code. Solution: make the Color a variable that can be set.
If you want to do Swing animation, use a Swing Timer.
For an example of Swing animation, have a look at my example here.
For another example, have a look at this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class SimpleAnimation extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Color[] COLORS = { Color.red, Color.orange,
Color.yellow, Color.green, Color.blue, Color.magenta };
private static final int RECT_WIDTH = 40;
private static final int TIMER_DELAY = 10;
private int x = 0;
private int y = 0;
private int colorIndex = 0;
private Color color = COLORS[colorIndex];
public SimpleAnimation() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
if (x + RECT_WIDTH > getWidth()) {
x = 0;
}
if (y + RECT_WIDTH > getHeight()) {
y = 0;
}
if (x % 40 == 0) {
colorIndex++;
colorIndex %= COLORS.length;
color = COLORS[colorIndex];
}
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SimpleAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SimpleAnimation());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Your changing the location before the frame gets the chance to render it. So its creating it at 10, 10 and then when its rendered changes it to 50 50 then to 10, 10.
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
I'm trying to modify this program so that it draws an image of a castle and I am able to scale this image using the up and down arrow keys. I can't manage to get the keylistener to work, the program runs but it isn't responding to key presses. Any help will be appreciated, thanks.
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
import java.net.*;
import java.awt.event.*;
public class DrawImage extends JFrame implements KeyListener {
int scale = 1;
private Image image;
enter code here
public static void main(String[] args) {
new DrawImage();
}
public DrawImage() {
super("DrawImage");
setSize(600,600);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Toolkit tk = Toolkit.getDefaultToolkit();
image = tk.getImage(getURL("castle.png"));
addKeyListener(this);
}
private URL getURL(String filename) {
URL url = null;
try {
url = this.getClass().getResource(filename);
}
catch (Exception e) { }
return url;
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform trans = new AffineTransform();
trans.scale(scale, scale);
System.out.println("scale: " + scale);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getSize().width, getSize().height);
g2d.setTransform(trans);
g2d.drawImage(image, 0, 40, this);
addKeyListener(this);
}
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { }
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_UP) {
scale++;
}
if (key == KeyEvent.VK_DOWN) {
scale--;
}
}
}
I can't manage to get the keylistener to work, the program runs but it isn't responding to key presses.
Yours is a very common problem that is asked here frequently, and almost always the problem is due to lack of focus -- that the component being listened to does not have the current focus, and focus is essential for a KeyListener to work.
The short answer is to give the component that is being listened to the focus.
The longer better answer is to not use KeyListeners and instead to use Key Bindings for such a project.
Edit
Other problems:
In your code above, you're adding the KeyListener to a JFrame, and this shouldn't be done, even if you had good reason to use KeyListeners.
You're also drawing directly in the JFrame which in my opinion is an even bigger problem, since you risk unwanted side effects.
You're adding a listener inside of the paint method, an even bigger problem. The paint method should not be overridden (in general), and if you did have to override it, should never be used for program logic, for adding or removing listeners, or for doing any non-painting activities. It should be for painting and painting only.
Instead you should draw directly in a JPanel or JComponent.
Instead you should do the drawing in a paintComponent(Graphics g) override in your painting JPanel or JComponent.
Just as described above, this method should be for painting and painting only and should be as fast as possible.
You should call the super.paintComponent(g) inside of your paintComponent(Graphics g) override.
Again the drawing component should listen for key strokes using Key Bindings (tutorial here). The tutorial will explain why this distinction (KeyListener vs. Key Bindings) is important.
Edit 2
You never want to ignore exceptions as your code shows here since this is the programming equivalent of driving blind:
try {
url = this.getClass().getResource(filename);
} catch (Exception e) {
}
Hopefully this is not how your production code looks, that you only ignored the exceptions in your posted code for the sake of brevity, but understand for many of us seeing this is like hearing nails on a chalk-board.
Edit 3
More on KeyListeners vs. Key Bindings: Assuming that you got your code to work with a KeyListener, then assume that you added any other focusable components to your GUI and they somehow got the focus via user interaction, then your KeyBindings will no longer work. If you had done this correctly with Key Bindings, this would not be an issue.
Edit 4
You really want your scale field to be a double, not an int. And you really don't want to increment and decrement it, but rather you want to multiply it and divide it by some multiplier constant, say 1.2 as an example. You will also want to call repaint() whenever you change your scale.
Edit 5
Please check out two sample programs, the first, called DrawImagePanelKeyListener.java, uses a KeyListener, while the second, called DrawImagePanelKeyBindings, uses Key Bindings. They both should compile, run, and function as expected: when you press the up or down arrow keys, the displayed image shrinks and grows. The difference in their behavior though can be seen when the JButton both have is pressed. Press the button and see what happens to your key response. When the component that has a KeyListener loses focus, its KeyListener will stop working, but the same is not true for the component that uses Key Bindings.
A kludge that could get around this problem could be to prevent all other components from getting the focus, but that is not practical or desired with most GUI's.
DrawImagePanelKeyListener.java
import java.awt.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.IOException;
import java.net.*;
import java.awt.event.*;
#SuppressWarnings("serial")
public class DrawImagePanelKeyListener extends JPanel implements KeyListener {
public static final String IMAGE_PATH = "https://duke.kenai.com/"
+ "nyanya/.Midsize/NyaNya.jpg.png";
private static final double MULTIPLIER = 1.2;
double scale = 1.0;
private Image image;
private Dimension initialSize = new Dimension(0, 0);
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DrawImagePanelKeyListener drawImage = new DrawImagePanelKeyListener();
drawImage.add(new JButton("Foo"));
JFrame frame = new JFrame("Draw Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawImage);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public DrawImagePanelKeyListener() {
setFocusable(true);
requestFocusInWindow();
URL imgURL;
try {
imgURL = new URL(IMAGE_PATH);
image = ImageIO.read(imgURL);
initialSize = new Dimension(image.getWidth(this),
image.getHeight(this));
addKeyListener(this);
setVisible(true);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return initialSize;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, getSize().width, getSize().height);
Graphics2D g2d = (Graphics2D) g.create();
g2d.scale(scale, scale);
g2d.drawImage(image, 0, 0, this);
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_UP) {
scale *= MULTIPLIER;
}
if (key == KeyEvent.VK_DOWN) {
scale /= MULTIPLIER;
}
repaint();
}
}
DrawImagePanelKeyBindings.java
import java.awt.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.IOException;
import java.net.*;
import java.awt.event.*;
#SuppressWarnings("serial")
public class DrawImagePanelKeyBindings extends JPanel {
public static final String IMAGE_PATH = "https://duke.kenai.com/"
+ "nyanya/.Midsize/NyaNya.jpg.png";
private static final double MULTIPLIER = 1.2;
double scale = 1.0;
private Image image;
private Dimension initialSize = new Dimension(0, 0);
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DrawImagePanelKeyBindings drawImage = new DrawImagePanelKeyBindings();
drawImage.add(new JButton("Foo"));
JFrame frame = new JFrame("Draw Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawImage);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public DrawImagePanelKeyBindings() {
setBindings();
URL imgURL;
try {
imgURL = new URL(IMAGE_PATH);
image = ImageIO.read(imgURL);
initialSize = new Dimension(image.getWidth(this),
image.getHeight(this));
setVisible(true);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void setBindings() {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
final KeyStroke[] keyStrokes = {
KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)
};
for (final KeyStroke keyStroke : keyStrokes) {
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent evt) {
myKeyPressed(keyStroke.getKeyCode());
}
});
}
}
#Override
public Dimension getPreferredSize() {
return initialSize;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, getSize().width, getSize().height);
Graphics2D g2d = (Graphics2D) g.create();
g2d.scale(scale, scale);
g2d.drawImage(image, 0, 0, this);
}
public void myKeyPressed(int key) {
if (key == KeyEvent.VK_UP) {
scale *= MULTIPLIER;
}
if (key == KeyEvent.VK_DOWN) {
scale /= MULTIPLIER;
}
repaint();
}
}
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.
I have a simple class that draws a line when mouse dragging or a dot when mouse pressing(releasing).
When I minimize the application and then restore it, the content of the window disappears except the last dot (pixel). I understand that the method super.paint(g) repaints the background every time the window changes, but the result seems to be the same whether I use it or not. The difference between the two of them is that when I don't use it there's more than a pixel painted on the window, but not all my painting. How can I fix this?
Here is the class.
package painting;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
class CustomCanvas extends Canvas{
Point oldLocation= new Point(10, 10);
Point location= new Point(10, 10);
Dimension dimension = new Dimension(2, 2);
CustomCanvas(Dimension dimension){
this.dimension = dimension;
this.init();
addListeners();
}
private void init(){
oldLocation= new Point(0, 0);
location= new Point(0, 0);
}
public void paintLine(){
if ((location.x!=oldLocation.x) || (location.y!=oldLocation.y)) {
repaint(location.x,location.y,1,1);
}
}
private void addListeners(){
addMouseListener(new MouseAdapter(){
#Override
public void mousePressed(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
paintLine();
}
#Override
public void mouseReleased(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
paintLine();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
paintLine();
}
});
}
#Override
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.red);
g.drawLine(location.x, location.y, oldLocation.x, oldLocation.y);
}
#Override
public Dimension getMinimumSize() {
return dimension;
}
#Override
public Dimension getPreferredSize() {
return dimension;
}
}
class CustomFrame extends JPanel {
JPanel displayPanel = new JPanel(new BorderLayout());
CustomCanvas canvas = new CustomCanvas(new Dimension(200, 200));
public CustomFrame(String titlu) {
canvas.setBackground(Color.white);
displayPanel.add(canvas, BorderLayout.CENTER);
this.add(displayPanel);
}
}
public class CustomCanvasFrame {
public static void main(String args[]) {
CustomFrame panel = new CustomFrame("Test Paint");
JFrame f = new JFrame();
f.add(panel);
f.pack();
SwingConsole.run(f, 700, 700);
}
}
You are not storing the state of the points you are drawing. When the panel is repainted, it only has information for the last point it drew.
Response to comment:
You would need to have a collection of Points, for instance ArrayList<Point> location = new ArrayList<Point>();
Then, in your listeners: location.add(new Point(me.getX(), me.getY()));
Finally, in paintLine():
for (Point location : locations) {
repaint(location.x,location.y,1,1);
}
The collection locations is usually referred to as a Display List. Most graphics programs use them.
Response to comment:
Yes, I expect so. I just tossed off an idea based on your code to give you a starting point. It is almost certainly a bad idea to do exactly as I have described.
Doesn't that mean I will draw all the points(instead of one) everytime I press or drag the mouse?
Yes, but #Dave's approach is perfectly satisfactory for thousands of nodes, as may be seen in GraphPanel. Beyond that, consider the flyweight pattern, as used by JTable renderers and illustrated here.
Addendum: Focusing on your AWTPainting questions, the variation below may illustrate the difference between System- and App-triggered Painting. As the mouse is dragged, repaint() invokes update(), which calls paint(); this is app-triggered. As you resize the window, only paint() is called (no red numbers are drawn); this is system-triggered. Note that there is a flicker when the mouse is released after resizing.
Flickering typically occurs when the entire component's background is cleared and redrawn:
4. If the component did not override update(), the default implementation of update() clears the component's background (if it's not a lightweight component) and simply calls paint().
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
public class AWTPainting {
public static void main(String args[]) {
CustomPanel panel = new CustomPanel();
Frame f = new Frame();
f.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(panel);
f.pack();
f.setVisible(true);
}
}
class CustomPanel extends Panel {
public CustomPanel() {
this.add(new CustomCanvas(new Dimension(320, 240)));
}
}
class CustomCanvas extends Canvas {
private MouseAdapter handler = new MouseHandler();
private List<Point> locations = new ArrayList<Point>();
private Point sentinel = new Point();
private Dimension dimension;
CustomCanvas(Dimension dimension) {
this.dimension = dimension;
this.setBackground(Color.white);
this.addMouseListener(handler);
this.addMouseMotionListener(handler);
this.locations.add(sentinel);
}
#Override
public void paint(Graphics g) {
g.setColor(Color.blue);
Point p1 = locations.get(0);
for (Point p2 : locations.subList(1, locations.size())) {
g.drawLine(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
}
#Override
public void update(Graphics g) {
paint(g);
g.clearRect(0, getHeight() - 24, 50, 20); // to background
g.setColor(Color.red);
g.drawString(String.valueOf(locations.size()), 8, getHeight() - 8);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
if (locations.get(0) == sentinel) { // reference identity
locations.set(0, new Point(e.getX(), e.getY()));
}
}
#Override
public void mouseDragged(MouseEvent e) {
locations.add(new Point(e.getX(), e.getY()));
repaint();
}
}
#Override
public Dimension getPreferredSize() {
return dimension;
}
}
#Andrew, #Dave, #trashgod Hi,
I did some research on this and, finally, this is what I've got. Please correct me if I'm wrong. You cannot override paint() so you call repaint() everytime you need to do app-triggered painting.
Repaint() calls update() which its default behavior is to call paint().
update() is used for incremental painting; that explains the flickering screen when paint() was doing all the work, which practically means it was painting the whole image at every step.
However, my question is if I add "locationsAdded = 0" in the update method that means everytime I drag the mouse I paint the whole image(like in paint), so why doesn't it blink like before?
I've also read something about painting in swing and I didn't understand why update() is never invoked for swing. Can you explain me why?
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
class CustomCanvas extends Canvas{
ArrayList<Point> locations;
int locationsAdded;
Point oldLocation;
Point location;
Dimension dimension;
CustomCanvas(Dimension dimension){
locations = new ArrayList<>();
this.dimension = dimension;
this.init();
addListeners();
}
private void init(){
oldLocation= new Point(0, 0);
location= new Point(0, 0);
}
public void paintLine(Graphics g, int x){
Point p1 = (Point)locations.get(x);
Point p2 = (Point)locations.get(x+1);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
locationsAdded++;
}
#Override
public void paint(Graphics g){
locationsAdded = 0;
g.setColor(Color.red);
for(int i = locationsAdded; i < locations.size()-1; i++){
paintLine(g, i);
}
}
public void update(Graphics g) {
//locationsAdded = 0;
for (int i = locationsAdded; i < locations.size()-1; i++) {
paintLine(g, i);
}
}
private void addListeners(){
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
locations.add(location);
repaint();
}
});
}
#Override
public Dimension getMinimumSize() {
return dimension;
}
#Override
public Dimension getPreferredSize() {
return dimension;
}
}
class CustomFrame extends Panel {
Panel displayPanel = new Panel(new BorderLayout());
CustomCanvas canvas = new CustomCanvas(new Dimension(700, 700));
public CustomFrame(String titlu) {
canvas.setBackground(Color.white);
displayPanel.add(canvas, BorderLayout.CENTER);
this.add(displayPanel);
}
}
public class AWTPainting {
public static void main(String args[]) {
CustomFrame panel = new CustomFrame("Test Paint");
Frame f = new Frame();
f.add(panel);
f.pack();
f.setSize(700,700);
f.show();
}
}
set your layout to Null layout