1

I'm trying to make a button that toggles the state of the button (changes color and text) when pressed. But I can't get the addActionListener to work and I can't find the ToggleState method from my main class. I am new to java and would appreciate your help.

I get the errors - “The method addActionListener(ActionListener) in the type AbstractButton is not applicable for the arguments” - "ActionListener cannot be resolved to a type"

package Grafiktest;

import javax.swing.*;
import java.awt.*;

public class coolgrafik extends JFrame implements ActionListener {

private static final long serialVersionUID = 1L;
private JPanel panel;
private JLabel label;

public coolgrafik(){

    //super(title);
    setSize(400,60);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    panel = new JPanel();
    panel.setBackground(Color.GRAY);
    label = new JLabel("test");
    panel.add(label);

    MyButton button = new MyButton(Color.green, Color.red, "RUN","STOP");
    panel.add(button);
    button.addActionListener(this);

    add(panel);
}

public static void main(String[] args){
    new coolgrafik().setVisible(true);
    //toggleState(Color.green, Color.red, "RUN","STOP");
}

public void actionPerformed(ActionEvent e) {
    toggleState(Color.green, Color.red, "RUN","STOP");
}
}

and this is the button class

package Grafiktest;

import javax.swing.*;
import java.awt.*;

public class MyButton extends JButton{

private static final long serialVersionUID = 1L;

public MyButton(Color c1, Color c2, String s1, String s2){

    setText(s1);
    setForeground(c1);
    setBackground(c1);
    setOpaque(true);

}

public void toggleState(Color c1, Color c2, String s1, String s2){

    setText(s2);
    setForeground(c2);
    setBackground(c2);
    setOpaque(true);

}

}

G.Dog
  • 23
  • 3
  • well you didn't import the class ActionListener, since it's not in `java.awt.*` but in `java.awt.event.ActionListener` - for reference https://docs.oracle.com/javase/7/docs/api/java/awt/event/ActionListener.html - also http://stackoverflow.com/questions/12131195/shouldnt-import-foo-also-include-subpackage-foo-bar – Japu_D_Cret Mar 29 '17 at 07:19
  • Haha thank you, that little line solved my actionlistener problem. But I still can't use my toggleState method from the actionperformer. How come? – G.Dog Mar 29 '17 at 07:25
  • It might actually be better for `MyButton` to monitor its own model state – MadProgrammer Mar 29 '17 at 07:32

3 Answers3

2

Let's just stop a second and think about what you're trying to do. You want to change the state of the button when it's pressed.

The real question is, who's actually responsible for doing that? Any ActionListener monitoring the button should be focused on carrying out the work they need to do, not managing the button as well, what happens when you have more than one ActionListener attached to the button? Who becomes responsible for it then?

What about if, instead, we made the button self managing? That is, the button monitors itself and update its state accordingly

Because you'd need to manage the selected state anyway, I've used a JToggleButton instead, as it has a "selected"/"unselected" state internally.

public class MyButton extends JToggleButton {

    private static final long serialVersionUID = 1L;

    public class State {
        public Color color;
        public String text;

        public State(Color color, String text) {
            this.color = color;
            this.text = text;
        }

    }

    private final State unselectedState;
    private final State selectedState;

    public MyButton(Color c1, Color c2, String s1, String s2) {

        selectedState = new State(c1, s1);
        unselectedState = new State(c2, s2);

        setContentAreaFilled(false);
        setBorderPainted(false);
        setFocusPainted(false);
        setOpaque(true);

        toggleState();

        getModel().addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                toggleState();
            }
        });

    }

    public void toggleState() {

        State state = !getModel().isSelected() ? selectedState : unselectedState;

        setText(state.text);
        setForeground(Color.WHITE);
        setBackground(state.color);

    }
}

Basically all this does it will configure the button based on the selected state of the ButtonModel.

You could use an ActionListener and register it directly to the button itself in similar fashion, but you'd need to manage which state it's using

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

To complete @Japu_D_Cret 's comment :

You either need to make your toggleState method static to call it like you do in your actionPerformed method, or to use your button to call it :

button.toggleState(Color.green, Color.red, "RUN","STOP");

as it is a method defined in your MyButton class and not in your coolgrafik class.

Asew
  • 374
  • 2
  • 13
0

