Animating a shape in a JPanel - java

first questing for me here. Been searching forever but cant seem to find the answer.
Im working on a school assignment. Got given an ui and are supposed to make the different panels in it do different things in separate threads. Anyway, Im trying to make a triangle rotate inside one of the JPanels. I have managed to draw it and somewhat rotate it, but when I try to make a loop to update it it just blinks and then disappears again. Heres the code Ive written.
StartAssignment, starts the application
public class StartAssignment1 {
public static void main(String[] args) {
Controller controller = new Controller();
}
Controller, recieves calls from the ui and executes various functions in other classes
public class Controller {
private GUIAssignment1 gui = new GUIAssignment1(this);
private RotateShape rotateShape;
private Thread t1;
public Controller() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
gui.Start();
}
});
}
public void startT1(JPanel panel) {
rotateShape = new RotateShape(panel);
t1 = new Thread(rotateShape);
t1.start();
}
public void t1Shutdown() {
rotateShape.shutdown();
}
RotateShape, where Im trying to rotate the damned thing
public class RotateShape implements Runnable {
JPanel panel;
private volatile boolean t1Running = true;
public RotateShape(JPanel panel) {
this.panel = panel;
}
private void draw() {
Graphics2D g2 = (Graphics2D) panel.getGraphics();
g2.rotate(Math.toRadians(10));
g2.drawPolygon(new int[] {50, 100, 150}, new int[] {150, 50, 150}, 3);
}
public void shutdown() {
t1Running = false;
System.out.println("Shutdown");
}
#Override
public void run() {
while (t1Running) {
try {
draw();
Thread.sleep(500);
System.out.println("loop working");
panel.repaint();
panel.revalidate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

I'm not certain because I don't have access to your GUI code and therefore cannot test it myself, but here's an idea:
It looks like you're rotating your Graphics2D object by a fixed amount on every re-render (i.e. every invocation of draw()). It's possible that the internal JPanel code initiates the Graphics2D at a default rotation before every re-render, which may be why your rotation only causes the shape to "somewhat rotate."
Maybe try storing a variable for the radian offset (e.g. double offset;) as a member field of the RotateShapeclass, incrementing it on every re-render, then calling g2.rotate(Math.toRadians(offset));?
Addendum 1:
The reason that it may draw on top of the previous render is because the Graphics2D object is not clearing the canvas between re-renders. One way to fix this is to "clear" the canvas at the beginning of the draw() method by filling a rectangle that covers the whole canvas.
Addendum 2:
When you call panel.repaint(), it triggers the paintComponent() method of the JPanel object. So, by calling repaint() and your own draw() method, you are doing two separate renders, which may cause some graphical errors. If you were able to extends the JPanel class and use that, you should define an override to replace draw():
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// TODO: add your rendering operations here
}
This way, calls to repaint() should trigger this method.

Related

Repaint function messes up the whole frame

I have a problem when trying to draw some elements using paint method in Swing.
As title says, my whole frame collapses and does some weird repeating.
I made a separate JPanel so I can manipulate drawn shapes:
public class PanelPovrsina extends JPanel{
private ArrayList<Oblik> listaOblika;
public PanelPovrsina() {
// svi oblici
this.listaOblika = new ArrayList<Oblik>();
this.listaOblika.add(new Kvadrat(new Tacka(50, 50), 50, "zuta", "crvena"));
this.setBackground(Color.WHITE);
this.setVisible(true);
}
public void paint(Graphics g) {
if(this.listaOblika.isEmpty()) return;
Iterator<Oblik> it = this.listaOblika.iterator();
while(it.hasNext()) {
it.next().crtajUBoji(g);
}
repaint(); // this causes problems!
}
public ArrayList<Oblik> getListaOblika() {
return this.listaOblika;
}
}
Here is the frame with this code:
And here it is without repaint method:
No, I know repaint method is essential in order to dynamically add shapes and actually draw, but I can't make this work correctly.
Also, as you can see from the code above, background of panel is set to white, but my frame would'n render it.
Hope there is enough information to solve my problem, if not, I will add code of my JFrame!
Thank you!
You should never override the paint method, as it handles a number of other things behind the scenes. You should override paintComponent instead.
As #Joe C answered, I should have been using paintComponent method, not paint! Working code:
public class PanelPovrsina extends JPanel{
private ArrayList<Oblik> listaOblika;
public PanelPovrsina() {
// svi oblici
this.listaOblika = new ArrayList<Oblik>();
this.listaOblika.add(new Kvadrat(new Tacka(50, 50), 50, "zuta", "crvena"));
this.setBackground(Color.PINK);
this.setVisible(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Oblik obl : this.listaOblika) {
obl.crtajUBoji(g);
}
repaint();
}
public ArrayList<Oblik> getListaOblika() {
return this.listaOblika;
}
}

Does "Thread.sleep()" do anything else than pause the program for a certain amount of time?

I have created a program that draws a thick line.
import javax.swing.*;
import java.awt.*;
public class Movement {
int xGrid = 50;
public static void main(String[] args) {
Movement m = new Movement();
m.animate();
}
public void animate() {
JFrame frame = new JFrame("Movement");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ScreenDisplay display = new ScreenDisplay();
frame.getContentPane().add(display);
frame.setSize(400, 400);
frame.setVisible(true);
for (int aL = 0; aL < 200; aL++) {
xGrid++;
display.repaint();
try {
Thread.sleep(50);
} catch (Exception ex) { }
}
}
class ScreenDisplay extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.RED);
g.fillOval(xGrid, 175, 50, 50);
}
}
}
Because of the method "Thread.sleep(50)", the speed of the program slows down a little.
So I got a little curious and removed the "sleep()" method.
What I expected to output was the exact same output, just extremely fast.
However, it just prints out one circle in the frame.
I don't really know why it outputs just one circle, none of the researches I've done back up the answer.
Can anyone please explain why?
From Component.repaint documentation:
Repaints this component.
If this component is a lightweight component, this method
causes a call to this component's paint
method as soon as possible. Otherwise, this method causes
a call to this component's update method as soon
as possible.
By the looks of it, your for loop finishes so quickly that by the time the component calls the repaint method, it has already finished and therefore only paints the final circle stored in the buffer.

