Reputation: 586
I want to make a custom button that consists of a big and a small square, which have these colors respectively: #2980b9
#3498db
.
The small square would be inside of the big one, and it would increment it's size when the cursor is placed above or if it's clicked, and at the same time the color would change to a clearer one (#4AA3DF
).
The problem is that only the smaller one is printed out, and it's not even printed out well; as it appears on the top left corner of the window.
Besides, the MouseListener
functions aren't being used at all.
This is the Button
class:
public class Button extends JComponent implements MouseListener {
private static final long serialVersionUID = 1L;
JFrame frame = new JFrame();
public Button(JFrame frame) {
enableInputMethods(true);
addMouseListener(this);
this.frame = frame;
}
// Mouse activity //DELETED
MouseEvent mouseEvent; //DELETED
// Window's width and height.
int width = (int) frame.getWidth();
int height = (int) frame.getHeight();
// Squares's sizes.
int bigSquareXSize = 200;
int bigSquareYSize = 200;
int smallSquareXSize = 180;
int smallSquareYSize = 180;
// smallSquare color.
volatile String color = "#3498db";
//I think that I should do something with the update method,
//but i'm not sure about what (sorry, I know this is stupid).
public void update() {
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// Squares's X and Y positions.
int bigSquareXPosition = width / 2 - bigSquareXSize / 2;
int bigSquareYPosition = height / 2 - bigSquareYSize / 2;
int smallSquareXPosition = width / 2 - smallSquareXSize / 2;
int smallSquareYPosition = height / 2 - smallSquareYSize / 2;
g.setColor(Color.decode("#2980b9"));
g2.setColor(Color.decode(color));
g.fillRect(bigSquareXPosition, bigSquareYPosition, bigSquareXSize, bigSquareYSize);
g2.fillRect(smallSquareXPosition, smallSquareYPosition, smallSquareXSize, smallSquareYSize);
}
// Returns a true value if the cursor is placed over the smallSquare.
public boolean insideArea(MouseEvent e) {
boolean value = false;
int smallSquareXPosition = width / 2 - smallSquareXSize / 2;
int smallSquareYPosition = height / 2 - smallSquareYSize / 2;
if (e.getX() > smallSquareXPosition && e.getX() < smallSquareXPosition + smallSquareXSize) {
if (e.getY() > smallSquareYPosition && e.getY() < smallSquareYPosition + smallSquareYSize) {
value = true;
}
}
return value;
}
volatile boolean clicked = false;
@Override
public void mouseClicked(MouseEvent e) {
if (insideArea(e)) {
clicked = !clicked;
if (clicked) {
color = "#4AA3DF";
smallSquareXSize = 190;
smallSquareYSize = 190;
}
} else {
color = "#3498db";
smallSquareXSize = 180;
smallSquareYSize = 180;
}
this.repaint();
}
@Override
public void mouseEntered(MouseEvent e) {
if (!clicked) {
if (insideArea(e)) {
color = "#4AA3DF";
smallSquareXSize = 190;
smallSquareYSize = 190;
}
}
this.repaint();
}
@Override
public void mouseExited(MouseEvent e) {
if (!clicked) {
if (insideArea(e)) {
color = "#3498db";
smallSquareXSize = 180;
smallSquareYSize = 180;
}
}
this.repaint();
}
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
And this is the StartingPoint
class:
public class StartingPoint implements Runnable {
Thread thread = new Thread(this);
static JFrame frame = new JFrame("BUTTON!");
static Button button = new Button(frame);
public static void main(String[] args) {
//Frame creation
JFrame frame = new JFrame("BUTTON!");
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(button);
frame.add(panel);
}
@Override
public void run() {
while (true) {
button.update();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
This is what the console says:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at Button.insideArea(Button.java:62)
at Button.mouseEntered(Button.java:92)
For as far as I can understand, the problem has something to do with the frame
, but I don't know how to solve it.
I looked up for the java.lang.NullPointerException
exception but what I understood is that it happens when you don't call the setSize()
method or a similar one, but I did.
What's the problem and what's the best way to solve it?
EDITED: I figured out that what was causing the console to say what it said was the mouseEvent
. Now each time that I want to refer to the cursor, I'll stick with the e
between the parenthesis at the definition of the method.
There are no error in the console; however, things are printed in the same way and MouseListener is still not working.
EDIT: I dont have exceptions anymore because there were due to the MouseEvent mouseEvent
, but the other problems are still there.
Upvotes: 4
Views: 118
Reputation: 425073
There are a couple of problems.
These lines should be inside the paint method:
int smallSquareXPosition = width / 2 - smallSquareXSize / 2;
int smallSquareYPosition = height / 2 - smallSquareYSize / 2;
As it stands, these values are assigned once on initialization of the object. But your paint method expects them to be dynamic, because your event listener changes smallSquareXSize
and smallSquareXSize
.
Since these variables are only accessed from the paint method, move all sch variable declarations inside the paint method:
public void paintComponent(Graphics g) {
int bigSquareXPosition = width / 2 - bigSquareXSize / 2;
int bigSquareYPosition = height / 2 - bigSquareYSize / 2;
int smallSquareXPosition = width / 2 - smallSquareXSize / 2;
int smallSquareYPosition = height / 2 - smallSquareYSize / 2;
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.decode("#2980b9"));
g2.setColor(Color.decode(color));
g.fillRect(bigSquareXPosition, bigSquareYPosition, bigSquareXSize, bigSquareYSize);
g2.fillRect(smallSquareXPosition, smallSquareYPosition, smallSquareXSize, smallSquareYSize);
}
This also follows a good general coding practice of declaring variables such that they have the smallest possible scope. In this case, you'll be reducing the scope from all methods to just the method that uses them. Although it doesn't apply in this case, you should further reduce the scope to to just a block of code (eg within loop or if
block etc) if possible.
The other problem is more subtle. It's one of concurrency, ie running in a multi-threaded environment. The thread that sends the mouse event is a different thread to the main thread. Further it may be a different thread every mouse event (I'm not sure). One of the implications of concurrent threads is that changes to instance variables made in one thread may not be "seen" by other threads. Specifically, this field:
boolean clicked = false;
and this line:
clicked = !clicked;
This "problem" is due to the java memory model, which simply put says that each thread may cache a copy of the field's value, meaning if one thread assigns a new value to it, another thread may or may not see this change.
But don't panic - there is an easy fix:
volatile boolean clicked = false;
The volatile
keyword tells java to never cache the value - always check the value in the memory managed by the thread that created the object.
I'm not sure if the framework you're using may use different threads for the various types of mouse events, but if it does, you need volatile
. If not, no harm will come of making it volatile
if it doesn't need to be.
Upvotes: 2
Reputation: 1927
The problem is that your MouseEvent mouseEvent;
variable is never initialized. You could initialize it and then pass it down to your insideArea
every time or you could completely remove it and just use the event directly as show bellow.
@Override
public void mouseClicked(MouseEvent e) {
if (insideArea(e)) {
...
And here
@Override
public void mouseEntered(MouseEvent e) {
if (!clicked) {
if (insideArea(e)) {
...
and here
@Override
public void mouseExited(MouseEvent e) {
if (!clicked) {
if (insideArea(e)) {
...
Upvotes: 1