简体   繁体   中英

Graphics not appearing in Java Jframe Canvas

I am trying to draw a vine within a Jframe however the graphics are not appearing when I run the program. The idea is that a "Vine" is randomly generated and can randomly branch out into other "Vines". If anyone can help that would be much appreciated.

import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;

    public class Vine extends JPanel {
        public void paint(Graphics g, int a, int b, int c)
          {
            super.paint(g);
            g.setColor(Color.BLACK);
            g.drawLine(10, 10, 100, 100);
            grow(g, a, b, c);
          }
        public Boolean TF(){
            Boolean A;
            int B = ((int)Math.random()+1);
            if (B==1){
                A = true;
            } else {
                A = false;
            }
            return A;
        }
        public void grow(Graphics g, int a, int b, int c){
            int x = a;
            int y = b;
            int age = c;
            for (int i=0; i<= age; i++) {
                if (TF()) {
                    if (TF()) {
                        grow(g, x, y, ((age-i)/2));
                    }
                }
                if (TF()){
                    if (TF()){
                        g.drawLine(x, y, (x+1), y);
                        x++;
                    } else {
                        g.drawLine(x, y, (x-1), y);
                        x++;
                    }
                } else {
                    g.drawLine(x, y, x, (y+1));
                    y++;
                }
            }
        }
        public static void main(String[] args)
          {
            JFrame f = new JFrame("Vine");
            f.setBounds(300, 300, 200, 120);
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            Vine panel = new Vine();
            Container c = f.getContentPane();
            panel.setBackground(Color.WHITE);
            c.add(panel);
            f.setResizable(true);
            f.setVisible(true);
          }
    }

There are several issues with this code:

  • Nobody is calling the paint(Graphics g, int a, int b, int c) method. When you inherit from a Swing component like this, there are several methods that are invoked "automatically" (among them a paint(Graphics g) method). But in order to perform custom painting, you should usually override the paintComponent(Graphics g) method .

  • You are generating random numbers while you are painting. This will have some odd effects. The most obvious one: When you resize the frame (or something else happens that causes the frame to be repainted), a new random vine will appear. It will have nothing in common with the previous one, causing a flickering mess, in the best case.

  • In order to create reporoducible results that are still "random", I generally recommend to not use Math.random() . (In fact, I never use this, at all). The java.util.Random class is usually preferable. It allows creating reproducible random number sequences (which is helpful for debugging), and it offers convenient methods like Random#nextBoolean() , which has exactly the effect that you probably wanted to achieve with the (somewhat oddly implemented) TF() method. This leads to the next point...:

  • Use better variable- and method names. Naming variables like the c in your example, or methods TF() , will make the code unreadable. You may call them x and y if they refer to screen coordinates, but the c should probably be called age , and the TF() method ... heck, I don't know how this should be called, maybe something like shouldBranch() ?

  • If you have to perform "extensive" computations (like the random branching, in your example), it is usually better to pull this computation out of the painting process. You can assemble the desired lines and paintings in various ways. For the given example, a simple Path2D should be sufficient.


So far, the technical things. Apart from that: The algorithm itself will not lead to "nice" results. The random branching for each pixel will cause the lines to clob together to a black, fuzzy spot.

In fact, it can be pretty hard to tweak this to create "nice" results. It is hard to exactly say how the "randomness" should influence the overall appearance. The branching should be random, the angles should be random, the branch lengths should be random. Additionally, it will always look a bit odd when all the lines are drawn with the same thickness. The thickness of the lines should probably decrease at each branch, and along the branches in general.

In some practical applications, this generation of random plant-like structures is done with Lindenmayer systems - this may be a starting point for further research.

However, here is a very simple example of how simple lines can be assembled, somewhat randomly, to create something that resembles a plant:

藤蔓

Of course, this looks like cr*p compared to what one could do, given enough time and incentive. But it consists only of a few lines of code that randomly assemble a few branching lines, so this is as good as it gets without considering all the possible improvements.

import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class VinePainting
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }

            private void createAndShowGUI()
            {
                JFrame f = new JFrame("Vine");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                VinePanel panel = new VinePanel();
                Container c = f.getContentPane();
                panel.setBackground(Color.WHITE);
                c.add(panel);
                f.setSize(500, 500);
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }
}


