Java - Separate Listener Class throws NullPointerException - java

Apologize for the long post, but I wanted to be as detailed as possible so there's a better chance people understand what I'm trying to convey:
OK, so an overview of this problem is that I'm trying to make a program that simulates a cash register. (This is a "for-fun" project). The way I set it up is:
CashRegister: main class that starts the program, serves as the main window for everything.
public class CashRegister extends JFrame {
...
}
Other classes: serve as JPanels that provide the different components of the main CashRegister window. Example:
public class NorthPanel extends JPanel {
...
}
Then in the CashRegister class:
add(new NorthPanel(), BorderLayout.NORTH);
etc.
Basically, I have a JTextField in the NorthPanel class called priceField that holds the value of the price the user enters. I have a separate class (Keypad) that also extends JPanel and serves as the number keypad in the center of the main window. In CashRegister:
add(new NorthPanel(), BorderLayout.NORTH);
add(new Keypad()); // (default for BorderLayout is CENTER)
One of the problems I have run into is that I created one class, EventHandler, to serve as the listener class for the entire program because there are components in each JPanel class (NorthPanel and Keypad, for instance) that need to communicate with each other; the user presses a keypad button, the price field in the NorthPanel needs to know what key in the keypad was pressed.
I don't know if the problem comes from the fact that these components are coming from different classes and are therefore referenced differently, or what. All the classes are in the same directory, I'm using NetBeans and it's all part of the same package.
In my EventHandler class, I created several different constructors so as to be able to pass in all the components that need to communicate with each other from different classes. For example:
public class EventHandler implements ActionListener {
private JTextField priceField;
private JButton[][] keypad;
public EventHandler(JTextField priceField) {
this.priceField = priceField;
}
public EventHandler(JButton[][] keypad) {
this.keypad = keypad;
}
}
In my NorthPanel class, I instantiate priceField first, configure it (set the font, etc.) and say:
EventHandler e = new EventHandler(priceField);
in my attempt to pass the priceField through to my listener class.
Then in the Keypad class, I say:
EventHandler e = new EventHandler(keypad);
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 3; j++) {
keypad[i][j].addActionListener(e);
}
}
Then in the EventHandler class, having passed through those variables:
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 3; j++) {
if(keypad[i][j] == e.getSource()) {
// call a custom method to append the numbers to the text field
}
}
}
}
It is at this point that I get a NullPointerException. I don't know why, but my guess is that it's because the listener objects are coming from different classes and using different constructors because I have to pass in different objects from each class that need to communicate with each other. I'm not 100% sure though. Is there a way to get around this or am I doing something completely wrong?

You should let the CashRegister handle all user input, as it is the main Frame of your application (it extends the JFrame). For the sub-panels, make a method, such as aKeyWasPressed( int keyCode ), that will be called from your frame, within the body of keyPressed method you override.
Also, set the JFrame to be focusable, and preferably make the other panels unfocusable setFocusable(false);
Here is a sample code of the CashRegister:
public class CashRegister extends JFrame implements KeyListener
{
private NorthPanel northPanel;
private Keypad keypad;
public CashRegister ()
{
this.setFocusable(true);
this.addKeyListener(this);
northPanel = new NorthPanel();
keypad = new Keypad();
/* Your code ... */
}
#Override
public void keyTyped(KeyEvent ke)
{
/*Do nothing*/
}
#Override
public void keyPressed(KeyEvent ke)
{
northPanel.aKeyWasPressed( ke.getKeyCode() );
keypad.aKeyWasPressed( ke.getKeyCode() );
}
#Override
public void keyReleased(KeyEvent ke)
{
/*Do nothing*/
}
}
Note: To distinguish which key was pressed, inside the aKeyWasPressed method from your panels, you can do:
private void aKeyWasPressed(int code)
{
if(code == KeyEvent.VK_A)
{
/*If its letter 'A', do this...*/
}
}
Again, there are many ways of doing it, but letting the Frame itself handle all user input is, in my opinion, the best pratice in most cases, mainly when dealing with multiple panels.

Related

Java enter key the same as submit button?