repaint method of JComponent does not work

I am working on a 2D game and now I have got a problem.
My last goal of the following codes is moving an object in a full screen window.
This is some part of my code:
public class mainTest {
public static void main(String args[])
{
DisplayMode displayMode=new DisplayMode(1024,768,64,75);
GameScreen gameScreen=new GameScreen(displayMode,"background.jpg");
Sprite s=new Sprite("some.png");
gameScreen.addSprite(s);
gameScreen.start();
}
}
I have used two kinds of start() method. One causes NullPointerException and I have commented it and the other does not work:
public class GameScreen extends JComponent{
private Screen screen;
private ArrayList<Sprite> sprites=new ArrayList<Sprite>();
private Image background;
//...(constructor)
public void addSprite(Sprite sprite)
{
sprites.add(sprite);
}
// public void draw()
// {
// Graphics g=screen.getWindow().getGraphics();
// g.drawImage(background,0,0,null);
// for(Sprite i:sprites)
// {
// g.drawImage(i.getImage(),(int)i.getX(),(int)i.getY(),null);
// }
// g.dispose();
// }
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(background,0,0,null);
for(Sprite i:sprites)
{
g.drawImage(i.getImage(),(int)i.getX(),(int)i.getY(),null);
}
g.dispose();
}
public void start()
{
//draw();
repaint();
long currentTime=System.currentTimeMillis();
while(true)
{
long elapsedTime=System.currentTimeMillis()-currentTime;
for(Sprite i:sprites)
{
i.update(elapsedTime);
}
currentTime+=elapsedTime;
//draw();
repaint();
}
}
}
And this is my Sprite class's update() method:
public void update(long elapsedTime)
{
x+=vx*elapsedTime;
y+=vy*elapsedTime;
}
While vx,vy shows velocity.
As you can see, I firstly used method getGraphicsI() to get a Graphics object and called its drawImage() method in my draw() method, but as I said I got NullPointerException.So,
1.What does it mean to getGraphics() of a window,and
2.Why I get Exception?
3.Why should I dispose() Graphics object? Or a better question:Should I dispose() it?
Then I used another way:Making my class extend JComponent, overriding paintComponent() and using repaint() in my start() method.So,
4.Is it suitable to make this class extend JComponent?Does it mean GameScreen class is a place to draw things on?
5.Why my code does not work? It enters the infinite loop, and does not exit.
But why I don't see anything on the screen?
UPDATE:
for more information, this is my Screen class:
public class Screen {
private DisplayMode displayMode;
private JFrame window;
private GraphicsDevice device;
public Screen (DisplayMode dm)
{
window=new JFrame();
device=GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
displayMode=dm;
}
public void setFullScreen()
{
device.setFullScreenWindow(window);
device.setDisplayMode(displayMode);
window.setResizable(false);
window.setUndecorated(true);
window.setBackground(Color.black);
window.setForeground(Color.white);
}
public Window getWindow()
{
return device.getFullScreenWindow();
}
}
UPDATE 2:
Stack trace for NullPointerException:
Exception in thread "main" java.lang.NullPointerException at
GameScreen.draw(GameScreen.java:25) at
GameScreen.start(GameScreen.java:46) at
mainTest.main(mainTest.java:11)
where line 25 of GameScreen class belongs to getGraphics() method.
NOTE:
My problem is not caused by infinite loop, because I changed the program so that the loop ended after two seconds; but my problem did not solve and I saw nothing on the screen.

JFrame and GlassPane - Trouble with repaint()