class VinePanel extends JPanel
{
    private static final Random RANDOM = new Random(0);
    private Path2D vine;

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        if (vine == null)
        {
            vine = createVine(getWidth()/2, getHeight());
        }
        g.setColor(Color.BLACK);
        g.draw(vine);
    }

    private Path2D createVine(int x, int y)
    {
        Path2D path = new Path2D.Double();
        double angleRad = Math.toRadians(-90);
        grow(path, x, y, angleRad, 10.0, 0, 30);
        return path;
    }

    private static void grow(Path2D path, 
        double x, double y, double angleRad, 
        double stepSize, int step, int steps)
    {
        if (step == steps)
        {
            return;
        }
        path.moveTo(x, y);

        double dirX = Math.cos(angleRad);
        double dirY = Math.sin(angleRad);
        double distance = random(stepSize, stepSize + stepSize);
        double newX = x + dirX * distance;
        double newY = y + dirY * distance;
        path.lineTo(newX, newY);

        final double angleRadDeltaMin = -Math.PI * 0.2;
        final double angleRadDeltaMax =  Math.PI * 0.2;
        double progress = (double)step / steps;
        double branchProbability = 0.3;
        double branchValue = RANDOM.nextDouble();
        if (branchValue + 0.1 < branchProbability)
        {
            double angleRadDelta = random(angleRadDeltaMin, angleRadDeltaMax);
            double newAngleRad = angleRad + angleRadDelta;
            double newStepSize = (1.0 - progress * 0.1) * stepSize;
            grow(path, newX, newY, newAngleRad, newStepSize, step+1, steps);
        }
        double angleRadDelta = random(angleRadDeltaMin, angleRadDeltaMax);
        double newAngleRad = angleRad + angleRadDelta;
        double newStepSize = (1.0 - progress * 0.1) * stepSize;
        grow(path, newX, newY, newAngleRad, newStepSize, step+1, steps);
    }

    private static double random(double min, double max)
    {
        return min + RANDOM.nextDouble() * (max - min);
    }


}

A side note: This is somewhat similiar to this question . But there, randomness did not play a role, so the recursion is done while painting the tree. (Therefore, it allows playing around with some sliders, to modify the parameters and observe the effects).

There are lots of implementation issues here. You can override the paintComponent of the JPanel to paint in it. Here is a quick fix to demonstrate how this is done. I changed your implementation a lot to fix the issues so you can get an idea.

Note: This was a quick fix. So the code quality is low and some OOP concepts were ignored. Just go through this and understand how this work and implement your own code.

Explanation:

You have to call repaint method of JPanel class to make it repaint itself. It will paint all Lines in the LinkedList to the panel too(refer the implementation). After repainting, create a new random line and add it to the LinkedList. It will be painted next time.

Then we have to animate this. So I implemented runnable interface in the Vine class. run method will be called when we add this vine object(panel) to a Thread and start() the thread. We need to run it forever. So add a loop in the run method. Then repaint the panel every time run method is called. This animation is too fast, so add a Thread.sleep() to slow down the animation.

import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.util.LinkedList;
import javax.swing.JFrame;
import javax.swing.JPanel;

class Line {//Line class to store details of lines

    int x1, y1, x2, y2;

    public Line(int x1, int y1, int x2, int y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }
}

class Vine extends JPanel implements Runnable {//implements runnable to animate it

LinkedList<Line> lines = new LinkedList<>();//LinkedList to store lines

int x = 10;
int y = 10;
Line line;

public Boolean TF() {
    Boolean A;
    int B = (int) (Math.random() * 2 + 1);//logical error fixed
    if (B == 1) {
        A = true;
    } else {
        A = false;
    }
    return A;
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    g.setColor(Color.BLACK);

    for (Line line : lines) {//paint all lines in the LinkedList
        g.drawLine(line.x1, line.y1, line.x2, line.y2);
    }

    //Create and add a next line
    if (TF()) {
        if (TF()) {
            line = new Line(x, y, (x + 1), y);
            lines.add(line);
            x++;
        } else {
            line = new Line(x, y, (x - 1), y);
            lines.add(line);
            x++;
        }
    } else {
        line = new Line(x, y, x, (y + 1));
        lines.add(line);
        y++;
    }
}

private static Vine panel;

public static void main(String[] args) {
    JFrame f = new JFrame("Vine");
    f.setSize(300, 300);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    panel = new Vine();
    Container c = f.getContentPane();
    panel.setBackground(Color.WHITE);
    c.add(panel);
    f.setResizable(true);
    f.setVisible(true);
    panel.start();//start the animation(thread)
}

private void start() {
    Thread thread = new Thread(this);
    thread.start();
}

@Override
public void run() {
    while (true) {

        try {
            Thread.sleep(100);//slow down the animation
            panel.repaint();//then repaint the panel
        } catch (InterruptedException ex) {

        }
    }
}
}