"The method addActionListener(ActionListener) in the type AbstractButton is not applicable for the arguments” - "ActionListener cannot be resolved to a type"

you didn't import the class ActionListener, since it's not in java.awt.* but in java.awt.event.ActionListener - for reference https://docs.oracle.com/javase/7/docs/api/java/awt/event/ActionListener.html

But I still can't use my toggleState method from the actionperformer. How come?

well in your void actionPerformed(ActionEvent) you try to call a method called void toggleState(Color,Color,String,String) - but it is not declared in the same class nor is it statically imported.

You obviously want to call the toggleState method on your object button of class MyButton, to do that you have to call the method on that object like button.toggeState(...) This will also not directly work, since your object button is only known in your constructor and you have to add an object attribute to your coolgrafik class, like you did with the panels - then it should work just fine

here are code fragments that you need to change in your coolgrafik class

attributes:

private JPanel panel;
private JLabel label;
private MyButton button;

in your coolgrafik constructor

//...
this.button = new MyButton(Color.green, Color.red, "RUN","STOP");
this.panel.add(this.button);
this.button.addActionListener(this);
//...

and your actionPerformed Method

public void actionPerformed(ActionEvent e) {
    button.toggleState(Color.green, Color.red, "RUN","STOP");
}
Japu_D_Cret
  • 632
  • 5
  • 18
  • 1
    It worked! But I don't fully understand how. I have read about the this keyword but I don't see how it helped in this situation. Do you mind giving me an explanation? – G.Dog Mar 29 '17 at 07:47
  • @G.Dog the `this.button` means that you explicitly want to use the object attribute `button`, you can also just use `button` and get the same result, as long as the method does not also declare a variable with name `button`. The notation with the `this`-keyword is in my opinion much more clear and less likely to break your code, as it always uses the object attribute. But it does not make your code any slower nor does it make it any faster, it's just very convenient to use and less arbituary - see also http://stackoverflow.com/questions/3728062/what-is-the-meaning-of-this-in-java – Japu_D_Cret Mar 29 '17 at 07:52
  • 1
    Okay I understand it better now. But I moved the ActionListener to the MyButton class instead. Can I make the actionPerformed method call toggleState with the parameters that the MyButton constructor takes from the MyButton call in the coolgrafik constructor? For example something like "toggleState(c1, c2, s1, s2)" – G.Dog Mar 29 '17 at 09:33
  • @G.Dog of you can can - to do that, you have to save the objects as attributes, that you get in the constructor - so for every color and string you add an attribute(`private Color c1; private String s1; //...`) and assign the value to that attributes in the constructor. Then if you want to call toggleState with those saved objects you can use `toggeState(this.c1, this.s1, ...)` – Japu_D_Cret Mar 29 '17 at 09:42
  • I defined (private Color c1; private String s1; //...) as fields over my constructor and then tried to give them values in the constructor as Color c1 = c1; Color c2 = this.c2 etc but it's not possible. Am I misunderstanding you? Error duplicate local variable c1 – G.Dog Mar 29 '17 at 11:55
  • @G.Dog apply a method parameter to an object attribute when they have the same name, you should use this: `this.c1 = c1`, if you use `Color c1 = c1` it creates a new object names c1 with value of c1(this results in an error, as you then have multiple definitions of c1) – Japu_D_Cret Mar 29 '17 at 11:57
  • Thank you, that solved my problem. But I also tried to define the fields {private Color c1_new; private Color c2_new;...} and set values to them in the constructor as {Color c1_new = c1; Color c2_new = c2;...} and then calling toggleState(c1_new, c2_new, s1_new, s2_new) but then the button goes blank when I press it. Why is that? – G.Dog Mar 29 '17 at 12:15
  • if you do this `Color c1_new = c1;` you do not assign the value c1 to the variable/object attribute c1_new, rather you create a new object which you copy the value to - in Java you create a new variable the moment you write a type in front of it, what you want to use is `c1_new = c1` or more elegantly `this.c1_new = c1` – Japu_D_Cret Mar 29 '17 at 13:11
  • I see how that is a more effective solution but I don't understand why the uglier solution isn't working. I'm thinking that the toggleState should use the newly created c1_new etc and solve the problem anyhow. Am I wrong? – G.Dog Mar 29 '17 at 16:30