I'm a little confused about where the best class is for ActionListener instances in a small Swing application.
If I have a Controller, a MainFrame for the main JFrame and various JPanel containers such as LeftPanel, RightPanel etc, each with buttons, a list etc.
Where's the best place to put the action listeners? Should I have them in the same class as the components (as an inner class or class implementing action listener), in the MainFrame as this is the 'parent' of all the panels, or in the Controller, which really only has the main() method and a few other Swing details?
Each approach seems to have its pros and cons. I am trying to un-couple features from functionality but I'm finding it hard to work this out.
Or have I missed another (better) way?
One possible for the panels to communicate with each other is through the main frame. Each panel would have a reference to the main frame and the main frame provides all the operations such as reading data from the form, updating data to the form. Below is an example:
class UserForm extends JPanel {
private final MyApp app;
private final JTextField userNameField = new JTextField(20);
public UserForm(MyApp app){
this.app = app;
this.add(userNameField);
}
public String getUserName(){
return userNameField.getText();
}
public void setUserName(String name){
userNameField.setText(name);
}
}
class FormControl extends JPanel {
private final MyApp app;
private final JButton randNameButton = new JButton("Random");
private final JButton saveButton = new JButton("Save");
private final String[] names = {"john", "mike", "bill", "joe"};
private final Random r = new Random();
public FormControl(MyApp app){
this.app = app;
this.setLayout(new FlowLayout());
this.add(randNameButton);
this.add(saveButton);
randNameButton.addActionListener(e->app.setUserName(names[r.nextInt(4)]));
saveButton.addActionListener(e->app.save());
}
}
public class MyApp extends JFrame{
private final UserForm form = new UserForm(this);
private final FormControl control = new FormControl(this);
public MyApp(){
this.setTitle("Demo");
this.setSize(200, 100);
JPanel pane = new JPanel();
pane.setSize(200,100);
pane.setLayout(new BorderLayout());
pane.add(form, BorderLayout.NORTH);
pane.add(control, BorderLayout.SOUTH);
this.setContentPane(pane);
}
// all the operations are here.
public String getUserName(){
return form.getUserName();
}
public void setUserName(String userName){
form.setUserName(userName);
}
public void save(){
System.out.println("Saving");
System.out.println(getUserName());
System.out.println("Done saving.");
}
public static void main(String[] args) {
new MyApp().setVisible(true);
}
}
I would use lambda for action listener. See example below:
public class JavaApplication7 extends JFrame{
public JavaApplication7(){
this.setTitle("Demo");
this.setSize(100, 100);
JButton b = new JButton("Click");
b.addActionListener(e->{
System.out.println("hello");
System.out.println("from " + this.getTitle());
});
this.setContentPane(b);
}
public static void main(String[] args) {
new JavaApplication7().setVisible(true);
}
}
Related
I am making application, using IDEA Forms. I want to have different forms. First for login, then for displaying information from database after login. I created two different forms. First one: MenuMain and second AdminForm. After clicking a login button in MenuMain I want to open AdminForm, I tried with setContentPane and passing main panel from AdminForm, but it does not work. How to solve this?
public class MenuMainForm extends JFrame implements ActionListener {
private JPanel panel1;
private JTextPane logowanieText;
private JTextField userField;
private JTextField passField;
private JButton logowanieButton;
public MenuMainForm(){
logowanieButton.addActionListener(this);
public void actionPerformed(ActionEvent e) {
AdminForm adminForm = new AdminForm();
this.setContentPane(adminForm);
}
public static void main( String[] args){
JFrame menuFrame = new JFrame("Kawiarnia");
menuFrame.setContentPane(new MenuMainForm().panel1);
menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
menuFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
menuFrame.setVisible(true);
}
And the AdminForm Code:
public class AdminForm extends JPanel{
private JPanel adminPanel;
private JButton infoKawiarniaButton;
private JTable tabelaInfo;
private static String QUERY_INFO = "SELECT * FROM NATALIAGAZDA.KAWIARNIE ";
public AdminForm() {
DefaultTableModel model = (DefaultTableModel) tabelaInfo.getModel();
model.addRow(new Object[]{"Atrybut", "Wartosc"});
public JPanel getAdminPanel() {
return adminPanel;
}
So I'm new to programming and I've been making a program that uses multiple JPanels on a JFrame and other JPanels. I'm using CardLayout to go between different JPanels and I have it working on two different JButtons, but I can't get the last one to return to the main screen.
I've looked for answers, but it seems like most people just forget to use an ActionListener, something that I know I've done. Here's some of the involved classes in my code (There are a lot so I won't include them all, but I can provide any others that are needed).
Here's the JFrame class:
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.*;
public class ElephantCare extends JFrame {
private static final long serialVersionUID = 1L;
private final String MAIN_STRING = "Main";
public JPanel container, main;
private Staff1Panel staff1 = new Staff1Panel();
private Staff2Panel staff2 = new Staff2Panel();
private StaffConfirmPanel staffConfirm = new StaffConfirmPanel();
private WelcomePanel welcome = new WelcomePanel();
private StaffPanel staff = new StaffPanel();
private GallonsPanel gallons = new GallonsPanel();
private ToysPanel toys= new ToysPanel();
private ActionPanel action = new ActionPanel();
public CardLayout card = new CardLayout();
public ElephantCare() {
setSize(400,300);
setTitle("Elephant Care");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
buildPanel();
add(container);
setVisible(true);
}
private void buildPanel() {
main = new JPanel();;
container = new JPanel(card);
container.add(main, MAIN_STRING);
container.add(staff1, "Staff 1");
container.add(staff2, "Staff 2");
main.setLayout(new BorderLayout());
main.add(welcome, BorderLayout.NORTH);
main.add(staff, BorderLayout.WEST);
main.add(gallons, BorderLayout.CENTER);
main.add(toys, BorderLayout.EAST);
main.add(action, BorderLayout.SOUTH);
staff.getStaff1Button().addActionListener(new Staff1Listener());
staff.getStaff2Button().addActionListener(new Staff2Listener());
staffConfirm.getConfirmButton().addActionListener(new ConfirmButtonListener());
}
private class Staff1Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, "Staff 1");
}
}
private class Staff2Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, "Staff 2");
}
}
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, MAIN_STRING);
}
}
}
Here's the JPanel with the Button:
import javax.swing.*;
public class StaffConfirmPanel extends JPanel{
private static final long serialVersionUID = 1L;
public JButton confirm;
public StaffConfirmPanel() {
confirm = new JButton("OK");
add(confirm);
}
public JButton getConfirmButton() {
return confirm;
}
}
And here's the JPanels where the button is used:
import java.awt.BorderLayout;
import javax.swing.*;
public class Staff1Panel extends JPanel{
private static final long serialVersionUID = 1L;
private Staff1NamePanel name = new Staff1NamePanel();
private Staff1JobPanel job = new Staff1JobPanel();
private StaffConfirmPanel confirm = new StaffConfirmPanel();
public Staff1Panel() {
setLayout(new BorderLayout());
add(name, BorderLayout.WEST);
add(job, BorderLayout.EAST);
add(confirm, BorderLayout.SOUTH);
}
}
And:
import java.awt.BorderLayout;
import javax.swing.*;
public class Staff2Panel extends JPanel{
private static final long serialVersionUID = 1L;
private Staff2NamePanel name = new Staff2NamePanel();
private Staff2JobPanel job = new Staff2JobPanel();
private StaffConfirmPanel confirm = new StaffConfirmPanel();
public Staff2Panel() {
setLayout(new BorderLayout());
add(name, BorderLayout.WEST);
add(job, BorderLayout.EAST);
add(confirm, BorderLayout.SOUTH);
}
}
Thanks for any help!
There are a lot of things happening in this code that are not quite right, and they contribute to your issue. So lets address the biggest issue and then you will need to do the rest yourself.
First edit this code to have some debug text:
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//NEW LINE OF CODE
System.out.println("ConfirmButtonListener was triggered");
card.show(container, MAIN_STRING);
}
}
Now when you run your code you will notice that the message "ConfirmButtonListener was triggered" will never be printed to the console, meaning that the code never runs, so you can never return to the main screen.
This happens because you create a StaffConfirmPanel named staffConfirm in your ElephantCare class. Then you add an action listener to staffConfirm. The problem is that you never use that staffconfirm panel anywhere because you create a new StaffConfirmPanel inside Staff1Panel and Staff2Panel so the action listener you have made will do nothing.
So the solution is to move the whole ConfirmButtonListener method and the staffConfirm.getConfirmButton().addActionListener line into the StaffConfirmPanel class like so:
import javax.swing.*;
public class StaffConfirmPanel extends JPanel{
private static final long serialVersionUID = 1L;
public JButton confirm;
public StaffConfirmPanel() {
confirm = new JButton("OK");
add(confirm);
//NEW LINE: SET THE ACTION LISTENER
confirm.addActionListener(new ConfirmButtonListener());
}
public JButton getConfirmButton() {
return confirm;
}
//NEW CODE, MOVE THE ACTION LISTENER METHOD HERE
//ACTION LISTENER MOVED HERE
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//NEW LINE OF CODE
System.out.println("ConfirmButtonListener was triggered");
card.show(ElephantCare.container, ElephantCare.MAIN_STRING);
}
}
}
Now it should work correctly and return to the main screen.
EDIT: you will need to make MAIN_STRING a public variable in the ElephantCare class.
I have 2 classes. Both implements runnable to create the GUI. The first one is the main, and the second one is the secondary class.
I want within the actionlistener of the main class to startup the secondary class.
Here is the code (the two classes are separated files):
public class Main implements Runnable
{
private JTextField txt1, txt2;
private JLabel lbl1, lbl2;
public void run()
{
JFrame frame = new JFrame("Secondary");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container pane = frame.getContentPane();
JPanel background = new JPanel();
background.setLayout(new BoxLayout(background, BoxLayout.LINE_AXIS));
.........
// Horizontally adding the textbox and button in a Box
Box box = new Box(BoxLayout.Y_AXIS);
......
background.add(box);
pane.add(background);
frame.pack();
frame.setVisible(true);
}
private class SListener implements ActionListener
{
public void actionPerformed(ActionEvent a)
{
Secondary s = new Secondary();
}
}
public static void main (String[] args)
{
Main gui = new Main();
SwingUtilities.invokeLater(gui);
}
}
public class Secondary implements Runnable
{
private JTextField txt1, txt2;
private JLabel lbl1, lbl2;
public Secondary()
{
Secondary gui = new Secondary();
SwingUtilities.invokeLater(gui);
}
public void run()
{
JFrame frame = new JFrame("Secondary");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container pane = frame.getContentPane();
JPanel background = new JPanel();
background.setLayout(new BoxLayout(background, BoxLayout.LINE_AXIS));
.........
// Horizontally adding the textbox and button in a Box
Box box = new Box(BoxLayout.Y_AXIS);
......
background.add(box);
pane.add(background);
frame.pack();
frame.setVisible(true);
}
}
I want to keep the code in two files, I don't want to mixed the two classes in one file.
As you can see from the code, in the Secondary class, in it's constructor I create an Instance of the Secondary class and I run the gui so that when the Instance of this class is created in the Main class, to run the gui.
Unfortunately this technique is not working.
Any ideas?
Thanks
The following line are complety wrong:
public Secondary(){
Secondary gui = new Secondary();
SwingUtilities.invokeLater(gui);
}
Each time you call new Secondary() somewhere in your code, the above code will be triggered, which in turn calls new Secondary() again, and again, and again, ... and your program is blocked.
You probably want to replace it either by
public Secondary(){
SwingUtilities.invokeLater(this);
}
which will avoid the loop, but this is weird behaviour for a constructor.
It makes much more sense to switch to an empty constructor (or delete it all together)
public Secondary(){
}
and rewrite your listener to
public void actionPerformed(ActionEvent a){
Secondary s = new Secondary();
SwingUtilities.invokeLater( s );
}
I would recommend that you completely re-design your program. I find that it is most helpful to gear my GUI's towards creation of JPanels, not top level windows such as JFrame, which can then be placed into JFrames or JDialogs, or JTabbedPanes, or swapped via CardLayouts, wherever needed. I find that this greatly increase the flexibility of my GUI coding, and is exactly what I suggest that you do. So...
Your first class creates a JPanel that is then placed into a JFrame.
In the first class's ActionListener, create an instance of the 2nd class, place it into a JDialog (not a JFrame), and then display it.
For example,
import java.awt.Component;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class TwoWindowEg {
public TwoWindowEg() {
// TODO Auto-generated constructor stub
}
private static void createAndShowGui() {
GuiPanel1 mainPanel = new GuiPanel1();
JFrame frame = new JFrame("Main GUI");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class GuiPanel1 extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private GuiPanel2 guiPanel2 = new GuiPanel2(); // our second class!
private JDialog dialog = null; // our JDialog
public GuiPanel1() {
setBorder(BorderFactory.createTitledBorder("GUI Panel 1"));
add(new JButton(new LaunchNewWindowAction("Launch New Window")));
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class LaunchNewWindowAction extends AbstractAction {
public LaunchNewWindowAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
if (dialog == null) {
// get the Window that holds this JPanel
Window win = SwingUtilities.getWindowAncestor(GuiPanel1.this);
dialog = new JDialog(win, "Second Window", ModalityType.APPLICATION_MODAL);
dialog.add(guiPanel2);
dialog.pack();
}
dialog.setVisible(true);
}
}
}
class GuiPanel2 extends JPanel {
public GuiPanel2() {
setBorder(BorderFactory.createTitledBorder("GUI Panel 1"));
add(new JLabel("The second JPanel/Class"));
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
}
}
class DisposeAction extends AbstractAction {
public DisposeAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
Component comp = (Component) e.getSource();
Window win = SwingUtilities.getWindowAncestor(comp);
win.dispose();
}
}
Alternatively, you could swap JPanel "views" using a CardLayout, but either way, you will want to avoid showing two JFrames. Please have a look at The Use of Multiple JFrames, Good/Bad Practice?.
Ok, here is my problem. Class B is a class that build a GUI ,which has a textField and button. class A has an instance of class B.Now I enter some value in the textfield, when I click the button, in class A I want to print out the value I just enter in the textfield, how can I achieve that?
Code below may better explain what I want to achieve:
public class A
{
B myB = new B();
(when the JButton was clicked,
how can I get the new textfield value here?)
}
public class B
{
JLabel myLabel;
JButton myButton;
public B()
{
getContentPane().setLayout(null);
myLabel = new JLabel();
myLabel.setLocation(0,0);
myLabel.setSize(100,30);
myLabel.setBackground( new Color(-6710887) );
myLabel.setText("");
getContentPane().add(myLabel);
myButton = new JButton();
myButton.setLocation(0,50);
myButton.setSize(100,30);
myButton.setBackground( new Color(-16737895) );
myButton.setText("Submit");
getContentPane().add(myButton);
myButton.addActionListener(this);
setSize(400,400);
setVisible(true);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e)
{
(how can I pass this "myLabel.getText()" value to class A when
this action performed?)
}
}
Can anybody help me finish this little program? Thanks in advance!
You need to expose the value in text field with a method in class B. Then class A can call that method. What it actually sounds like though is that class A (or something else) should be a ActionListener for your button.
However, a bigger problem is that you don't have a text field you just have a label in class B. This code is a good reason why you shouldn't use a GUI builder, especially when learning Swing.
Some reading:
http://docs.oracle.com/javase/tutorial/uiswing/components/textfield.html
http://docs.oracle.com/javase/tutorial/uiswing/events/
I often make an "App" class that ties all my GUI-builder-built components together. Any GUI builder worth anything lets you add getters to the generated source code. Add some getters to the GUI-built components to retrieve key elements of the GUI, then let the App class use the getters to interact with the components as necessary. This won't win any MVC/MVVM/MVP design awards, but it gets the job done, which ought to count for something.
public class App {
private B _b;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
App app = new App();
app.run();
}
});
}
void run() {
_b = new B();
_b.getMainButton().addActionListener(new MainButtonListener());
_b.setVisible(true);
}
private void handleMainButtonClicked() {
String mainText = _b.getMainTextArea().getText();
System.out.println("Button clicked; main text = " + mainText);
}
public class MainButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
handleMainButtonClicked();
}
}
}
public class B extends JFrame {
private JPanel _contentPane;
private JTextArea _jTextArea;
private JButton _jButton;
public B() {
initComponents();
}
private void initComponents() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400);
_contentPane = new JPanel();
setContentPane(_contentPane);
_jTextArea = new JTextArea();
_contentPane.add(_jTextArea, BorderLayout.CENTER);
_jButton = new JButton("My Button");
_contentPane.add(_jButton, BorderLayout.SOUTH);
}
public JButton getMainButton() {
return _jButton;
}
public JTextComponent getMainTextArea() {
return _jTextArea;
}
}
The following code produces a window with buttons, but an error message pops up when I run i and actally press the button. According to the Spring tooltip:
Cannot make a static reference to the non-static method setBackground(Color) from the type JComponent
This program is literally entered from my Java textbook line for line, as far as I can tell. It's an older book, so there might be incompatibility, but it doesn't seem likely.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonTest
{
public static void main(String[] args)
{
final ButtonFrame frame = new ButtonFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
class ButtonFrame extends JFrame
{
public ButtonFrame()
{
setTitle("Button Test");
setSize(Default_width, Default_height);
//panel
ButtonPanel panel = new ButtonPanel();
Container contentPane=getContentPane();
contentPane.add(panel);
}
public static final int Default_width = 300;
public static final int Default_height = 200;
}
class ButtonPanel extends JPanel
{
public ButtonPanel()
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
ColorAction yellowAction= new ColorAction(Color.YELLOW);
ColorAction redAction = new ColorAction(Color.RED);
ColorAction blueAction = new ColorAction(Color.BLUE);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
}
class ColorAction implements ActionListener
{
public ColorAction(Color c)
{
backgroundColor=c;
}
public void actionPerformed(ActionEvent event)
{
ButtonPanel.setBackground(backgroundColor);
}
private Color backgroundColor;
}
One approach is to nest ColorAction as an inner class in ButtonPanel, where it has implicit access to the enclosing panel.
Addendum: As noted in comments by #Andrew Thompson and #nachokk, the implicit accessibility can be made explicit by qualifying this using the enclosing class name. See JLS ยง15.8.4. Qualified this for details. In this example, these two invocations are equivalent:
setBackground(backgroundColor);
ButtonPanel.this.setBackground(backgroundColor);
As an more general alternative, consider encapsulating the target panel and color in an Action, as outlined here.
class ButtonPanel extends JPanel {
public ButtonPanel() {
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction redAction = new ColorAction(Color.RED);
ColorAction blueAction = new ColorAction(Color.BLUE);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
private class ColorAction implements ActionListener {
public ColorAction(Color c) {
backgroundColor = c;
}
#Override
public void actionPerformed(ActionEvent event) {
setBackground(backgroundColor);
}
private Color backgroundColor;
}
}
ButtonPanel.setBackground()
is not a static method so you can't call it as one. You need a concrete instance of ButtonPanel to set the background.
ButtonPanel bp = new ButtonPanel();
bp.setBackground(backgroundColor);
Also change in look and feel can help:
//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());