What the program is supposed to do is to mimic the "spin" button with the enter key. The program works and it doesn't crashes but eclipse console is giving me a "Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: javax.swing.JTextField cannot be cast to javax.swing.JButton" error.
//.. gui code
spin = new JButton("Spin");
bet = new JTextField("");
play p = new play();
spin.addActionListener(p);
keys k = new keys();
bet.addKeyListener(k);
}
private class play implements ActionListener{
public void actionPerformed(ActionEvent e) {
JButton src = (JButton) e.getSource();
if(src.equals(spin)) {
//do something
}
}
private class keys implements KeyListener{
#Override
public void keyTyped(KeyEvent e) {
char c= e.getKeyChar();
if(c == KeyEvent.VK_ENTER) {
spin.doClick();
}
}
//.. the other override methods
}
EDIT
there are 2 more buttons, I just didn't include them since they worked fine and functioned differently.
JTextField has a KeyListener because I was filtering out numbers from letters so I would consume the event. Can't have a user bet with letters right?
I would guess that your ActionListener is receiving events from multiple objects, one of which is a JTextField. The exception occurs when you try to cast this object to JButton:
(JButton) e.getSource();
There may be a better solution but from what you've shown us the easiest way to prevent the exception is to check that the object generating is an event is an instance of JButton before casting:
private class play implements ActionListener{
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
// Check type before casting
if (src instanceof JButton) {
JButton bsrc = (JButton)src;
if(bsrc.equals(spin)) {
//do something
}
}
}
}
First, don't use KeyListener for this task, JTextField already supports ActionListener and you could make use of JRootPane's "default button" support as well, so a number of better solutions are available.
You should also take advantage of the actionComamnd support of the ActionEvent (and ActionListener), which will mean you don't have to cast the source, which is safer and makes the solution more re-usable (as it's decoupled).
For example...
Play p = new Play();
spin = new JButton("Spin");
spin.setActionCommand(Play.COMMAND);
bet = new JTextField("");
bet.setActionCommand(Play.COMMAND);
spin.addActionListener(p);
bet.addActionListener(p);
//...
private static class Play implements ActionListener {
public static String COMMAND = "Spin";
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals(COMMAND)) {
//do something
}
}
}
Alternatively, you could make use of the existing Actions API. This allows you to design a self contained unit of work that can be applied directly to a number of Swing components (which already support the ActionListener API), but which are also self configurable, neat...
private class SpinAction extends AbstractAction {
public SpinAction() {
putValue(NAME, "Spin");
}
#Override
public void actionPerformed(ActionEvent event) {
// Spin me baby
}
}
And then simply apply it...
SpinAction action = new SpinAction();
spin = new JButton(action);
bet = new JTextField("");
bet.setAction(action);
See How to use actions for more details

How to change JPanel color with ActionListener from another class?

I am having issues changing the color of a Jpanel (mainPanel) in one of my classes using an ActionListener for a radio button in a different class. I have the default color for mainPanel set to black.
Essentially I have a setMainPanel method in my first class:
public class MainBoard extends JFrame{
public void setMainPanel(Color c){
mainPanel.setBackground(c);
}
}
In my second class I have my action listener as:
private class MenuHandler implements ActionListener{
public void actionPerformed(ActionEvent e){
if(e.getSource() == exit) {
System.exit(0);
}else if(e.getSource() == blueBackground) {
MainBoard mb = new MainBoard();
mb.setMainPanel(Color.BLUE);
}
}
}
My System.exit command will work if I click "Exit" in my menu. The concept I am trying to accomplish is to have a "Change Color" menu with radio buttons where the user can change to Black or Blue. I know this is probably overkill, but we are trying to learn how to make changes for components from different classes.
Thank you for any help!
No good:
MainBoard mb = new MainBoard(); // this is not the currently displayed MainBoard
mb.setMainPanel(Color.BLUE);
This represents a common Java newbie error of magical thinking where you think that changing the state of an object of a class will magically change the state of a different object of the same class, but this is not how Java works. You need to call the setMainPanel(...) method on a reference to the currently displayed or active MainBoard instance, not some random new and totally distinct MainBoard object that you create within this method and which is never displayed.
So pass in the appropriate reference into the listener, perhaps using something like:
private class MenuHandler implements ActionListener {
MainBoard mainBoard;
MenuHandler(MainBoard mainBoard) {
this.mainBoard = mainBoard;
}
public void actionPerformed(ActionEvent e){
if(e.getSource() == exit) {
System.exit(0);
}else if(e.getSource() == blueBackground) {
// MainBoard mb = new MainBoard();
mainBoard.setMainPanel(Color.BLUE);
}
}
}

