JSlider changeListener won't update - Java - java

I have a fractal tree generator and I am trying to make a slider control the number of iterations, but I can not get it to work. Also, the layout gets messed up whenever the repaint() method is called. Any thoughts on how to fix this?
public class FractalTree extends JPanel implements ChangeListener {
static JSlider slider = new JSlider(0,12);
static int slideVal=7;
public FractalTree()
{
super();
slider.addChangeListener(this);
}
public void paint(Graphics g)
{
g.setColor(Color.green);
drawTree(g, 400, 750, 200, Math.toRadians(-90), Math.toRadians(45), slideVal); //Don't let # of iterations exceed 12, it is useless
}
private void drawTree(Graphics g, int x1, int y1, double l, double t, double dt, double iterations) {
if (iterations > 0) {
int x2 = x1 + (int) (l * Math.cos(t));
int y2 = y1 + (int) (l * Math.sin(t));
g.drawLine(x1, y1, x2, y2);
drawTree(g, x2, y2, l / 1.5, t + dt, Math.PI / 4, iterations - .5);
drawTree(g, x2, y2, l / 1.5, t - dt, Math.PI / 4, iterations - .5);
}
}
#Override
public void stateChanged(ChangeEvent e) {
slideVal=slider.getValue();
repaint();
}
public static void main(String[] args) {
JFrame t = new JFrame("Some swaggy fractal shit");
FractalTree tree = new FractalTree();
slider.setValue(slideVal);
slider.setMinorTickSpacing(1);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
tree.add(slider);
t.add(tree);
t.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
t.setResizable(false);
t.setLocationByPlatform(true);
t.setSize(800, 800);
t.setBackground(Color.black);
t.setVisible(true);
}
}

Two main problems:
You're overriding paint instead of paintComponent.
You're not calling super.paintComponent(g) (or in your case, super.paint(g)) as the first thing in your overridden method.
This is what you need to have:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.green);
drawTree(g, 400, 750, 200, Math.toRadians(-90), Math.toRadians(45), slideVal);
}
Other things to consider:
Add the slider to the frame on position BorderLayout.PAGE_START instead of to the panel. If you add it to the panel, you risk drawing where the slider is.
Set the background color on the panel and not on the frame.
No need to call super() in the constructor, it's automatic.
setResizable(false) on the frame should generally be avoided. No need to restrict the user's space.
Call pack() on the frame instead of setSize(...). The latter is too dependent on the local graphics configuration.
You will need to override the panel's getPreferredSize method to return the correct size for the drawing.
Your pixel calculation should be adjusted to that the tree is aligned to the upper left corner of the panel, and not start it from an arbitrary location on the bottom, which causes you to lose a lot of screen real estate:
Response to comments
Why paintComponent should be used?
See these:
paintComponent() vs paint() and JPanel vs Canvas in a paintbrush-type GUI
Difference between paint() and paintcomponent()?
public void paint(Graphics g)
This method actually delegates the work of painting to three protected methods: paintComponent, paintBorder, and paintChildren. They're called in the order listed to ensure that children appear on top of component itself. [...] A subclass that just wants to specialize the UI (look and feel) delegate's paint method should just override paintComponent.
You saw that if you override paint and added the slider to the panel, you got issues with the slider painting because you ignored paintChildren.
What calling the superclass constructor does?
Best to answer is the JLS:
JLS 8.8.7. Constructor Body
If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of its direct superclass that takes no arguments.
So calling super() does nothing.

Related

Inaccurate Graphics2D.draw(Shape) method

