Edited with CMilbys suggestion, However i'm getting an error when adding "replayData to the frame.
Here is the replayData class + paint method
public class ReplayData extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
private ArrayList<Point> points;
// Create new list of points when ready then call Redraw()
public void ReplaceData() {
points = new ArrayList<Point>();
}
public void addPoint(Point point) {
points.add(point);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Point p : points)
g.fillRect(p.x, p.y, 2, 2);
}
public void draw() {
repaint();
}
}
And here is where I try to call it to print all of the records that have been retrieved from the csv
JButton button_KMeans = new JButton("View K-Means");
button_KMeans.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
kMeans.initialize();
kMeans.kMeanCluster();
//for (Point p : kMeans.getPoints() )
// Will this be very slow? Data sets are going to be large
Point temp = new Point();
for (int i = 0; i < kMeans.TOTAL_DATA; i++)
{
temp.x = (int)TrackerData.getRecordNumber(i).getEyeX();
temp.y = (int)TrackerData.getRecordNumber(i).getEyeY();
replayData.addPoint(temp); // Add points to JPanel
}
replayData.draw();
// How could I make it so this data shows over like 5 seconds, or over 30 etc?
}
});
I'm getting errors when adding the instance of ReplayData to the frame
at javax.swing.JFrame.addImpl(Unknown Source)
at java.awt.Container.add(Unknown Source)
private ReplayData replayData;
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 1920, 1080);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
frame.add(replayData); // if I comment this line the program starts fine
In your paintComponent method you should call super.paintComponent. That was correct so uncomment that.
Secondly, your ReplayData class extends JPanel. Because of this you need to use JFrame's add method and add an instance of the class to the JFrame. This will only allow you to have 1 point though. So I recommend you re-structure your class to have an array of points and not two integer variables which it what it appears to be. So for example
class ReplayDate extends JPanel {
private List<Point> points;
public ReplaceData() {
points = new ArrayList<Point>();
}
public void addPoint(Point point) {
points.add(point);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Point p : points)
g.fillRect(point.x, point.y, 2, 2);
}
public void draw() {
repaint();
}
}
// In your Main class
private ReplayData replayData;
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 1920, 1080);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
frame.add(replayData); // Add replay data to jframe
JButton button_KMeans = new JButton("View K-Means");
button_KMeans.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
kMeans.initialize();
kMeans.kMeanCluster();
for (Point p : kMeans.getPoints()
replayData.addPoint(p); // Add points to JPanel
replayData.draw();
}
}
This will allow you to add any number of points and draw them all. I hope this answered your question. If you are still confused just leave a comment and I will try to explain a little more.
EDIT: To help with a few more problems...
First: When setting the size of a JFrame, yours seems to work since you said nothing about it, however, I've never actually used that method. For future reference it can also be done like this.
jframe.getContentPane().setPreferredSize(new Dimension(width, height));
As for when you add a ReplayData instance to your JFrame, I'm not sure about that one... I copied your code into a compiler and it worked fine for me. Post more code or send me your project and I can take a deeper look.
Lastly, you're worried about speed. How large are your datasets? This also depends on your computer. My computer has a 2.4 GHz Intel Core i5. Since 1 hertz is '1/second', assuming I did this math right, and in an ideal world it could do 2.4 billion operations per second. This obviously isn't the actual case but my point is even with datasets of ~10,000, you will probably notice a small delay, but it will only be seconds.
Related
I have a JPanel that I draw upon. The shapes I draw are some objects stored in a list. I would like to register MouseOvers over these drawn objects. What I am currently doing is adding a MouseMotionListener that checks the list of objects for a hit every time the mouse moves. This is of course pretty inefficient once there are a lot of objects. Is there a better way to check for MouseOvers than just checking all the objects every time the mouse moves?
Here is a minimal example:
class Ui.java:
public class Ui {
private static JFrame frame;
private static DrawPanel drawPanel;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { createGui();
});
}
private static void createGui() {
frame = new JFrame("Test");
drawPanel = new DrawPanel();
frame.setContentPane(drawPanel);
frame.pack();
frame.setVisible(true);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
}
class SomeObject.java:
public class SomeObject {
//class that represents some object that will be drawn. note that this is
//just a minmal example and that in my actual application, there are
//other aspects and functions to this class that have nothing to do with drawing.
//This is just kept small for the sake being a minimal example
private String id;
private Point2D origin;
private int length;
private int height;
public SomeObject(String id, Point2D origin, int length, int height) {
this.id = id;
this.origin = origin;
this.length = length;
this.height = height;
}
public String getId() {
return id;
}
public Point2D getOrigin() {
return origin;
}
public int getLength() {
return length;
}
public int getHeight() {
return height;
}
}
class CustomMouseMotionListener.java:
public class CustomMouseMotionListener implements java.awt.event.MouseMotionListener {
public CustomMouseMotionListener() { }
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
for (SomeObject object: DrawPanel.objectsToDraw) {
Shape s = new Rectangle((int)object.getOrigin().getX(), (int)object.getOrigin().getY(), object.getLength(), object.getHeight());
if (s.contains(e.getPoint())) {
System.out.println("hit: " + object.getId());
}
}
}
}
class DrawPanel.java:
public class DrawPanel extends JPanel {
public static List<SomeObject> objectsToDraw = new ArrayList<>();
public DrawPanel() {
objectsToDraw.add( new SomeObject("a", new Point2D.Float(20,1), 20,20));
objectsToDraw.add( new SomeObject("b", new Point2D.Float(20,45), 20,20));
addMouseMotionListener(new CustomMouseMotionListener());
setFocusable(true);
setVisible(true);
grabFocus();
}
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
for (SomeObject object: objectsToDraw) {
g.drawRect((int)object.getOrigin().getX(), (int)object.getOrigin().getY(), object.getLength(), object.getHeight());
}
}
}
Instead of using your SomeObject class, i recommend using Shape or Area. The whole purpose of SomeObject seems to be to be turned into a Shape anyway, right? Not only that, but with an ArrayList of Shapes, you can eliminate creating rectangles for your shapes in every mouseMove.
BTW, years ago I put together a package for treating Areas like 1st class Components. You can see this here: https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/ui/shape/
(Start with AreaManager and AreaModel). Area has some advantages and disadvantages: Advantages: Easy to manage as a group, fairly easy to test if they are overlapping. Disadvantage: You lose the information about how the Area was constructed (ex. Polygon points, circle radius, etc)
You've already taken this a long way, so kudos to you. This answer is (a) responding to your question about efficiency, but also pointing you in some ways you could choose to go
There is more to SomeObject than just being drawn, so I think I can't just replace it with Shape,
Then you keep a Rectangle instance as part of your SomeObject class, instead of your Point and length/height variables.
Then you modify your methods to return the values from the Rectangle.
This will prevent you from continually creating new Rectangle instances, make the process more memory efficient and reducing garbage collection.
But, you should also be able to extend the Rectangle class and add your extra functionality in the same way that you extend JPanel to add custom painting logic.
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 5 years ago.
I need to set up a standard smiley face GUI where you can wink, blink, smile, and frown with JButtons. I need to do this with three separate classes: a class that draws the smiley face, a class with all my buttons and actionListeners that control the smiley face, and a class with the applet.
I keep getting NPE's on my buttons in the button class. I can't figure out why. Please go easy on me, I'm new to Java.
Here's my controls class:
public class SmileyControls extends JPanel implements ActionListener {
Smiley smiley;
JPanel controlPanel, eyePanel;
JButton open, wink, shut; // make these an animation???? see loop chapter in text
public SmileyControls(Smiley smileControl) {
smiley = smileControl;
controlLayout();
}
public void controlLayout() {
eyePanel = new JPanel(new FlowLayout());
open = new JButton("Open");
wink = new JButton("Wink");
shut = new JButton("Shut");
open.addActionListener(this);
wink.addActionListener(this);
shut.addActionListener(this);
eyePanel.add(open);
eyePanel.add(wink);
eyePanel.add(shut);
add(eyePanel);
}
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==open){
smiley.setEyeCondition(0); // this calls the method setEyeCondition() from the smiley class that I created. I'm getting my NPE's here
}
if(e.getSource()==wink){
smiley.setEyeCondition(1); // and here
}
if(e.getSource()==shut){
smiley.setEyeCondition(2); // and here
}
}
}
Here's my smiley class:
public class Smiley extends JPanel {
int locX, locY, height, width;
Color moleColor;
int eyeCondition;
Graphics2D g2d;
public Smiley(int x, int y, int w, int h) {
locX = x;
locY = y + 100; // needed to add 100 pixels to make room for hair
height = h;
width = w;
moleColor = new Color(84,60,37);
eyeCondition = 0;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g2d= (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
setHair();
setFace();
setEyes(); // these methods paint the face
setMole();
setMouth();
}
// CONTROL METHODS
public void setEyeCondition(int eye) {
// the int values here are taken from the smileyControls class
// I think they'd repaint my applet if it weren't for the NPE's
if(eye == 0) {
// draw eyes open
g2d.fillOval(locX+width/5, locY+height/5,width/5, height/5); // left eye
g2d.fillOval(locX+3*width/5, locY+height/5, width/5, height/5); // right eye
repaint();
} else if(eye == 1) {
// draw wink
g2d.fillRect(locX+width/5, locY+height/5,width/2, height/20); // left eye winking
g2d.fillOval(locX+3*width/5, locY+height/5, width/5, height/5); // right eye open
repaint();
} else if(eye == 2) {
// draw blink
g2d.fillRect(locX+width/5, locY+height/5,width/2, height/20); // left eye blinking
g2d.fillRect(locX+3*width/5, locY+height/5,width/2, height/20); // right eye blinking
repaint();
}
}
public void setEyes() { // this method paints the original eyes
g2d.setColor(Color.black);
g2d.fillOval(locX+width/5, locY+height/5,width/5, height/5); // left eye
g2d.fillOval(locX+3*width/5, locY+height/5, width/5, height/5); // right eye
}
}
And here's my applet:
public class SmileyApplet extends JApplet {
Smiley smiley1;
SmileyControls control1;
JPanel container, smileyAndControls1, smileyAndControls2, smileyAndControls3;
BorderLayout border;
public void init() {
border = new BorderLayout();
setLayout(border);
setUpContainer();
}
public void setUpContainer() {
container = new JPanel(new FlowLayout());
smileyAndControls1 = new JPanel(new FlowLayout());
setUpControl();
setUpSmiley();
smiley1.setPreferredSize(new Dimension(450, 600));
smileyAndControls1.add(control1);
smileyAndControls1.add(smiley1);
container.add(smileyAndControls1); // add more controls to master container here
add(container, BorderLayout.CENTER);
}
public void setUpSmiley() {
smiley1 = new Smiley(0, 0, 400, 400);
add(smiley1);
}
public void setUpControl() {
control1 = new SmileyControls(smiley1);
}
}
At first you call setUpControl(), in there you create your SmileyControls and pass smiley1 to it (which is null at that time).
After that you call setUpSmiley() which creates the instance of Smiley.
So you probably only have to call setUpSmiley() before you call setUpControl() and your problem should be solved.
Try changing the order of these lines, as you use your smiley1 variable before it gets its value:
setUpControl(); // This uses smiley1
setUpSmiley(); // This instantiates smiley1
EDIT
You should move the drawing to the paint*() methods, or methods called directly from them.
That is, your setEyeCondition() method should set a property on the smiley, and the drawing should go into your setEyes()method.
I am a little confused about how extending JPanel to make a canvas style object works in Swing, what would I do if I wanted to create a new image or text after I've made the new object? Since I assume that's when the paintComponent is called.
In addition to the excellent advice from ControlAltDel (see step 3 of the tutorial for information on how to programatically repaint) and Andrew Thompson, this could be useful as a very simple example to help you get started:
public class Main {
public static void main(final String[] args) throws InterruptedException {
final JFrame frame = new JFrame("Swing canvas");
frame.setBounds(100, 100, 640, 480);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
final CanvasPanel canvasPanel = new CanvasPanel();
frame.getContentPane().add(canvasPanel);
frame.setVisible(true);
final List<String> words = Arrays.asList("one", "ein", "une", "uno", "jeden", "een");
for (final String word : words) {
canvasPanel.addWord(word);
// Dirty way to simulate some heavy translation work...
Thread.sleep(246);
}
}
}
class CanvasPanel extends JPanel {
private final List<String> words = new ArrayList<>();
public void addWord(final String word) {
words.add(word);
repaint();
}
#Override
protected void paintComponent(final Graphics graphics) {
super.paintComponent(graphics);
for (int wordIndex = 0; wordIndex < words.size(); wordIndex++)
graphics.drawString(words.get(wordIndex), 42, 64 + 28 * wordIndex);
}
}
In this example, the canvas panel calls the repaint method on itself, but it can also be called from the outside. Good luck with your project!
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
I am working to develop a GUI in which I paint some 2D shapes repeatedly on different locations. Currently I am having a method createGUI() that creates the basic layout and the panels and then I call the constructor for the content_panel to create 2D shapes in the content_panel.
But, I want to use another method to create the shapes in the main JPanel. Is there a way in Java, so that I can have two method calls in main. First method createGUI() creates the GUI including JFrames and JPanel. While the second method createShapes() creates shapes in the one specific JPanel - content_panel. I would like to call this createShapes() method repeatedly and pass different arguments to see shapes at different locations.
Please let me know if you need some more info or the question is unclear. Thanks
Code:
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class Board{
public static void main(String[] args)
{
createGUI();
drawShapes();
}
//This method creates the basic GUI
private static void createGUI()
{
//Creating the JFrame main window
JFrame mainFrame = new JFrame();
mainFrame.setSize(800, 500);
mainFrame.setTitle("Particle Filter");
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setLocation(100, 100);
mainFrame.getContentPane().setLayout(new BoxLayout(mainFrame.getContentPane(), BoxLayout.X_AXIS));
//creates two panels content and sidebar. Sidebar has null layout
JPanel content = new JPanel();
content.setPreferredSize(new Dimension(700,500));
content.setBackground(Color.LIGHT_GRAY);
mainFrame.getContentPane().add(content);
JPanel sidebar = new JPanel();
sidebar.setBackground(Color.LIGHT_GRAY);
sidebar.setPreferredSize(new Dimension(100,500));
mainFrame.getContentPane().add(sidebar);
sidebar.setLayout(null);
//creates three buttons in sidebar
JButton start_button = new JButton("START");
start_button.setBounds(10, 75, 77, 23);
sidebar.add(start_button);
JButton stop_button = new JButton("STOP");
stop_button.setBounds(10, 109, 77, 23);
sidebar.add(stop_button);
JButton reset_button = new JButton("RESET");
reset_button.setBounds(10, 381, 77, 23);
sidebar.add(reset_button);
//calls the content_Walls class and sends the number of ovals to be generated
int n=1000; // n denotes the number of ovals
content.add( new Content_Walls(n));
mainFrame.setVisible(true);
}
private static void drawShapes()
{
}
}
class Content_Walls extends JPanel
{
ArrayList<Integer> list;
Content_Walls(int n)
{
setPreferredSize(new Dimension(680,450));
setBackground(Color.WHITE);
list = new ArrayList<Integer>(Collections.nCopies(n, 0));
}
public void paintComponent(Graphics g)
{
int x=0,y=0;
super.paintComponent(g);
createObstacles(g,150,225,100,40);
createObstacles(g,500,300,40,100);
for(int i=0;i<list.size();i++)
{
x=randomInteger(11,670); // bounds of x between which the particles should be generated
y=randomInteger(11,440); // bounds of y between which the particles should be generated
int radius = 4;
x=x-(radius/2);
y=y-(radius/2);
g.fillOval(x, y, radius, radius);
}
private void createObstacles(Graphics g, int x, int y, int width, int height)
{
g.setColor(Color.BLACK);
g.fillRect(x, y, width, height);
}
private static int randomInteger(int min, int max)
{
Random rand = new Random();
int randomNum = rand.nextInt((max - min) + 1) + min;
return randomNum;
}
}
There are all sorts of problems with your code.
You're creating Swing components on the main thread instead of the Event Dispatch Thread. Search for help on this.
Have Board subclass JFrame and do the GUI initialization in the constructor or an instance method instead of a static method.
Make drawShapes() an instance method.
When you create the JPanel, store its reference in an instance variable (e.g., myPanel). This will be easier to do and will be a lot less messy if you fix #2.
If you do #2 and #3, just pass the reference to the drawShapes() method.
drawShapes()might not even be needed, if all the logic is in the paintComponent() method. Call myPanel.repaint() to invoke the paintComponent() method.
You should use event dispatching to let different JPanels component to act on the event.
e.g. You could attach a custom event with an event type and jpanel id; and then fire the event from the main. The panel listening to the event do something based on the logic.
Each JPanel listening to the event will intercept the event and if the jpanelid in the event matches to its own jpanel id, it will draw the shape.
I hope you get a pointer.
Here is a sample code, which I have not tested, but it shows how could you use event dispatching/listening to communicate between GUI components.
Define a ChangeEventListener interface
public interface ChangeEventListener {
public void stateChanged(ChangeEvent e);
}
And define an Event
public class ChangeEvent {
private Object source;
private int jPanelId;
public ChangeEvent(Object source, int jPanelId) {
this.source = source;
this.jPanelId= jPanelId;
}
public Object getSource() {
return source;
}
public int getJPanelId() {
return jPanelId;
}
}
Define your panel like
public class ShapePanel extends JPanel {
private int jPanelId;
private ChangeEventListener changeEventListener;
public void ShapePanel(int jPanelId){
this.jPanelId = jPanelId;
}
/*
.............
.............. Other code
.................
*/
public void addChangeEventListener(ChangeEventListener changeEventListener) {
this.changeEventListener = changeEventListener;
}
public int getJPanelId(){
return jPanelId;
}
public getChangeEventListener(){
return changeEventListener;
}
}
Your main should contain something like;
// Craete different Jpanel
JPanel squareShapePanel = new ShapePanel(1);
JPanel roundShapePanel = new ShapePanel(2);
JPanel triangleShapePanel = new ShapePanel(3);
// Attach event listener with each one like
squareShapePanel.addChangeEventListener(new ChangeEventListener() {
#Override
public void stateChanged(ChangeEvent e) {
if(e.getJPanelId() == squareShapePanel.getJPanelId()){
// Createshape method can be available inside JPanel code
// something like squareShapePanel.craeteShape();
// All in one it is a method which could do something for you on the event.
// Assuming that it is available in current class
createShape("square");
}
});
/*
Similarly attach eventlistener with each panels.
*/
// to draw the square shape, Fire change event
ChangeEvent event = new ChangeEvent(new String("Main"),1);
squareShapePanel.getChangeEventListener().stateChanged(event);
Hope this helps.
Check out Custom Painting Approaches for the two common ways to do custom painting:
Keep the Shapes you want to paint in an List and then just paint all the Shapes in the List
Use a BufferedImage and just draw the Shapes onto the BufferedImage.
Both examples contain an addRectangle(...) method that allows you to dynamically add a Rectangle to be painted.