How to repaint JPanel from outside its parent JFrame?

I can add/remove elements to/from a panel and repaint it when the method used to fill the panel is called by one of its parent JFrame events, but I can not repaint it by events from other classes even if their sources have been added to it, or that is how I understand the problem for now.
I want to understand what is going on here, Thank you.
Main Class
public class Principal extends JFrame implements ActionListener{
private static Principal instPrincipal = null;
private SubClass subClassInst =new SubClass();
public JPanel panelPrincipal;
public static Principal getInstance() {
if (instPrincipal != null)
return instPrincipal ;
else {
instPrincipal = new Principal ();
return instPrincipal ;
}
}
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
try {
if(source == btnSub)
{
subClassInst.fillPanelPrincipal();
}
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
Sub Classes Example
public class SubClass implements ActionListener {
private JPanel tempPanel;
private JButton btnSave;
private Principal instPrincipal;
public void fillPanelPrincipal() {
instPrincipal = Principal.getInstance();
instPrincipal.panelPrincipal.removeAll();
//Start adding elements..
tempPanel = new JPanel();
instPrincipal.panelPrincipal.add(tempPanel);
btnSave = new JButton("Save");
btnSave.addActionListener(this);
tempPanel.add(btnSave);
//End.
instPrincipal.panelPrincipal.repaint();
}
public void actionPerformed(ActionEvent event) {
instPrincipal = Principal.getInstance();
Object source = event.getSource();
if (source == btnSave) {
// modify local data, Database .. ; //work but need to be repainted on panelPrincipal
instPrincipal.panelPrincipal.repaint();//does not work
}
}
}
Update
To clarify the problem more, I have one single JPanel on a JFrame and there are different classes to fill it for multiple functionalities, I call their methods using JMenuItems on the main frame, these Classes implement ActionListener, passing the panel didn't work, and also the method I am trying here.
I thought about changing the design to use CardLayout, but it was very difficult.
You are calling Principal as a static reference, so how is it supposed to know what frame to repaint? You should pass the instance of the JFrame through the constructor of the subclass. Like so:
private SubClass subClassInst = new SubClass(this);
And create the constructor like this
private JFrame parent;
public SubClass(JFrame parent) { this.parent = parent; }
You can then use it like so
this.parent.repaint();

How to add global JPanels to a JFrame

I am currently in a programming course in highschool and we are focusing on Java, one of the programs required to be built is a Rock Paper Scissors game. I have done this easily and it works but i decided to try and figure out how to make it work in a window of its own. this led me to research JFrames and how to use them. I have loked up many tutorials to introduce it and I have 5 different examples from the oracle site saved to use for reference, yet i have not be able to figure out why this program won't work.
package rps;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* Name: Steven Biro
* Course Code: ICS3U
* Teacher: Mr.Carron
* Date: 23-Apr-2014
* Program Description:
*/
public class RPS
implements ActionListener {
static JPanel text,buttons;
/**
* #param args the command line arguments
*/
public void RPS() {
JButton Rock,Paper,Scissors;
buttons = new JPanel();
Rock = new JButton("Rock");
Paper = new JButton("Paper");
Scissors = new JButton("Scissors");
Rock.setMnemonic(KeyEvent.VK_D);
Paper.setMnemonic(KeyEvent.VK_M);
Scissors.setMnemonic(KeyEvent.VK_E);
Rock.setActionCommand("Rock");
Paper.setActionCommand("Paper");
Scissors.setActionCommand("Scissors");
Rock.addActionListener(this);
Paper.addActionListener(this);
Scissors.addActionListener(this);
buttons.add(Rock);
buttons.add(Paper);
buttons.add(Scissors);
}
public void actionPerformed(ActionEvent e) {
String PC,Player;
int outcome;
PC="";
Player=(e.getActionCommand());
int computer = (int)(Math.random()*3+1);
if (computer==1) {
PC="Rock";
} else if (computer==2) {
PC="Paper";
} else {
PC="Scissors";
} if (Player.equals(PC)) {
outcome=0; //tied
} else {
if ("Rock".equals(PC)) {
if ("Paper".equals(Player)) {
outcome=1; //win
} else {
outcome=2; //lose
}
} else if ("Paper".equals(PC)) {
if ("Scissors".equals(Player)) {
outcome=1; //win
} else {
outcome=2; //lose
}
} else {
if ("Rock".equals(Player)) {
outcome=1; //win
} else {
outcome=2; //lose
}
}
}
JLabel r;
if (outcome==0) {
r = new JLabel ("You Tied.");
} else if (outcome==1) {
r = new JLabel ("You Win.");
} else if (outcome==2) {
r = new JLabel ("You Lose.");
} else {
System.exit(2);
r = new JLabel ("wont ever execute");
}
text = new JPanel();
text.add(r);
}
public static void GUI() {
//Create and set up the window.
JFrame frame = new JFrame("RockPaperScissors");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
frame.add(buttons);
frame.add(text);
//Display the window.
frame.pack();
frame.setVisible(true);
frame.setSize(250,150);
}
public static void main(String[] args) {
GUI();
}
}
If you can help me figure out why the JPanels called "buttons" and "text" wont add I would be very grateful.
If I remove
//Create and set up the content pane.
frame.add(buttons);
frame.add(text);
from my program, then it runs without a problem and is just a blank window as would be expected, so i am at a loss of what to do.
EDIT:
The error i get after i make the correction of removing void from public void RPS() {
Exception in thread "main" java.lang.NullPointerException
at java.awt.Container.addImpl(Container.java:1091)
at java.awt.Container.add(Container.java:1003)
at javax.swing.JFrame.addImpl(JFrame.java:564)
at java.awt.Container.add(Container.java:415)
at rps.RPS.GUI(RPS.java:102)
at rps.RPS.main(RPS.java:114)
Picked up _JAVA_OPTIONS: -Djava.net.preferIPv4Stack=true
Java Result: 1
If i remove static from static JPanel text,buttons; then netbeans "corrects" each method so it never says static for any of them including Main so it says it cant find main.
sorry if im coming across as stupid, but if anyone could please help me figure this out i would be very appreciative.
You never create a RPS instance anywhere via new RPS().
You have a "pseudo-constructor" in your RPS class. i.e., this, public void RPS() { is not a constructor. Get rid of the void return type as constructors should have no return type: public RPS() {
Your Swing component fields should most definitely not be static.
You're adding components to a JFrame without respect for its layout manager, the BorderLayout. Your current code is adding null components, but if they weren't null, they'd both be added BorderLayout.CENTER, the last one covering up the previous one.
Create a master JPanel in the RPS class, and then add that to the JFrame in the main method, via non-static methods.
For example, RPS could potentially extend JPanel, and if that's the case, then you'd add it to the JFrame like so:
JFrame frame = new JFrame("RPS");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(new RPS());
Other suggestions:
Check out enums as Rock Paper Scissors lends itself well to this, including giving the enum methods to test for win
By using an enum, it would be easier to modify the code later to allow for additional states, such as Lizard and Spock.
Try to separate the logic portion of your program from the view -- the GUI portion.
Avoid having your GUI classes implement your listener interfaces as this leads to creation of "switch-board" listeners, kind of like you're writing -- a bear to debug or enhance. Instead experiment with anonymous inner classes, or private inner classes here.
This is probably because
public void RPS() {
is not the constructor, but a method (because it has a return type), and so is never called.
Remove the void and you should be good.

action listener in another class - java

it is possible to have two class, and in one something like
arrayButtons[i][j].addActionListener(actionListner);
and in another
ActionListener actionListner = new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int j = 0; j < arrayButtons.length; j++) {
for (int i = 0; i < arrayButtons[j].length; i++) {
if (arrayButtons[j][i] == e.getSource()) {
if ((gameNumber == 2) && (playHand.getNumberOfCards() == 0)) {
if (player[j].getCard(i).getSuit() == Suit.HEARTS.toString() && player[j].hasSuitBesideHearts())
//second game
messageOnTable("xxx");
else{
arrayButtons[j][i].setVisible(false);
test[j].setIcon(player[j].getCard(i).getImage());
pnCardNumber[j].setText(Integer.toString(player[j].getCard(i).getNumber()));
pnCardName[j].setText(player[j].getCard(i).toString());
pnCardSuit[j].setText(player[j].getCard(i).getSuit());
playHand.addCard(player[j].getCard(i), j);
player[j].removeCard(i);
}
}
}
//and more
the reason of that is because i need to separate the button (swing) to the action listener
how i can do ?
thanks
Not only it is possible to separate these two, it's also recommended (see MVC pattern - it's very much about separating screen controls like buttons, and the logics of your program)
The easiest way that comes to my mind is to do write a named class that implements ActionListener interface, something like this:
public class SomeActionListener implements ActionListener{
private JTextField textField1;
private JComboBox combo1;
private JTextField textField2;
//...
public SomeActionListener(JTextField textField1, JComboBox combo1,
JTextField textField2){
this.textField1=textField1;
this.combo1=combo1;
this.textField2=textField2;
//...
}
public void actionPerformed(ActionEvent e) {
//cmd
}
}
And then add it to your buttons:
ActionListener actionListener = new SomeActionListener(textField1, combo1, textField2);
someButton.addActionListener(actionListener);
To answer: "my problem is that action listener have many variables of swing like buttons for example,so, when i change to another class, i have problems with that"
Your action listener class could have a constructor that takes a parameter of the type of the view class:
public class Listener implements ActionListener {
private final MyViewClass mView;
public Listener(MyViewClass pView) {
mView = pView;
}
public void actionPerformed(ActionEvent e) {
// can use mView to get access to your components.
mView.get...().doStuff...
}
}
Then in your view:
Listener l = new Listener(this);
button.addActionListener(l);
you can do it easily by using nested classes,
but i think the best way is pass the parent object as a parameter to the construct of object and using it as an action handler;
//**parent class - Calculator **//
public class Calculator extends JFrame implements ActionListener{
private DPanel dPanel;
private JTextField resultText;
public Calculator(){
// set calc layout
this.setLayout(new BorderLayout(1,1));
dPanel = new DPanel(this); // here is the trick ;)
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
resultText.setText(command);
// **** your code ****/
}
}
//**inner class - DPanel**//
public class DPanel extends JPanel{
private JButton digitsButton[];
private JButton dotButton,eqButton;
public DPanel(Calculator parent){
//layout
this.setLayout(new GridLayout(4,3,1,1));
// digits buttons
digitsButton = new JButton[10];
for (int i=9;i>=0;i--){
digitsButton[i] = new JButton(i+"");
digitsButton[i].addActionListener(parent); // using parent as action handler ;)
this.add(digitsButton[i]);
}
}
}
It's a bit off topic but you should definately not use the == operator to compare Strings as you appear to be doing on this line:
if (player[j].getCard(i).getSuit() == Suit.HEARTS.toString()
This is because Strings are pointers, not actual values, and you may get unexpected behaviour using the == operator. Use the someString.equals(otherString) method instead. And also
"String to compare".equals(stringVariable)
is alot better than the other way around
stringVariable.equals("String to compare to")
because in the first example you avoid getting a NullPointerException if stringVariable is null. It just returns false.
Yes, it can be done. It's very simple; in one class you have your buttons, in the other class you just need to implement an ActionListener and just make your //cmd
to separate that button's function. To do this, you need to use e.getActionCommand().equals(buttonActionCommand).
Sample code:
public class Click implements ActionListener{
public Click(
//input params if needed
){
}
public void actionPerformed(ActionEvent e) {
if( e.getActionCommand().equals(buttonActionCommand){
//cmd
}
}
}
To add that listener on your button just do:
buttonTest.addActionListener(new Click());

Categories