I make a game and when I animate entity with low velocity I use g.FILL(new Ellipse2D.Float(x, y, r, r)); because it renders smooth and fluent motion.
It work fine (in Example blue left circle). But when I needed just an outline of circle I used g.DRAW(new Ellipse2D.Float(x, y, w, h)); and it didn't work and I absolutely don't know what's wrong. No fluent motion, circle jumps pixel over pixel and it looks ugly (in Example red right circle). Graphics2D.draw(Shape) count float values like int values.
This Example code demonstrate it, don't study it a lot, just import, run and watch.
public class Example extends JFrame {
public static void main(String[] args) { new Example(); }
public Example() {
setBounds(50, 50, 400, 400);
setVisible(true);
while(true) {
x1 += 0.01;
y1 += 0.01;
x2 -= 0.01;
y2 += 0.01;
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
repaint();
}
}
double x1 = 50 , y1 = 50;
double x2 = 250, y2 = 50;
#Override
public void paint(Graphics gg) {
Graphics2D g = (Graphics2D) gg;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLUE);
g.fill(new Ellipse2D.Double(x1, y1, 100, 100));
g.setColor(Color.RED);
g.draw(new Ellipse2D.Double(x2, y2, 100, 100));
}
}
So how can I fix it without tricks like filling two cirle or moving image of circle? Thanks for every answer.
I would guess the problem is that you didn't invoke super.paint(...) as the first statement in the method which means you lose some of the default painting functionality.
However, that is NOT the proper solution as you should NOT be overriding the paint() method of a JFrame at all. Custom painting is done by overriding the `paintComponent(...) method of a JPanel and then you add the panel to the frame. Read the section from the Swing tutorial on Custom Painting for more information and working examples.
Also, don't use a while true loop for animation. Instead you should be using a Swing Timer to schedule the animation (the tutorial has a section on Swing Timers). Your code only works because you are NOT creating the GUI correctly. The GUI should be created on the Event Dispatch Thread. The Custom Painting tutorial shows you how to use the invokeLater(...) method to do this. The tutorial also has a section on Concurrency in Swing which explains this concept in more detail.
With drawing circles/ovals/ellipses in Java, the "pure" mode often helps drawing it with subpixel accuracy:
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE)
My other answer has more details: https://stackoverflow.com/a/31221395/1143274

what calls paintComponent()?

I am attempting to draw sprites out of a sprite sheet.
I have the following class
public class GTComponent extends JComponent {
Graphics2D g2;
#Override
public void paintComponent(Graphics g){
g2 = (Graphics2D)g;
}
public void drawSpriteFrame(Image source, int x, int y, int frame) {
int frameX = (frame % 12) * 32;
int frameY = (frame / 12) * 32;
g2.drawImage(source, x, y, x + 32, y + 32,
frameX, frameY, frameX + 32, frameY + 32, this);
}
}
That is created as an object in the main class as so
JFrame f = new JFrame();
GTComponent img = new GTComponent();
f.add(img);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize((int)(i.length * 8.1), (int)(i[0].length * 8.5));
f.setVisible(true);
f.setLocationRelativeTo(null);
BufferedImage test = null;
try {
test = ImageIO.read(new File( /*Image File path*/));
}
catch (IOException e) {
System.out.println("error");
System.exit(0);
}
img.drawSpriteFrame(test, (u * 32 + 1), (z * 32 + 1), c);
The problem im facing is that the following error gets thrown
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
After doing several debugs, setting breakpoints at paintComponent and drawSpriteFrame, i found out that the drawSpriteFrame method gets called before the paintComponent method, thus meaning that g2 = null resulting in that error being thrown.
The question here is what triggers the paintComponent method which allows me to initialise the g2 variable?
You seem to have a broad misconception how drawing in Swing works.
You do not call any rendering methods when you want to. You perform rendering when Swing demands it. When Swing calls paintComponent() thats where you perform all your rendering. The graphics passed to paintComponent should be treated as valid only while you're still in the paintComponent method. What happens to it after the method exits is Swings buisness.
You might want to consult the tutorial on Swing custom painting http://docs.oracle.com/javase/tutorial/uiswing/painting/index.html for example code.
paintComponent() is called automatically from the event dispatch thread. If you want your custom component to be painted as part of the ordinary Swing painting process, you should override paintComponent() to call your drawSpriteFrame() method, not call drawSpriteFrame() directly.
If you want to control the drawing operation yourself, you need to use "active rendering" as described in the Full-Screen Exclusive Mode tutorial -- note that the technique described there also works for windowed applications. Basically you need to ask the window for a Graphics instance (instead of waiting for one to be passed into paintComponent() and then draw to that.
A basic example using double buffering:
// Initial setup
Frame mainFrame = new Frame();
mainFrame.setVisible(true); // you'll also want to set size, location, etc.
mainFrame.createBufferStrategy(2);
BufferStrategy bufferStrategy = mainFrame.getBufferStrategy();
//....
// Inside your draw loop (call once for each frame)
Graphics2D g2 = (Graphics2D) bufferStrategy.getDrawGraphics();
g2.drawImage(...) // etc.
g2.dispose();
bufferStrategy.show();

JPanel Exact drawing size/Limit?

When drawing on my extended JPanel I want the size to be variable. I've worked out the code to always outline a 1 pixel margin. My code works but I don't understand why I must adjust the height and width and height of the dimension by -1 when drawing the rectangle.
If I draw a rectangle that has 1,1 as a size it draws a single pixel square so shouldn't drawing the width and the height work without the -1 modifier.
If any one can explain why there is a discrepancy between the size of my extended JPanel or explain a better way to get the exact dimensions of the drawing area I would appreciate it.
public class Engine extends JPanel {
Engine(){
}
#Override
public void paintComponent(Graphics g){
Dimension a = this.getSize();
g.setColor(Color.RED);
g.drawRect(0, 0, a.width-1, a.height-1);
}
}
Remember, most values are 0 based. But instead of having to say, " I want the width of my panel to be 199" so you get a panel that is 200 pixels wide (0-199), Swing allows you to specify 1 based values and makes adjustments internally
If you create a rectangle of 1, 1, 1, 1, your actually creating a rectangle at position 1x1, whose width & height is 1 pixel (1 + 1 would be 2)
"or explain a better way to get the exact dimensions of the drawing area I would appreciate it."
Override getPreferredSize() in your JPanel class
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
When you you paint, make use of getWidth() and getHeight(), inherited from the JPanel class. Using these methods, your painting will resize dynamically with the resizing of the panel.
int width = getWidth();
int height = getHeight();
g.fillRect((int)(width * 0.9), (int)(height * 0.9),
(int)(width * 0.8), (int)(height * 0.8));
Side Note
You should call super.paintComponent inside the paintComponent method so as not to break the paint chain.
paintComponent should be protected not public
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
I've worked out the code to always outline a 1 pixel margin.
Don't do custom painting. Use a Swing Border. See How to Use Borders. You probably want a LineBorder.

Java repaint issue-seeing ovals each move

Heres part of my simple code.I want to achieve moving oval to cursors X axis location after clicking left button.Problem is that I can see only last position of oval (when it already stops).I think repaint method in while block doesnt work as I would like.I would like to see each move of oval as its getting into position of cursor.Thank you for suggestions.
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
public class Testfile extends JPanel implements Runnable,MouseListener{
public static JFrame frame;
public int x;
public int y;
public int pointX;
public int pointY;
public void paintComponent(Graphics g){
super.paintComponent(g);
g.fillOval(x, y, 20, 20);
}
public static void main(String args[])throws InterruptedException{
Testfile z=new Testfile();
z.setBackground(Color.cyan);
frame=new JFrame("Test");
frame.setSize(500,500);
frame.add(z);
frame.addMouseListener(z);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent e){
pointX=(int)MouseInfo.getPointerInfo().getLocation().getX();
pointY=(int)MouseInfo.getPointerInfo().getLocation().getY();
try{
while(x!=pointX){
x=x+1;
Thread.sleep(10);
repaint();
}
}
catch(InterruptedException v){System.out.println(v);}
}
.I think repaint method in while block doesnt work as I would like
Your problem has nothing to do with repaint "not working" and all to do with your tying up the Swing event thread. If you run a long-running process on the Swing Event Dispatch Thread (or EDT), the thread responsible for painting the GUI and interacting with the user, your GUI freezes and won't paint itself or respond until the EDT is released.
Solution: don't use a while (true) loop or Thread.sleep(...) on the Swing event thread. Also:
Use a Swing Timer instead to act as your animation "loop".
Another possible solution is to use a background thread to do the Thread.sleep(...), but in my opinion this is not worth the trouble since a Swing Timer will work so well and is easier to implement correctly.
Also:
Don't add your MouseListener to the JFrame but rather to your drawing JPanel. Otherwise you'll find that you'll be off in the y direction by the height of the title bar.
Use the mousePressed(...) method not mouseClicked(...) since the former is more forgiving.
Get the deltaX and deltaY on mousePressed, the direction that the circle should go by subtracting x from pointX and y from pointY.
I've gotten your code to work by checking the Manhattan distance between x and y and pointX (manHattanDistance = Math.abs(x - pointX) + Math.abs(y - pointY);) and pointY, and stopping the timer if it gets below a minimum. I've also saved the prior Manhattan distance and have checked the differences between the old and new one to make sure that the oval doesn't over-shoot, kind of as a fail-safe.
Use doubles to hold your x, y, pointX, pointY, etc, and cast to int when drawing.
Don't forget to cast your Graphics object to a Graphics2D and use RenderingHints to turn antialiasing on. This will make for prettier graphics.
Avoid "magic" numbers. Use constants instead.
Consider using x and y for the center of your circle rather than the left upper corner.
For example, my paintComponent(...) method could look like:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// RADIUS is an int const and = 10
g.fillOval((int) x - RADIUS, (int) y - RADIUS, 2 * RADIUS, 2 * RADIUS);
}
As above. you need to do these things on separate threads.
Sometimes you will need to decrement x so check if it's already bigger than the the point
clicked, or the program will keep incrementing it indefinitely. Also you'll probably want to do the same with y

Elements not drawing when paint() is directly invoked, but drawing when added to content pane

A Graph object is being added to a JFrame. This object draws axes followed by a graph plot. When the object's paint() is invoked implicitly through the JFrame using:
this.getContentPane().add(new Graph());
both the axes and the function draw. However, when the paint() method is explicitly invoked, via:
Graph g = new Graph();
g.paint(this.getContentPane().getGraphics());
the axes do not draw, however the function does. The full constructor for the JFrame is as follows:
public GraphFrame() {
super("");
setSize(800, 800);
setVisible(true);
//One of the above blocks is called here
}
The function paint in object Graph is as follows:
public void paint(Graphics w) {
w.setColor(Color.WHITE);
w.fillRect(0, 0, 800, 800); //Clears the screen
w.setColor(Color.BLACK);
w.drawLine(100, 0, 100, 800);
w.drawLine(0, 700, 800, 700); //(Should) Draw the axes
for(int i = 1; i < 650; i++) {
//Draws the function
//This is just a repeated drawLine call.
}
}
Why would the axes draw when implicitly called when components paint, but not draw when explicitly invoked? Remember that the function draws (the block in the for loop), while the axes preceding the for loop do not.
Don't call paint directly on a component. Also for custom painting in Swing use paintComponent rather than paint and remember to call super.paintComponent(g). Also getGraphics returns a transient Graphics reference so should not be used for custom painting. In contrast the Graphics reference in paint (and paintComponent) is always initialized correctly and will display graphical output as expected.
Performing Custom Painting
Painting in AWT and Swing

Categories