Here's a GUI I created using your code. I didn't understand how your grow method worked, so I pretty much left it alone.

藤蔓

Here's what I did.

  1. Created a GrowVine method to hold the line segments to draw. I also created a LineSegment class to hold one line segment. I did this so that I could keep the model data separate from the view. By separating concerns, I could focus on one part of the problem at a time.

  2. I started the Swing application with a call to the SwingUtilities invoke later method. This ensures that the Swing components are created and used on the Event Dispatch thread .

  3. I created a DrawingPanel from a JPanel, and overrode the paintComponent method. Notice that my override code does nothing but draw. All of the calculations are done in the GrowVine class.

  4. I greatly simplified your TF method and renamed it to coinFlip. coinFlip better indicates to future readers of the code (including yourself) that the boolean should be true half the time and false half the time.

  5. I left your grow method alone. I removed the drawLine methods and had the grow method write line segments to the List.

  6. Once you get your grow method working, you can run the GrowVine class in a separate thread to animate the drawing of the vine.

Here's the code. I hope this helps you.

package com.ggl.testing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Vine implements Runnable {

    @Override
    public void run() {
        JFrame frame = new JFrame("Vine");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        DrawingPanel panel = new DrawingPanel(400, 400);
        frame.add(panel);

        frame.pack();
        frame.setVisible(true);

        new GrowVine(panel, 400, 400).run();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Vine());
    }

    public class DrawingPanel extends JPanel {

        private static final long serialVersionUID = -8460577623396871909L;

        private List<LineSegment> lineSegments;

        public DrawingPanel(int width, int height) {
            this.setPreferredSize(new Dimension(width, height));
            this.lineSegments = new ArrayList<>();
        }

        public void setLineSegments(List<LineSegment> lineSegments) {
            this.lineSegments = lineSegments;
            repaint();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.setColor(Color.BLACK);

            for (int i = 0; i < lineSegments.size(); i++) {
                LineSegment ls = lineSegments.get(i);
                Point s = ls.getStartPoint();
                Point e = ls.getEndPoint();
                g.drawLine(s.x, s.y, e.x, e.y);
            }
        }
    }

    public class GrowVine implements Runnable {

        private int width;
        private int height;

        private DrawingPanel drawingPanel;

        private List<LineSegment> lineSegments;

        public GrowVine(DrawingPanel drawingPanel, int width, int height) {
            this.drawingPanel = drawingPanel;
            this.lineSegments = new ArrayList<>();
            lineSegments.add(new LineSegment(10, 10, width - 10, height - 10));
            this.width = width;
            this.height = height;
        }

        @Override
        public void run() {
            grow(width / 2, height / 2, 200);
            drawingPanel.setLineSegments(lineSegments);
        }

        public void grow(int a, int b, int c) {
            int x = a;
            int y = b;
            int age = c;
            for (int i = 0; i <= age; i++) {
                if (coinFlip()) {
                    if (coinFlip()) {
                        grow(x, y, ((age - i) / 2));
                    }
                }

                if (coinFlip()) {
                    if (coinFlip()) {
                        lineSegments.add(new LineSegment(x, y, (x + 1), y));
                        x++;
                    } else {
                        lineSegments.add(new LineSegment(x, y, (x - 1), y));
                        x++;
                    }
                } else {
                    lineSegments.add(new LineSegment(x, y, x, (y + 1)));
                    y++;
                }
            }
        }

        private boolean coinFlip() {
            return Math.random() < 0.50D;
        }

    }

    public class LineSegment {
        private final Point startPoint;
        private final Point endPoint;

        public LineSegment(int x1, int y1, int x2, int y2) {
            this.startPoint = new Point(x1, y1);
            this.endPoint = new Point(x2, y2);
        }

        public Point getStartPoint() {
            return startPoint;
        }

        public Point getEndPoint() {
            return endPoint;
        }

    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM