I am trying to make a simple program where an oval follows your mouse cursor, and if you enter "r", "g", or "b" on the keyboard, the oval changes color accordingly.
However, I cannot get my KeyListener to work. Here is my issue. I have a static JPanel, because I need it to be accessible in all functions and methods. However, Java does not let you do this with a static JPanel. I need the JPanel to be static so I can set the color in the keyPressed(KeyEvent e) function.
I understand the basics of Java fairly well, and am grasping some more complicated concepts. Please try to explain if there is any complex code. Thank you!
Here is the code in Drivers.java, the main class.
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Drivers implements KeyListener
{
// panel.red = panel.red - 3;
// panel.green = panel.green - 3;
// panel.blue = panel.blue - 3;
public static JFrame frame = new JFrame();
public static ShapesPanel panel = new ShapesPanel().addKeyListener(this);
// Notice the error we get with the addKeyListener(this);
public static void main(String[] args)
{
// Creates new pointer info
PointerInfo info;
// Creates a point (for mouse tracking)
Point point;
JLabel label = new JLabel();
panel.add(label);
// Set window size
panel.setPreferredSize(new Dimension(300, 350));
// Set panel inside frame
frame.setContentPane(panel);
// Transparent 16 x 16 pixel cursor image.
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
// Set the blank cursor to the JFrame.
frame.getContentPane().setCursor(blankCursor);
// Compile everything into the frame
frame.pack();
// Set frame to close on red exit button
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Get screen size
Dimension sSize = Toolkit.getDefaultToolkit().getScreenSize();
// Position frame
frame.setLocation(sSize.width / 2 - frame.getWidth(), sSize.height / 2 - frame.getHeight());
// Make frame visible
frame.setVisible(true);
// Set name of frame
frame.setTitle("Graphical User Interface");
// While loop to draw oval
while(true)
{
// Repaint the panel
panel.repaint();
// Get mouse info (for tracking)
info = MouseInfo.getPointerInfo();
// Set mouse location data to point
point = info.getLocation();
// Create variables to store coordinates of oval from mouse point location
int x = (int) point.getX();
int y = (int) point.getY();
// Assign those coordinate variables to oval
panel.x = x;
panel.y = y;
// System.out.println("X: " + x);
// System.out.println("Y: " + y);
// System.out.println("X: " + point.getX());
// System.out.println("Y: " + point.getY());
// Try-catch to sleep, to reduce some memory
try
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
}
}
}
// If key is pressed
public void keyPressed(KeyEvent e)
{
// If key is R, change color and print that key has been pressed
if (e.getKeyCode() == KeyEvent.VK_R)
{
System.out.println("R");
panel.red = 255;
panel.green = 0;
panel.blue = 0;
}
// If key is G, change color and print that key has been pressed
if (e.getKeyCode() == KeyEvent.VK_G)
{
System.out.println("G");
panel.red = 0;
panel.green = 255;
panel.blue = 0;
}
// If key is B, change color and print that key has been pressed
if (e.getKeyCode() == KeyEvent.VK_B)
{
System.out.println("B");
panel.red = 0;
panel.green = 0;
panel.blue = 255;
}
}
// Doesn't do anything.. yet
@Override
public void keyReleased(KeyEvent e)
{
}
@Override
public void keyTyped(KeyEvent e)
{
}
}
And, here is the code in ShapesPanel.java:
import java.awt.*;
import javax.swing.*;
public class ShapesPanel extends JPanel
{
// Create x and y variables, and set as default to zero
public int x = 0, y = 0;
// Create RGB variables, used for changing color
public int red = 0, green = 0, blue = 0;
private static final long serialVersionUID = 1L;
// Create new paintComponent function, using an override
@Override
public void paintComponent(Graphics g)
{
// Create new Graphics2D g2 version
Graphics2D g2 = (Graphics2D) g;
// Reset screen, so there are no trails
g2.clearRect(0, 0, getWidth(), getHeight());
// Set background, currently unfunctional
// g2.setBackground(new Color(235, 150, 30));
// Set color palette, using RGB variables
g2.setColor(new Color(red, green, blue));
// Use color palette to draw oval, using x and y variables
g2.fillOval(x, y, 100, 100);
}
}
This is not a good reason for making a field static.
This is not true at all. You can add KeyListeners or any other similar construct just as easily to static and non-static fields. Your problem has nothing to do with restrictions on use of static fields. It's all because you're trying to use
this
in a static context wherethis
doesn't exist.Note that your compiler error could go away with something as simple as:
This again is not a good reason for the field to be static. A Swing listener has direct access to the listened to component any time and at all times via the XxxEvent parameter's
getSource()
method. For instance, if you used a KeyListener, then its method's KeyEvent parameter has agetSource()
method which will return the component (here your drawing JPanel) that is being listened to. If you need references to other components or objects, then pass them into the listener via a constructor setter parameter.this
in a static context, andthis
doesn't exist within this context.while (true)
and aThread.sleep(...)
within code that on later iterations (when you've structured your program better and have started all Swing code on the event thread) risk being called on the Swing event thread. You will want to get rid of these guys and consider using a Swing Timer instead.while (true)
block isn't even needed. Don't poll for the mouse's position but instead use a MouseListener and/or MouseMotionListener for this.For example:
You may ask, why use a
Map<Color, Integer>
when creating the Key Bindings?Doing this allows me to use the for loop to avoid code repetition. Often more concise code is easier to understand and to debug. It also makes it easier to enhance the program later. For instance, if later I want to add Color.CYAN and associate it with the
c
char, all I have to do is add another entry to my Map:and boom it's done. If I needed more than a 1-1 association, then I'd consider using an Enum or separate class to hold the related attributes together.