I'm having a little problem with my Java code. For a school project, we have to write a simple Java program. I have some programming experience, but not with Java.
We're using Netbeans, and we have to make a JFrame, containing a panel containing an applet. The applet should be a simple traffic light (red/orange/green), and the JFrame should contain three buttons "Red", "Orange" and "Green", which should update the traffic light.
I got most of it working: I have an applet that draws the traffic light based on the booleans red, orange and green, and the updating using the buttons works too.
The problem is that the screen only redraws when I hide the window behind others, and then make it appear again. I have very limited understanding of how painting works in Java, and I can't really find any solutions for this problem on the internet. Can anyone help me?
Here's my code:
DeFrame.java
package my.AppletInPanel;
public class DeFrame extends javax.swing.JFrame {
private DeApplet applet;
/** Creates new form DeFrame */
public DeFrame() {
initComponents();
applet = new DeApplet();
jPanel1.add(applet);
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
#SuppressWarnings("unchecked")
// [Generated code]
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
applet.SetDaColor(true, false, false);
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
applet.SetDaColor(false, true, false);
}
private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {
applet.SetDaColor(false, false, true);
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new DeFrame().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JButton jButton3;
private javax.swing.JPanel jPanel1;
// End of variables declaration
}
And DeApplet.java:
package my.AppletInPanel;
import java.applet.Applet;
import java.awt.*;
public class DeApplet extends Applet {
public boolean rood = true, oranje = false, groen = false;
public DeApplet(){
setLayout(null);
setSize(50, 150);
}
public void SetDaColor(boolean r, boolean o, boolean g){
rood = r;
oranje = o;
groen = g;
}
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.white);
g.drawRect(0, 0, 50, 150);
g.fillRect(0, 0, 50, 150);
if(rood){
g.setColor(Color.red);
g.fillOval(0 , 0, 50 ,50);
}
if (oranje){
g.setColor(Color.orange);
g.fillOval(0 , 50, 50 ,50);
}
if (groen){
g.setColor(Color.green);
g.fillOval(0 , 100, 50 ,50);
}
String s1 = (new Boolean(rood).toString());
String s2 = (new Boolean(oranje).toString());
String s3 = (new Boolean(groen).toString());
g.setColor(Color.black);
g.drawString(s1, 25, 25);
g.drawString(s2, 25, 75);
g.drawString(s3, 25, 125);
}
}
try repainting the panel and/or the frame
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
applet.SetDaColor(false, true, false);
applet.repaint();
}
You should have a look at Java Swing revalidate() vs repaint(), which discusses methods of importance to you.
Since this is for school, I would also like to point out that your "which color to show" logic would be greatly improved by use of Java Enums, where you could not wind up in the weird state that e.g. groen and rood are both true at the same time.
Related
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.
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.
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.
I have been making an applet for a project for my school but I stumbled upon a nasty error. My problem is probably quite common but I can't seem to find an answer for it. I have been getting the error:
The type Pyramid must implement the inherited abstract method ActionListener.actionPerformed(ActionEvent)
but I have already implemented the method in there and it's spelling is correct. I checked so many times that my head hurts. My project has frozen at this process for like 3 hours now and I have no idea how to get it to work. Oh and also do you think that using 3 polygons to draw a pyramid is the best way to do it in java?
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class Pyramid extends Applet implements ActionListener {
int n = 3; // vurhove
double a, k;
double Sok, S, V;
public void init()
{
setLayout(null);
TextField aT; // textbox za osn. rub
aT = new TextField("Osnoven rub", 20);
aT.setBounds(20, 410, 125, 20);
TextField lT; // textbox za ok. rub
lT = new TextField("Okolen rub", 20);
lT.setBounds(20, 460, 125, 20);
TextField SokT; // textbox S ok.
SokT = new TextField("Okolna povurhnina", 20);
SokT.setBounds(20, 510, 125, 20);
TextField ST; // textbox S1
ST = new TextField("Osnovna povurhnina", 20);
ST.setBounds(20, 560, 125, 20);
TextField VT; // textbox za obema
VT = new TextField("Obem", 20);
VT.setBounds(20, 610, 125, 20);
Button ochButton; // izchertava piramidata
ochButton = new Button("Izchertai");
ochButton.setBounds(700, 435, 100, 30);
ochButton.addActionListener(ActionOchButton);
Button oznButton; // ozna4ava vurhovete, izpisva velichinite
oznButton = new Button("Oznachi cherteja");
oznButton.setBounds(700, 510, 100, 30);
oznButton.addActionListener(ActionOznButton);
Button iButton; // iz4islqva veli4inite
iButton = new Button("Izchisli");
iButton.setBounds(700, 585, 100, 30);
iButton.addActionListener(ActioniButton);
add(aT);
add(lT);
add(SokT);
add(ST);
add(VT);
add(ochButton);
add(oznButton);
add(iButton);
Color bg;
bg = new Color(168,168,168);
setBackground(bg);
}
ActionListener ActionOchButton = new ActionListener() {
public void actionPerformed(ActionEvent e){
Graphics g = getGraphics();
int px[] = {340, 440, 490};
int py[] = {235, 335, 235};
g.drawPolygon(px, py, 3);
int px1[] = {340, 415, 490};
int py1[] = {235, 60, 235};
g.drawPolygon(px1, py1, 3);
int px2[] = {440, 415, 490};
int py2[] = {335, 60, 235};
g.drawPolygon(px2, py2, 3);
}
};
ActionListener ActionOznButton = new ActionListener() {
public void actionPerformed(ActionEvent e){
}
};
ActionListener ActioniButton = new ActionListener() {
public void actionPerformed(ActionEvent e){
}
};
public void paint(Graphics g)
{
g.setColor(Color.white);
g.drawRect(20, 20, 780, 370);
}
}
You have not implemented the method, plain and simple.
You need to have that method defined in your class. You're adding a bunch of other action listeners, but nowhere in your class do you implement the required method.
To clarify further, on the same level as your init() method, you need a public void actionPerformed(ActionEvent e) { /* ... */ } method.
You have not impleented ActionListener.actionPerformed(ActionEvent) in Pyramid, only in the three anonymous instances of ActionListener. For Pyramid to implement ActionListener, you must implement this method in th class, not in some local variable.
You should implement the method in your class. For example, here it is at the end of your Pyramid class:
#Override
public void actionPerformed(ActionEvent e) {
}
} //End of Pyramid class
Now, in this actionPerformed() method, you can determine which button was clicked and do the appropriate action. But you'll have to change a few things:
//make these instance variables
Button ochButton; // izchertava piramidata
Button oznButton; // ozna4ava vurhovete, izpisva velichinite
Button iButton; // iz4islqva veli4inite
Add action listener this way...
ochButton.addActionListener(this);
oznButton.addActionListener(this);
iButton.addActionListener(this);
Implement actionPerformed()...
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == ochButton) {
System.out.println("ochButton clicked");
} else if (e.getSource() == oznButton) {
System.out.println("oznButton clicked");
} else if (e.getSource() == iButton) {
System.out.println("iButton clicked");
}
}
Regarding drawing a pyramid, perhaps you can draw the base triangle and the top point. Then draw lines from the top point to each point of the triangle.
I'm new to java, and have been working on creating a code to get a small picture of a car to move using the keys. My problem is when I add more than 1 buttons to the panel. The function of the button in the code I post is nothing, just prints "button [i] clicked" message. The purpose is for them to read a file and update the locations of the car from the data in the file. This is supposed to be a piece on my Reinforcement Learning project. I thought it would be a good opportunity to learn java since this "graphics package" is not necessary for the project, just a "nice" addition. The code is here:
package graphics;
public class Board extends JPanel implements ActionListener {
private Timer timer;
private Agent agent;
private String button = "button.png";
private Image image;
protected JButton b1;
protected JButton b2;
protected JButton b3;
public Board() {
//Keylistener added for the agent to respond to arrow keys
addKeyListener(new TAdapter());
agent = new Agent();
timer = new Timer(10, this); //10ms timer calls action performed
timer.start();
//This part for the button.
ImageIcon i = new ImageIcon(this.getClass().getResource(button));
image = i.getImage();
b1 = new JButton("1", i);
b1.setVerticalTextPosition(AbstractButton.CENTER);
b1.setHorizontalTextPosition(AbstractButton.LEADING);
b1.setActionCommand("Active1");
b2 = new JButton("2", i);
b2.setVerticalTextPosition(AbstractButton.CENTER);
b2.setHorizontalTextPosition(AbstractButton.LEADING);
b2.setActionCommand("Active2");
b3 = new JButton("3", i);
b3.setVerticalTextPosition(AbstractButton.CENTER);
b3.setHorizontalTextPosition(AbstractButton.LEADING);
b3.setActionCommand("Active3");
b1.addActionListener(this);
b2.addActionListener(this);
b3.addActionListener(this);
add(b1); add(b2); add(b3);
setFocusable(true);
setBackground(Color.BLACK);
setDoubleBuffered(true);
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
//Transformations for the agent to be painted based upon its position and orientation
AffineTransform trans = new AffineTransform();
trans.rotate(Math.toRadians(agent.getTh()), agent.getX()+64, agent.getY()+64);
trans.translate(agent.getX(), agent.getY());
g2d.drawImage(agent.getImage(), trans, this); // Draws agent with said transformations
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void actionPerformed(ActionEvent ae) {
b1.setEnabled(true);
b2.setEnabled(true);
b3.setEnabled(true);
if (ae.getActionCommand()=="Active1") {
b1.setEnabled(false);
b2.setEnabled(true);
b3.setEnabled(true);
System.out.println("Clicked 1");
agent.reset();
}
if(ae.getActionCommand()=="Active2") {
b1.setEnabled(true);
b2.setEnabled(false);
b3.setEnabled(true);
System.out.println("Clicked 2");
agent.reset();
}
if (ae.getActionCommand()=="Active3") {
b1.setEnabled(true);
b2.setEnabled(true);
b3.setEnabled(false);
System.out.println("Clicked 3");
agent.reset();
}
agent.move();
repaint();
}
private class TAdapter extends KeyAdapter {
public void keyReleased(KeyEvent e) {
agent.keyReleased(e);
}
public void keyPressed(KeyEvent e) {
agent.keyPressed(e);
}
}
}
Now, the problem is this. If I click button 1, or button 2, the other buttons are disabled and it says "Clicked 1 (or 2)" which is fine. But the agent.move() and repaint() aren't invoked. The car doesn't move when I press the keys. If I then click button 3, the other two buttons are disabled and the car moves with the keys.
If I add buttons in a different order add(b3); add(b2); add(b1); then the same happens but this time its button 1 that works fine.
Problems:
Your main problem is one of focus -- When a JButton gets focus and the JPanel loses focus, the JPanel's KeyListener won't work, since the KeyListener requires that the listened to component have the focus, no exception.
A bad solution is to force the JPanel to retain the focus at all times. This will be bad if your window has JButtons and will be disaster if you need to display JTextFields, JTextAreas, or other text components.
A better solution is not to use a KeyListener as it has lots of problems with Swing applications and in particular it has focus issues as noted above. Use Key Bindings instead. Google the tutorial for the gory details on this.
Don't use == to compare Strings, use equals(...) or equalsIgnoreCase(...) instead. The problem is that == checks for object equality, is String A the same object as String B, and you don't care about this. You want to know if the two Strings hold the same chars in the same order which is where the two methods come in.
Don't dispose() of a Graphics object given you by the JVM as this may mess up the painting of a component's borders, children, and have even other side effects.
don't draw in the JPanel's paint(...) method but rather in its paintComponent(...) method just as the tutorials tell you to do. Drawing in paint(...) can have side effects on a component's borders and children if you're not careful, and also does not have the benefit of default double buffering which is important for smooth animation. paintComponent(...) fixes all these problems.
Speaking of which, you should Google and read the Swing graphics tutorials. You can't make stuff up and hope that it will work, and graphics programming will require a whole different approach from what you're used to.
Ignore Andromeda's threading suggestion. While he means well, I suggest that you don't do painting in a background thread. Just move the car with your Swing Timer as you're doing. Background threading has its uses, but not here, as a Timer will work just fine. You will need to use a background thread when you have a long-running process that blocks the calling thread, something that you don't have in your current code, and so a thread "fix" is not needed and in fact is a potential source of problems if you don't take extreme care to make your Swing calls on the Swing event thread. The unknown for us though is what your "agent" is doing. If it is calling long-running code or code with Thread.sleep(...) or wait()/notify() in it, then yes, you will need to use background threading.
But again, we know that's not your primary problem, since your problems began only after adding focus grabbers -- the JButtons. Again this is strong indication that your primary problem is not threading but is use of KeyListeners and their focus requirement.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.*;
import java.util.EnumMap;
import javax.swing.*;
#SuppressWarnings("serial")
public class KeyBindingPanel extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = PREF_W;
private static final Stroke THICK_STROKE = new BasicStroke(5f,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
private static final int OVAL_WIDTH = 30;
private static final int OVAL_HEIGHT = 30;
private static final Color OVAL_COLOR = Color.red;
private static final Color BKGRD_COLOR = Color.black;
private static final int TIMER_DELAY = 20;
public static final int STEP = 2;
private int myX = 0;
private int myY = 0;
private JButton[] buttons = new JButton[3];
private int condition = WHEN_IN_FOCUSED_WINDOW;
private InputMap inputMap = getInputMap(condition);
private ActionMap actionMap = getActionMap();
private EnumMap<Direction, Boolean> directionMap = new EnumMap<Direction, Boolean>(
Direction.class);
public KeyBindingPanel() {
for (int i = 0; i < buttons.length; i++) {
buttons[i] = new JButton(new ButtonAction());
add(buttons[i]);
}
setBackground(BKGRD_COLOR);
for (final Direction direction : Direction.values()) {
directionMap.put(direction, Boolean.FALSE);
Boolean[] onKeyReleases = { Boolean.TRUE, Boolean.FALSE };
for (Boolean onKeyRelease : onKeyReleases) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(
direction.getKeyCode(), 0, onKeyRelease);
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), new DirAction(direction,
onKeyRelease));
}
}
new Timer(TIMER_DELAY, new GameTimerListener()).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2b = (Graphics2D) g.create();
g2b.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2b.setStroke(THICK_STROKE);
g2b.setColor(OVAL_COLOR);
g2b.drawOval(myX, myY, OVAL_WIDTH, OVAL_HEIGHT);
g2b.dispose(); // since I created this guy
}
private class GameTimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (Direction direction : Direction.values()) {
if (directionMap.get(direction)) {
myX += STEP * direction.getRight();
myY += STEP * direction.getDown();
}
}
repaint();
}
}
private class DirAction extends AbstractAction {
private Direction direction;
private boolean onRelease;
public DirAction(Direction direction, boolean onRelease) {
this.direction = direction;
this.onRelease = onRelease;
}
#Override
public void actionPerformed(ActionEvent evt) {
directionMap.put(direction, !onRelease); // it's the opposite!
}
}
private class ButtonAction extends AbstractAction {
public ButtonAction() {
super("Press Me!");
}
#Override
public void actionPerformed(ActionEvent e) {
JButton thisBtn = (JButton) e.getSource();
for (JButton btn : buttons) {
if (btn == thisBtn) {
btn.setEnabled(false);
} else {
btn.setEnabled(true);
}
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("KeyBindingPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new KeyBindingPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Direction {
UP(KeyEvent.VK_UP, -1, 0), DOWN(KeyEvent.VK_DOWN, 1, 0), LEFT(
KeyEvent.VK_LEFT, 0, -1), RIGHT(KeyEvent.VK_RIGHT, 0, 1);
private int keyCode;
private int down;
private int right;
private Direction(int keyCode, int down, int right) {
this.keyCode = keyCode;
this.down = down;
this.right = right;
}
public int getKeyCode() {
return keyCode;
}
public int getDown() {
return down;
}
public int getRight() {
return right;
}
}
It seems you need to use Threading. You can make your class to implements Runnable and then override the public void run(){} method and do the painting there.