This question may be a simple matter of me lacking a fundamental understanding of Java Swing or Graphics, and if so, I apologize.
I'm trying to develop a GUI application using Java Swing that can be controlled by an external device that sends pitch, yaw, and roll values via bluetooth to the Application. My idea is to create a cursor (perhaps an empty circle) that moves around when the external device moves around. I have no problems with receiving the data from the device, just the part where I need to actually paint something over all of my components.
I figured that a GlassPane was the easiest way to show a cursor over the entire application, and have it move around when the external device is moved around. I use a Thread to capture the data, and I'm trying to call repaint() afterwards, but it doesn't seem to be triggering.
Here is the relevant code:
JFrame:
public class Frame extends JFrame {
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
//Thread myoHandlerThread = new Thread(myoHandler);
//myoHandlerThread.start();
Frame frame = new Frame();
GlassPane glassPane = new GlassPane();
glassPane.setVisible(true);
frame.setGlassPane(glassPane);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Frame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(50, 50, 1000, 650);
/* Code to add and place components */
}
}
And my GlassPane:
public class GlassPane extends JComponent {
private static double pitch;
private static double yaw;
private static double roll;
Point point;
public void setPoint(Point p) {
this.point = p;
}
public void paintComponent(Graphics g) {
if (point != null) {
System.out.println("Test print statement");
g.setColor(Color.red);
g.fillOval(point.x - 10, point.y - 10, 20, 20);
}
}
public GlassPane() {
Thread handler = new Thread(deviceHandler);
handler.start();
}
private Runnable deviceHandler = new Runnable() {
#Override
public void run() {
Hub hub = new Hub("com.garbage");
System.out.println("Attempting to find device...");
Device externalDevice = hub.waitForDevice(10000);
if (externalDevice == null) {
throw new RuntimeException("Unable to find device!");
}
System.out.println("Connected");
DataCollector dataCollector = new DataCollector();
hub.addListener(dataCollector);
while (true) {
hub.run(1000/20); //gathers data and stores in dataCollector
roll = dataCollector.getRoll();
pitch = dataCollector.getPitch();
yaw = dataCollector.getYaw();
Point p = new Point();
p.setLocation(Math.abs(pitch) * 10, Math.abs(yaw) * 10);
setPoint(p);
repaint();
}
}
};
}
What I would like to happen is for a red circle to be drawn somewhere on the GUI depending on the orientation of the external device. At this point, my "test print statement" doesn't fire even once.
My guess is that I'm lacking some sort of basic understanding of Java's GlassPane or even how paint, paintComponent, and repaint even works. Could anyone point out what I'm doing wrong?
The likely cause of your frustration is trying to set the glass pane visible (Swing components are visible by default), before setting it as the frames GlassPane.
The JFrame is likely resetting the glass pane to be invisible, meaning that it won't be painted (no point painting something that's not visible)
Try setting the glass pane visible AFTER you apply it to the frame

JFrame will not close when "X" button is clicked

The JFrame will not shut down when the default "X" button is clicked. I think this problem has something to do with the main thread not being read, but I don't understand the intricacies of swing or honestly, threads in general. "Window" is an extension of JFrame, "Boxy" drives the program. Program is only in initial stages. Also, I'd like to know how to get the main thread run on every loop-over. Couldn't find anything about this in other questions.
public class Window extends JFrame implements KeyListener{
private static final long serialVersionUID = 1L;
JPanel panel;
public Window(){
super("FileTyper");
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.setSize(200,100);
super.setResizable(false);
panel = new JPanel();
super.getContentPane().add(panel);
super.setFocusable(true);
addKeyListener(this);
super.setVisible(true);
}
public void update(){
}
public void render(Graphics2D g){
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_F9:
break;
case KeyEvent.VK_F10:
break;
}
}
#Override
public void keyTyped(KeyEvent arg0) {
}
}
public class Boxy {
public Window window;
public static void main (String args[]){
start();
}
public Boxy(){
init();
boolean forever = true;
while(forever){
update();
render();
delay();
}
}
private void init(){
window = new Window();
}
private void update(){
window.update();
}
private void render(){
Graphics2D g2 = (Graphics2D) window.getContentPane().getGraphics();
window.render(g2);
g2.fillRect(0, 0, 100, 100);
}
private void delay(){
try {Thread.sleep(20);} catch (InterruptedException ex) {System.out.println("ERROR: Delay compromised");}
}
public static void start(){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Boxy box = new Boxy();
}
});
}
}
I would suggest that you are blocking the Event Dispatching Thread with
while(forever){
update();
render();
delay();
}
This is preventing the Event Queue from processing the event that would close the window.
Start by taking a look at Concurrency in Swing. I would suggest you might like to take a look at something like javax.swing.Timer to start with, but if you want more control of the frame rate, you're going to need to use some kind of Thread. Remember though, Swing expects all updates to be executed from within the context of the Event Dispatching Thread.
Custom painting in Swing is not done by using something like...
Graphics2D g2 = (Graphics2D) window.getContentPane().getGraphics();
The Graphics context is short lived, anything your paint to it (using this method) will be destroyed on the next paint cycle.
Instead, you should use something like a JPanel as the bases for your painting and override it's paintComponent method and render the state from within it, when ever it is called.
You would then simply need to call repaint when you want to update the component.
Take a look at Performing Custom Painting for more details.
I would also recommend that you take a look at How to use Key Bindings as an aletrnative to KeyListener
Your program's "game" loop is incorrect:
while(forever){
update();
render();
delay();
}
Rather than looping the program, it freezes it by tying up the Swing event thread or EDT (for Event Dispatch Thread). You should use a Swing Timer instead for this functionality.

Categories

Resources