I have simple Swing GUI with main window JFrame and its main panel derive from JPanel. The panel has some buttons that can be clicked and generate events.
I want these events affect data stored in JFrame because it is my main application - it has some queues for thread, open streams and so on.
So how do I make my button in panel invoke callbacks in its parent frame? What is best practice of this for Java/Swing?
To invoke methods in the parent frame you need a reference to the parent frame. So your JPanel's constructor can be declared like this:
public MyPanel(MyFrame frame){
super();
this.frame = frame;
//the rest of your code
}
And in the JFrame you invoke this constructor like this:
panel = new MyPanel(this);//this refers to your JFrame
In the event handlers attached to your buttons you now have access to the frame and can invoke the various methods as needed.
button1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
//do some stuff
frame.someMethod();//invoke method on frame
//do more stuff
}
});
Have a look on this tutorial for using SwingWorker.
Use addActionListener method on desired buttons specifying the class implementing ActionListener.
ActionListenerClass actionListenerObject = new actionListenerClass();
JButton b = new JButton("Button");
b.addActionListener(actionListenerObject);
public class ActionListenerClass implements ActionListener(){
//or better : actionListenerClass extends AbstractAction
public void actionPerformed(ActionEvent e) {
}
}
EDIT:
Yes, I know this. But the action
listener I want to be in parent JFrame
class - this is the problem
then extends JFrame class making the new derived class implementing the desired interface.
You can implement the ActionListener in your class that has the JFrame (or extends it):
class MyPanelClass {
public MyPanelClass(ActionListener al)
{
//...
JButton myButton = new JButton("Button");
myButton.addActionListener(al);
//...
}
}
class MainClass extends JFrame implements ActionListener {
public void someMethod() {
MyPanelClass mpc = new MyPanelClass(this);
}
#Override
public void ActionPerformed(ActionEvent ev) {
// your implementation
}
}
Related
I've been learning quite a lot in Java recently but something has been really bugging me. I learned / was taught how to use ActionListeners when the program involves a constuctor, for example,
public class test extends JFrame implements ActionListener {
JButton button;
public test
{
setLayout(null);
setSize(1920,1080);
setTitle("test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button = new JButton("");
button.setBounds(x,x,x,x);
button.AddActionListener(this); //What can replace the this parameter here.
button.setVisible(true);
add(button);
}
public static void main(String[] args) {
test testprogram = new test();
test.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent clickevent) {
if (clickevent.GetSource() == button) {
//DoSomething
}
}
It can be anything which implements ActionListener.
You might want to consider not making your JFrame implement ActionListener: this means that
It is part of the class' interface that it implements actionPerformed; but you probably don't want other classes to call that directly.
You can only implement it "once", so you end up having to have conditional logic to determine what the source of the event was, and then handle it appropriately.
The alternative is to create a button-specific action listener:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent clickevent) {
// Don't need to check if it is from button, nothing else
// could have created the event.
}
});
and remove implements ActionListener from the test class.
It is instance of class which is going to handle ActionEvent.
From the Documents
Register an instance of the event handler class as a listener on one
or more components. For example:
someComponent.addActionListener(instanceOfMyClass);
Here's an abrevated version of what my code looks like:
public class ColorFactory extends JFrame {
public ColorFactory(){
buildTopPanel();
}
public void buildTopPanel(){
JPanel topPanel = new JPanel();
this.add(topPanel, BorderLayout.NORTH);
}
}
As you can see I have a method that makes a new JPanel object when called. How can I access that particular JPanel object from another class? I have a button listener class that I want to change the color of the JPanel from outside the ColorFactory class. This code is right after the ColorFactory class.
public class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
//Change JPanel color here.
}
}
Would it be better just to instantiate JPanel in the ColorFactory constructor and then just access it through there?
For starters, you need to make the JPanel a field in ColorFactory, so references to it don't disappear when you exit buildTopPanel(). Once you've saved a reference to it, then you have a couple of choices. From the design standpoint, the bad choice is to expose it, e.g.:
JPanel getTopPanel(){
return topPanel;
}
The better choice is to have your action listener send a message to ColorFactory that says "respondToButton(Color newColor)", and have ColorFactory decide to change topPanel's color... e.g.:
public void respondToButton(Color newColor){
topPanel.setBackground(newColor);
}
You are facing a design issue; in general, this type of situations require more investigation to understand how to end up with a clean and maintainable design.
However, For the specific problem you are reporting, I would:
Create a constructor of ButtonListener that receives a parameter (i.e. the ColorFactory) which could access the information you need, so that you can initialize a field in ButtonListener itself
Create a method changeColor in the ColorFactory. This method actually applies the color change
In the ButtonListener, invoke changeColor on the field, i.e. the reference to the ColorFactory
You should make the JPanel a field of the class like this:
public class ColorFactory extends JFrame {
JPanel topPanel;
public ColorFactory(){
buildTopPanel();
}
public void buildTopPanel(){
topPanel = new JPanel();
this.add(topPanel, BorderLayout.NORTH);
}
public void changeColor(Color color) {
//color changing code here
}
}
now You can get the JPanel from another class.
All you have to do now, is get the ColorFactory into your Button listener:
public class ButtonListener implements ActionListener{
ColorFactory colorFactory;
public ButtonListener(ColorFactory colorFactory) {
this.colorFactory = colorFactory;
}
public void actionPerformed(ActionEvent e) {
colorFactory.changeColor(/* color here */);
}
}
firstly apologies if the title is brief, I've thought it over but can't come up with a summary short enough for my question.
I have a JPanel class which consists of JButtons.
I have my main Swing application class, which has Swing components, AS WELL AS the JPanel class. What I want to do is for the ActionEvents fired from my JPanel class to be dispatched to my Swing application class to process. I've searched examples on the net and forums (including this one), but can't seem to get it to work.
My JPanel class:
public class NumericKB extends javax.swing.JPanel implements ActionListener {
...
private void init() {
...
JButton aButton = new JButton();
aButton.addActionListener(this);
JPanel aPanel= new JPanel();
aPanel.add(aButton);
...
}
...
#Override
public void actionPerformed(ActionEvent e) {
Component source = (Component) e.getSource();
// recursively find the root Component in my main app class
while (source.getParent() != null) {
source = source.getParent();
}
// once found, call the dispatch the current event to the root component
source.dispatchEvent(e);
}
...
}
My main app class:
public class SimplePOS extends javax.swing.JFrame implements ActionListener {
private void init() {
getContentPane().add(new NumericKB());
pack();
}
#Override
public void actionPerformed(ActionEvent e) {
...
// this is where I want to receive the ActionEvent fired from my NumericKB class
// However, nothing happens
}
}
The reason for wanting to write a separate JPanel class is because I want to reuse this in other apps.
Also, the actual code, my main app class has many subcomponents which and the JPanel class is added into one of the subcomponents, thus the recursive .getParent() calls.
Any help would be much appreciated. Thank in advance! Cheers.
You cannot rethrow the event to parent because the parent has no support for delivering of ActionEvents. But in your case you can simply check whether your component has action support and call it. Something like this
public class NumericKB extends javax.swing.JPanel implements ActionListener {
...
private void init() {
...
JButton aButton = new JButton();
aButton.addActionListener(this);
JPanel aPanel= new JPanel();
aPanel.add(aButton);
...
}
...
#Override
public void actionPerformed(ActionEvent e) {
Component source = (Component) e.getSource();
// recursively find the root Component in my main app class
while (source.getParent() != null) {
source = source.getParent();
}
// once found, call the dispatch the current event to the root component
if (source instanceof ActionListener) {
((ActionListener) source).actionPerformed(e);
}
}
...
}
anyone know or have an idea as to why my button disappears after i resize the applet?
this is my code:
import java.awt.event.*;
import javax.swing.*;
import acm.program.*;
public class button extends ConsoleProgram {
public void init(){
hiButton = new JButton("hi");
add(hiButton, SOUTH);
addActionListeners();
}
public void actionPerformed(ActionEvent e){
if(hiButton == e.getSource()){
println("hello") ;
}
}
private JButton hiButton;
}
I'm not sure if it is a good Idea to redefine the init-method. When I have a look at http://jtf.acm.org/javadoc/student/acm/program/ConsoleProgram.html I would expect that you have implement only the run-method. Overriding init without calling super.init() Looks strange to me.
Maybe I would be better to derive from JApplet directly for your first steps in Applet programming.
Assuming that
your ConsoleProgram extends (directly or indirectly) JApplet
You declared SOUTH as a static final variable that has the value BorderLayout.SOUTH (otherwise your code doesn't compile)
The code should work, no need to repaint (unless you would like to do some application-specific optimization). I just copied and pasted your code (by expliciting the two assumptions above), I see the applet and the button doesn't disappear on resize.
Anyway there are few "not good" things in the code:
First of all, a naming convention issue: the class name should be "Button" with the first letter capitalized (on top of that, it's a poor name for an Applet)
Second, action listeners should be attached before adding the component;
Third, as Oracle doc suggests here, the code that builds the GUI should be a job that runs on the event dispatcher thread. You can do that by wrapping the build gui code in a Runnable using a SwingUtilities.invokeAndWait(Runnable()
Have you tried calling super.init() at the start of your init() method?
Try explicitly using a layout for your Console and then use relative positioning.
To re-size a button in Applet:
public class Button extends JApplet implements ActionListener {
private JButton button;
public void init() {
Container container = getContentPane();
container.setLayout(null);
container.setBackground(Color.white);
button = new JButton("Press Me");
button.setSize(getWidth()/2,20);
button.setLocation(getWidth()/2-button.getSize().width/2, getHeight()/2-button.getSize().height/2);
container.add(button);
button.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
int width = (button.getSize().width == getWidth()/2) ? getWidth()/4 : getWidth()/2;
int height = button.getSize().height;
button.setSize(width,height);
button.setLocation(getWidth()/2-width/2, getHeight()/2-height/2);
}
}
To re-size a button in JFrame:
public class Button extends JFrame implements ActionListener {
private JButton button;
public Button(String title) {
Container container = getContentPane();
container.setLayout(null);
container.setBackground(Color.white);
setTitle(title);
setSize(400,400);
button = new JButton("Press Me");
button.setSize(getWidth()/2,20);
button.setLocation(getWidth()/2-button.getSize().width/2,
getHeight()/2-button.getSize().height/2);
container.add(button);
button.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
int width = (button.getSize().width == getWidth()/2) ? getWidth()/4 : getWidth()/2;
int height = button.getSize().height;
button.setSize(width,height);
button.setLocation(getWidth()/2-width/2, getHeight()/2-height/2);
}
public static void main(String[] args) {
Button button = new Button("Test");
button.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.setVisible(true);
}
}
Have you declared the repaint method...???
You are using swing. It needs to have declared a repaint.
Please define a custom repaint mwthod
Say, you have a subclass of JFrame, and use it to create your own custom JFrame. In this class (we'll call it mainFrame), we create a reference to another custom JFrame class (we'll call this one sidePanel).
In sidePanel, you have different buttons, radio buttons,..
My question is, is there a way to notify mainFrame the user presses on a button?
I've created a (untested) example of what I mean:
class mainFrame extends JFrame {
public mainFrame() {
super("main frame");
//...........
sidePanel panel = new sidePanel();
//...........
}
public static void main(String[] args) {
mainFrame mainF = new mainFrame();
//.........
}
}
And the sidePanel class:
class sidePanel extends JFrame {
public sidePanel() {
super("sidePanel frame");
//...........
JButton button1 = new JButton();
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
//Notify mainFrame somehow button is pressed
}});
//...........
}
}
To notify mainFrame of an event, the SidePanel instance (really bad name for a Frame) must have a reference to mainFrame. Pass mainFrame as an argument of the SidePanel constructor, and callback mainFrame from the actionPerformed method in SidePanel:
SidePanel panel = new SidePanel(this);
and in SidePanel:
public void actionPerformed(ActionEvent e) {
mainFrame.buttonHasBeenClicked();
...
}
This tightly couples both classes though. A way to decouple them is to make the SidePanel object accept listeners for custom events, and to fire such an event when the button is clicked. The mainFrame would construct the SidePanel instance, and add itself (or an inner anonymous class instance) as a listener to the sidePanel.
See this page for an example.