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
Related
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.
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.
I have made a class that displays a series of images to create an animation. I would like the animation to take place on a separate thread than the rest of the program. However, when I try to start the animation class, I get an error.
This is some of the animation class (There is more but is is irrelevant):
/**
* Starts a new thread to play the animation on
*/
public void start()
{
playing = true;
animateThread = (new Thread(() -> run()));
animateThread.start();
}
/**
* Will go through sprites and display the images
*/
public void run()
{
int index = 0;
while (playing)
{
if (index > sprites.length)
{
index = 0;
}
try
{
g.drawImage(sprites[index].getImage(), x, y, null);
animateThread.sleep(speed);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
index++;
}
}
I have also attempted at making the animation class Runnable, then making the whole object a new Thread but I received the same error.
This is the class which holds the JFrame and starts the animation (There is more but is is irrelevant):
public static void main(String[] args)
{
AnimationTester tester = new AnimationTester();
tester.frame.setResizable(false);
tester.frame.setTitle("Tester");
tester.frame.add(tester);
tester.frame.pack();
tester.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tester.frame.setLocationRelativeTo(null);
tester.frame.setVisible(true);
//Make the window not have to be clicked on to get input (Set is as the main focus when it begins)
tester.requestFocusInWindow();
//Start the program
tester.start();
}
public void start()
{
createGraphics();
animation.start();
}
public void createGraphics()
{
BufferStrategy bs = getBufferStrategy();
//Checks to see if the BufferStrategy has already been created, it only needs to be created once
if (bs == null)
{
//Always do triple buffering (put 3 in the param)
createBufferStrategy(3);
return;
}
//Links the bufferStrategy and graphics, creating a graphics context
g = bs.getDrawGraphics();
try
{
animation = new Animation(ImageIO.read(getClass().getResource("/SpriteSheet.jpg")), 16, 2, 200, 250, 250, 2.0);
animation.addGraphics(g);
}
catch (IOException e)
{
e.printStackTrace();
}
}
That's not really how BufferStrategy works, instead of using createGraphics, you should be calling it to update the state and render it to the screen
So, in your Thread, it should be updating the state in some way and call some "render" method which would get the next page, render the state and push that state to the screen.
Take a closer look at BufferStrategy and BufferStrategy and BufferCapabilities for more details about how it's suppose to work
For example
I Have written a listener for an IMediaReader from Xuggler.
It should show a video in a JPanel what i can add to a JFrame.
I have created this JFrame in the class main:
class Window extends JFrame {
static IMediaReader reader;
static Window main;
public static void main(String[] args) {
new Thread() {
public void run() {
reader = ToolFactory.makeReader("C:/Users/André/Desktop/Detail.wmv");
reader.addListener(new Player(IMediaViewer.Mode.AUDIO_VIDEO, main));
while (reader.readPacket() == null)
do {} while(false);
}
}.start();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
main = new Window();
}
});
}
private Window() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setVisible(true);
setSize(700, 700);
}
});
}
// invoked by Player with the video panel
public void add(final JPanel videoPanel) {
add(videoPanel, BorderLayout.CENTER);
}
}
It shows the video but it only works when i resize the window manually and that is my problem. Else, it shows a small black square.
Using pack() instead of setSize() or invoke repaint doesn't help.
The Code of class Player isn't just from me. I've just changed some things:
public class Player extends MediaListenerAdapter implements IMediaListener, IMediaViewer {
private static Main main;
Player(Mode mode, Main main) {
setMode(mode);
Player.main = main;
}
#Override
public void onAddStream(IAddStreamEvent event)
{
[...]
MediaFrame frame = new MediaFrame(stream, this, main);
[...]
}
#Override
public void onVideoPicture(IVideoPictureEvent event)
{
MediaFrame frame = mFrames.get(event.getStreamIndex());
frame.setVideoImage(event.getPicture(), event.getImage());
}
static class PositionFrame extends JPanel
{
public PositionFrame(Player viewer, Main main)
{
main.add(this);
mFrames.add(this);
}
protected void adjustSize()
{
invalidate();
}
}
private class MediaFrame extends PositionFrame
{
// the video image
private BufferedImage mImage;
// the video panel
private final JPanel mVideoPanel;
// the stream
private final IStream mStream;
// the index of the stream (incase it's closed)
private final int mStreamIndex;
/**
* Construct a media frame.
*
* #param defaultCloseOperation what should Swing do if the window
* is closed. See the {#link javax.swing.WindowConstants}
* documentation for valid values.
* #param stream the stream which will appear in this frame
* #param viewer containing media viewer
*/
public MediaFrame(IStream stream,
Player viewer, Main main)
{
super(viewer, main);
// get stream and set title based it, establish a copy of the
// stream since it lives in a separate thread
mStream = stream.copyReference();
mStreamIndex = mStream.getIndex();
// the panel which shows the video image
mVideoPanel = new JPanel()
{
public void paint(Graphics graphics)
{
paintPanel((Graphics2D) graphics);
}
};
// add the videoPanel
add(mVideoPanel);
// show the frame
setVisible(true);
}
// set the video image
protected void setVideoImage(IVideoPicture picture, BufferedImage image)
{
[...]
}
protected void paintPanel(Graphics2D graphics)
{
if (mImage != null)
graphics.drawImage(mImage, 0, 0, null);
}
}
}
It is made up from the class MediaViewer
http://code.google.com/p/xuggle/source/browse/trunk/java/xuggle-xuggler/src/com/xuggle/mediatool/MediaViewer.java?r=644
EDIT: If I do it like this, it doesn't work without resizing manually.
I'm not shure if I have unterstood you right.
You have this code in two places.
pack();
setSize(700, 700);
The pack() class is wasteful since you are setting the size immediately afterwards. Also, the first time you call in your constructor you haven't even added anything yet.
Try setting the size or calling pack after you have added the video component and after you have made the frame visible.
Based on your edits, I believe the issue is that you are setting the frame visible before the component is in place. Move your setVisible and pack() calls to the end of your constructor for the Main class instead of the add() method.
Based on more edits to your question: As I mentioned in the comments, make sure you separate off the GUI code in a separate call to invokeLater to get things happening on the EDT. Therefore you need to move your IMediaReader creation and thread starting into your main() method, then after that create a new call to SwingUtilities.invokeLater that creates a new Main, class. By the way, Main is a confusing name for a class.
I am trying to create a program with 2 JPanel using BorderLayout. The center panel is for random drawing of rectangle while the south panel is for the buttons.
I am getting a weird image of the button on the top left corner of the JFrame whenever I hover the mouse cursor over the North or South button. I did some research and found out that this could be the reason for having a transparent background. I tried using super.paintComponent(g) for the panel but the rest of the rectangles drawn earlier disappear. I need the rectangles to stay in the JPanel but not the weird image on the top left.
I don't know what I am doing wrong, hopefully someone can help or give some clue on how to solve this problem.
public class TwoBRandomRec extends JFrame{
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
TwoBRandomRec rec = new TwoBRandomRec();
rec.setSize(500,500);
rec.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
rec.setVisible(true);
}
public TwoBRandomRec() {
//Create the buttons
JButton north = new JButton("North");
JButton south = new JButton("South");
DrawPanel drawPanel = new DrawPanel(500,500);
JPanel southP = new JPanel();
southP.add(south);
southP.add(north);
this.add(drawPanel, BorderLayout.CENTER);
this.add(southP, BorderLayout.SOUTH);
this.setTitle("TwoButtonRandomRec");
this.pack();
}
public class DrawPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Random rand = new Random();
private int x,y,xSize,ySize;
private int height,width;
public DrawPanel(int w,int h) {
width = w;
height = h;
}
public void RandomX(){
x=rand.nextInt(width-1);
xSize=rand.nextInt(width-x);
}
public void RandomY(){
y=rand.nextInt(height-1);
ySize=rand.nextInt(height-y);
}
public Color RandomColour(){
rand.nextInt(height);
return new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255));
}
#Override
protected void paintComponent(Graphics g) {
RandomX();
RandomY();
g.setColor(RandomColour());
g.fillRect(x, y, xSize, ySize);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
repaint();
}
}
}
You're not calling super.paintComponent
protected void paintComponent(Graphics g) {
super.paintComponent(g); // <-- Insert me here and watch problem go away
RandomX();
RandomY();
g.setColor(RandomColour());
g.fillRect(x, y, xSize, ySize);
try {
Thread.sleep(50); // <-- This is an INCREDIBLY bad idea, NEVER, EVER do this
} catch (InterruptedException e) {
}
repaint(); // <-- This is a bad idea, watch CPU max out...
}
You are obligated to call super.paintComponent to ensure that the paint chain is upheld correctly and things like opacity and cleaning up of the graphics context takes place.
The Graphics object is shared between components through a single repaint pass, failure to honor the correct paint chain will result in, well, problems like yours
Never update the UI in anyway from any paint method (this includes calling repaint), this is just causing your paint method to be recalled, over and over and over...until you CPU hits 100% and program hangs.
Never, EVER do any time consuming or block operations within the paint methods (or the UI generally), this will make it look like the program as hung and make the users upset (you think zombi hordes are bad :P). Blocking in this way prevents the EDT from responding to paint requests...
I'd recommend having a read through:
Performing Custom Painting
2D Graphics
Painting in AWT and Swing
Concurrency in Swing