Why is paint(Graphics g) not getting called from child JComponent? - java

I'm having a simple JComponent that functions as a PdfPage but is basically just a rendered Image.
However, this PdfPage is supposed to hold Highlight annotations e.g. to mark an error inside a PDF Document.
Highlight is just another JComponent that is just painting itself as a rectangle:
public class Highlight extends JComponent {
private Rectangle rectangle;
private Color borderColor = new Color(0, 0, 0, 0);
private Color fillColor;
public Highlight(Rectangle rectangle, Color fillColor) {
this.rectangle = rectangle;
this.fillColor = fillColor;
}
#Override
public void paint(Graphics g) {
g.setColor(this.fillColor);
g.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
g.setColor(this.borderColor);
g.drawRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
}
The problem is that adding a Highlight to a PdfPage does not cause it to be painted:
for (DatasheetError datasheetError : datasheetErrorList) {
Highlight highlight = createErrorHighlight(datasheetError);
int pageNumber = datasheetError.getPage() - 1;
PdfPage pdfPage = pdfPages[pageNumber];
pdfPage.add(highlight);
pdfPage.repaint() // Does not help here!
pdfPage.invalidate(); // Does not help here!
}
That is why I have a loop over all child Component objects of a PdfPage and call paint(Graphics g) on each inside PdfPage which looks like this:
public class PdfPage extends JComponent {
// ...
#Override
public void paint(Graphics g) {
// Paint the pdf page ..
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
if (pageImage != null) {
int x = 0, y = 0, width = pageImage.getWidth(null), height = pageImage.getHeight(null);
g.drawImage(pageImage, x, y, width, height, null);
}
// Paint all child components such as "Highlight"
for(Component component : this.getComponents()) {
component.paint(g);
}
}
}
Is this how I am supposed to do that or is there a better way to draw child components of that PdfPage? I have tried to call repaint() and invalidate() on each PdfPage but that didn't work.
Why doesn't the pain(Graphics g) method get called from each Highlight? Can I put that in execution somehow?

You are not supposed to paint components directly, that is the job of Swing. Get rid of the code attempting to paint the highlights from the PdfPage class.
When you add components to a container, the child components are painted by the paintChildren(...) method. Read the section from the Swing tutorial on A Closer Look at the Paint Mechanism for more information.
As mentioned in the comment, custom painting is done in the paintComponent() method. In your case you override paint() but don't invoke super.paint() so the paintChildren() method is never invoked so the child components don't get painted.
However when you fix this, a JComponent does not use a layout manager so when you add components to your PdfPage class they will not be painted. So, you need to specify the size and location of the components. In your case is looks like the size/location would be based on the Rectangle.
So fix the code and let the normal Swing painting mechanism work instead of trying to do it yourself.
Or a different approach, if you want to do custom painting, would be to have an addHighlight(...) as a method of your PdfPage class. Then in this case you would keep a list of Rectangles that you want to paint and iterate through this list in the paintComponent() method of the PdfPage clase. Check out theDrawOnComponent example from Custom Painting Approaches for a working example of this approach.

Related

How to prevent shapes in a JFrame from disappearing after resizing the window

public void actionPerformed(ActionEvent e)
{
try
{
//récupérer les coordonnées(x,y) du text area
int x=Integer.parseInt(f.x.getText());
int y=Integer.parseInt(f.y.getText());
int puissance=Integer.parseInt(f.p.getText());
f.APs.add(new AccessPoint (x,y,f.APs.size(),puissance));
String ch="Point d'accés "+String.valueOf(f.APs.size())+" Center xc = "+String.valueOf(x)+" yc= "+String.valueOf(x);
System.out.println(ch);
f.t.add(ch);
Graphics g ;
g= f.getGraphics();
paintComponent(g);
}
catch(Exception e1){System.out.println("Erreur");}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if(f.APs.size()!=0)
{
try {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
int currPoint =f.APs.size()-1;
int puissance =f.APs.get(currPoint).p;
Color C= new Color(128,puissance,puissance,puissance);
Shape circle = new Ellipse2D.Float(f.APs.get(currPoint).x-(f.APs.get(currPoint).diametre/2),
f.APs.get(currPoint).y-(f.APs.get(currPoint).diametre/2),
f.APs.get(currPoint).diametre,f.APs.get(currPoint).diametre);
g2d.draw(circle);
g2d.setPaint(C);
g2d.fill(circle);
}catch(Exception e2){System.out.println("Erreur");}}
}
g= f.getGraphics();
paintComponent(g);
Don't use getGraphics(). Any painting done using that approach will only be temporary (as you have noticed)
Don't invoke paintComponent() directly. Swing will invoke the paintComponent(...) method as required and pass in the proper Graphics object.
The painting method should only ever do painting. It should not change the state of the component.
So if you want to dynamically add shapes to be painted you have two approaches:
Keep an ArrayList of the shapes to be painted. Create a method like addShape(..) to update the ArrayLIst. Then your painting code will iterate through the ArrayList to paint each shape.
Paint directlyl to a BufferedImage. Then paint the BufferedImage.
Working example of both approaches can be found in Custom Painting Approaches

Draw and redraw on a canvas in swing

I know there's no direct replacement for java.awt.Canvas in swing, and I know I'm supposed to use a JPanel and override paintComponent, for example like so:
public void paintComponent(Graphics g) {
g.setColor(Color.black);
g.drawOval(0, 0, 100, 100);
}
And this would draw a black circle on the JPanel when it is created. The problem I have is that I want a dynamic canvas: I want to be able to draw things in response to user input, and redraw continuously, not just once when the application starts. An example would be having a moving object on a canvas, that would need to be redrawn at a rate of say 60 frames per second. How could I achieve this without using AWT components?
EDIT: what I mean is, in an actual canvas, I'd be able to arbitrarily call, say, drawOval anywhere in my code, and that would draw an oval on the canvas; is this doable with JPanel?
Store the information to be drawn (e.g. a Shape or a group of them) and call repaint() from a Swing Timer. Each time the paintComponent(..) method is called, first call the super(..) method to erase the previous drawings, then iterate the list of shapes, move them if necessary, and draw each one.
Here's one way to do it:
public class Renderer extends JComponent implements ActionListener {
private int x;
public Renderer() {
Timer timer = new Timer(1000/60, this);
timer.start();
x = 0;
}
#Override
public void paintComponent(Graphics g) {
super.paint(g);
// drawing code
g.setColor(Color.black);
g.drawOval(x, 0, 100, 100);
}
private void update() {
this.x++;
}
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
}
Now just add this to your component (JPanel or whatever):
comp.add(new Renderer());

Why .paintComponent() is defined on JComponent?

Sorry if my question doesn't adjust to the Stackoverflow requirements due to it is theorical but i don't know where else to ask.
For the past few weeks i've been trying to understand better how the Swing API works and it's components in order to create my own custom components. I've read tons of tutorials, searched in here, i am neck-deep in Java's swing source code and frankly... my mind is a mess.
As far as i understand, the swing components are composed of 3 parts:
the model: where the component state and data are stored
the UI delegate: which paints the component and
the JComponent: it ties everything together.
In this tutorial https://docs.oracle.com/javase/tutorial/uiswing/painting/step2.html there is a paragraph that says:
The paintComponent method is where all of your custom painting takes place. >This method is defined by javax.swing.JComponent and then overridden by your >subclasses to provide their custom behavior
Why is there a paintComponent method on a JComponent? Shouldn't it be an exclusive method of an UI delegate?
Swing is based on AWT. So the initial restriction is based on how AWT paints it components. Because Swing is light weight, when a components root component is painted, the child, Swing, components need to be notified that they need to update as well. This is done by calling the paint methods of all the child components affected by the update (which in turn call the paintComponent methods).
Much of the decisions about the Swing API's paint chain are based around the concept of customisation. It reduces the complexity involved (by discouraging the overriding of paint and focus the functionality down t the paintComponent method).
The Look and Feel API is based on a "delegate" model. This means that the functionality used to perform a said action is "delegated" to some other object. This means that the UI delegate UI doesn't actually know when a component "needs" to be painted, but instead is told, by the component, that it needs to be painted. This makes it much more flexible and in many cases, easier to customise.
Based on your previous question a custom ButtonUI is probably one the better choices, this way you gain much more control over how the button is painted.
Trying to get the buttons to follow the current look and feels color schemes would be very difficult, but you could try having a look at the src.jar which is installed with the JDK, which includes many implementations of look and feels (and if your on Windows you should get the Windows look and feel as well, if your on Mac, then you don't get either Mac or Windows 🙄)
I started by having a look at BasicButtonUI and ButtonUI to get a better understanding of there properties. I pulled some of more interesting methods into a custom ShapeButtonUI...
public class ShapeButtonUI extends BasicButtonUI {
private Shape shape;
public ShapeButtonUI(Shape shape) {
this.shape = shape;
}
protected Color getSelectColor() {
return UIManager.getColor(getPropertyPrefix() + "select");
}
protected Color getDisabledTextColor() {
return UIManager.getColor(getPropertyPrefix()
+ "disabledText");
}
protected Color getFocusColor() {
return UIManager.getColor(getPropertyPrefix() + "focus");
}
#Override
protected void installDefaults(AbstractButton b) {
super.installDefaults(b);
}
#Override
protected void uninstallDefaults(AbstractButton b) {
super.uninstallDefaults(b);
}
#Override
public void paint(Graphics g, JComponent c) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setClip(shape);
Rectangle bounds = shape.getBounds();
LinearGradientPaint lgp = new LinearGradientPaint(
new Point(bounds.x, bounds.y),
new Point(bounds.x, bounds.y + bounds.height),
new float[]{0, 1},
new Color[]{c.getBackground().brighter(), c.getBackground().darker()});
g2d.setPaint(lgp);
g2d.fill(shape);
g2d.dispose();
g2d = (Graphics2D) g.create();
g2d.setColor(c.getForeground());
g2d.draw(shape);
g2d.dispose();
super.paint(g, c);
}
#Override
protected void paintButtonPressed(Graphics g, AbstractButton b) {
super.paintButtonPressed(g, b);
}
#Override
protected void paintFocus(Graphics g, AbstractButton b,
Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {
super.paintFocus(g, b, viewRect, textRect, iconRect);
}
#Override
protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, String text) {
super.paintText(g, b, textRect, text);
// ButtonModel model = b.getModel();
// FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
// int mnemIndex = b.getDisplayedMnemonicIndex();
//
// /* Draw the Text */
// if (model.isEnabled()) {
// /**
// * * paint the text normally
// */
// g.setColor(b.getForeground());
// } else {
// /**
// * * paint the text disabled **
// */
// g.setColor(getDisabledTextColor());
// }
// SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemIndex,
// textRect.x, textRect.y + fm.getAscent());
}
#Override
public Dimension getMinimumSize(JComponent c) {
Rectangle bounds = shape.getBounds();
return new Dimension(bounds.x + bounds.width + 1, bounds.y + bounds.height + 1);
}
#Override
public Dimension getPreferredSize(JComponent c) {
Rectangle bounds = shape.getBounds();
return new Dimension(bounds.x + bounds.width + 1, bounds.y + bounds.height + 1);
}
#Override
public Dimension getMaximumSize(JComponent c) {
Rectangle bounds = shape.getBounds();
return new Dimension(bounds.x + bounds.width + 1, bounds.y + bounds.height + 1);
}
}
Most of these you probably don't need to worry about, but you should at least know they exist as you may want to customise some of the other properties/functionality later.
This delegate is intended to be installed on a button as needed, as apposed to installing it as the default UI delegate for buttons. The reason for this is the need for the shape object. This allows each button to have it's own shape if you want.
You could seed a single shape into the UIManager's properties as well and use that instead, but I've not bothered for this example.
I then created my own shape/path...
public class PointerPath extends Path2D.Double {
public PointerPath() {
moveTo(1, 1);
lineTo(150, 1);
lineTo(198, 100);
lineTo(150, 198);
lineTo(1, 198);
lineTo(50, 100);
closePath();
}
}
And the applied it to a button...
ShapeButtonUI shapeUI = new ShapeButtonUI(new PointerPath());
JButton btn = new JButton("That way");
btn.setUI(shapeUI);
Which finally generated something like...
Now, this is a really basic example, as realistically, the button should be sizing itself around the text (with some additional padding/margin information), but this would require a multi part shape, so we knew which sections could be resized and in what directions, so, complicated.

Where to place the drawing code in a JPanel used as canvas

I've a JPanel where I'm drawing with the mouse events and after resizing, or minimizing-restoring it, it doesnt display what has to be drawn, despite a "degub" println shows me the function is being called, but for some reason, it reamains blank
I thought I should have added my drawing function in the paintComponent function but it seems not to be working right, so what I'm doing wrong or where should I place that drawElements() call?
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawElements();
}
void drawElements() {
Graphics2D g = (Graphics2D)this.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Element el : elements) {
el.draw(g);
}
}
Pass the graphics object to the drawElements method.
(Right answer provided by #rafalopez79)

java.awt.Graphics change color after drawing

I have asked a similar question a while ago here, but didn't get an answer. The original question was about changing the color of a shape after clicking on it. But I am puzzled on how to access the shape at all after it is drawn.
This is my paintComponent method
#Override
protected void paintComponent(Graphics graph) {
super.paintComponent(graph);
Graphics2D g = (Graphics2D) graph;
// smooth graphics
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// moving to the middle of the panel
g.translate(this.getWidth()/2, this.getHeight()/2);
// painting colored arcs
for(int i = 0; i < 4; i++) {
g.setColor(dimColors[i]);
g.fill(arcs[i]);
}
// painting borders
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(5F));
g.drawLine(-98, 0, 98, 0);
g.drawLine(0, -98, 0, 98);
g.draw(circle);
// painting central white circle
g.setColor(Color.WHITE);
g.fill(smallCircle);
g.setColor(Color.BLACK);
g.draw(smallCircle);
}
the arcs[] array contains a bunch of Arc2D's that are drawn on the panel. My question is now, if I want to change the color of, for example arcs[0], how do I do that?
Thanks!
EDIT: I now have this MouseAdapter event
private class MyMouseAdapter extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
Component c = getComponentAt(p);
Graphics g = c.getGraphics();
dimColors[1] = Color.RED;
paintComponent(g);
}
}
And it works, it changes the color of arc[1] because arcs[1] has dimColors[1] set as color when drawing it.
However, I still can't figure out how to check wether the right arc was clicked. Right now you just click anywhere on the graphics panel and it changes the color of that specific arc
This doesn't answer your earlier question, however it does answer your question of click detection. To do this it is best to use Graphics2D because it is a lot easier to write than most other options. Here is an example:
public class GraphicsPanel extends JPanel implements MouseListener
{
private Rectangle2D rect;
First we create our Graphics2D rectangle rect.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)(g);
g2d.setColor(Color.GREEN);
rect = new Rectangle2D.Double(70, 70, 100, 100);
g2d.fill(rect);
this.addMouseListener(this);
}
And then we override the paintComponent method and create our new Rectangle2D.Double object.
We then fill the rectangle with g2d.fill() and then add a mouse listener to the JPanel.
public void mousePressed(MouseEvent e)
{
if(rect.contains(e.getX(), e.getY()))
System.out.println("Rectangle clicked");
}
}
Finally, we need to see if that rectangle contains the point where the user clicked. To do this, simply see if the rectangle we created contains the user's click location by using the Rectangle2D.double's contains(int x, int y) method. That's it!
if I want to change the color of, for example arcs[0], how do I do that?
A line (or whatever) only exists as a bunch of pixels that were painted in the original color. To change its color you must change the current color and draw it again.

Categories

Resources