Java - best way to deal with a large GUI constructor? - java

I find that when making gui application in Java, the constructor of my GUI class becomes very long if i dont abstract/extract it away to other classes or methods to shorten it... What is the best/most logical/least messy way of dealing with a large gui constructor? I have gathered two of the most common ways i use to deal with this... What would be the best approach, and more importantly, why/why not?
Method 1, organizing into classes for each gui component, where each class extends its GUI component:
public class GUI extends JFrame{
public GUI(String title){
super(title);
this.setVisible(true);
this.setLayout(new GridBagLayout());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(500,500);
this.add(new mainPanel());
}
private class mainPanel extends JPanel{
private mainPanel(){
this.setSize(new Dimension(500,500));
this.setLayout(new BorderLayout());
this.add(new PlayButton("Play Now"));
}
private class PlayButton extends JButton{
private PlayButton(String text){
this.setText(text);
this.setSize(150,50);
this.setBackground(Color.WHITE);
this.setForeground(Color.BLACK);
}
}
}
}
Method 2: using initialization methods, and methods that return instances of each gui component:
public class GUI extends JFrame{
public GUI(String title){
super(title);
this.setVisible(true);
this.setLayout(new GridBagLayout());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(500,500);
initGuiComponents();
}
private void initGuiComponents(){
this.add(mainPanel());
}
private JPanel mainPanel(){
JPanel mainPanel = new JPanel();
mainPanel.setSize(new Dimension(500,500));
mainPanel.setLayout(new BorderLayout());
mainPanel.add(playButton("Play Now"));
return mainPanel;
}
private JButton playButton(String text){
JButton button = new JButton();
button.setText(text);
button.setSize(150,50);
button.setBackground(Color.WHITE);
button.setForeground(Color.BLACK);
return button;
}
}

I think using combination of both would be a good idea.
Instead of using inner classes, having top level classes might make the code more maintainable. You could divide the frame into small panels based on functionality and responsibility. If your segregation is good enough, they will be loosely coupled and you would not need to pass many arguments to them.
At the same time, if the constructor or any method is growing out of proportions, clubbing tightly related operations into private methods might be helpful to make the code readable.
Beautiful programs result from high quality abstraction and encapsulation.
Even though achieving those needs practice and experience, sticking to SOLID principles should always be your first priority.
Hope this helps.
Good luck.

Perhaps look into using the builder pattern.

If you use the same settings on many components (i.e. every Button has the same BG/FG color), use a factory:
class ComponentFactory{
static JButton createButton(String text) {
JButton button = new JButton();
button.setBackground(Color.WHITE);
button.setForeground(Color.BLACK);
}
}
Then, in your constructors, you can adjust the non-constant settings:
JButton button = ComponentFactory.createButton();
button.setText(text);
[...]
The advantage with this is that you only need to change a setting (like BG color) once to change the look of all buttons.
For keeping the constructors short, I usually split the process to createChildren(), layoutComponents(), registerListeners() and whatever else might seem useful. I then call these methods from a constructor in an abstract superclass. As a result, many subclasses don't require a constructor at all - or a really short one calling super() and doing some custom settings.

Related

BoxLayout and this reference escape

In a basic GUI I'm creating I'm trying to extend JPanel and setting BoxLayout as a layout. Here's what I'm trying to do:
public class TestPanel extends JPanel {
public TestPanel() {
super();
this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
}
}
I've recently discovered that this cannot be used as a parameter until the current instance is fully constructed; yet, I've seen this kind of code frequently in examples I've found around the net. Does it mean I need to do something like this to be sure everything is going as expected?
public class TestPanel extends JPanel {
private TestPanel() {
super();
}
public static TestPanel create() {
TestPanel panel = new TestPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
return panel;
}
}
EDIT: To be more clear, here's the issue I'm referring to. I'm not sure those consideration apply to my case, but I would've thought so.

How can a component on a panel alert other components outside the panel when a value changes?

I am writing a Swing program in which all panels are declared as separate classes and a JFrame that holds the panels. The input panel has a subpanel that has a number of buttons on it. When when one of those buttons is clicked and a value is calculated, I would like to pass that value back to the JFrame holding the input panel, so that the value can be used in a number of other calculations.
I know that I can just use the JFrame ActionListener to be directly responsible for the buttons on the sub panel, but that seems to violate either portability, encapsulation, or both. I want the sub panel to be able to work on its own and let the JFrame using it be aware when a certain value changes.
I would prefer to keep the value as a primitive, but if only an object works, I can go with that.
The program has already been written as one class and it worked fine, but a beast to edit. When I was done, I realized that it was a mistake not to break it down into six panels each as their own class. A lesson for future projects. So, I am rewriting it and running into the problem that the JFrame is dependent on knowing when one of its panels (actually a panel on that panel) recalculates a value. The value calculation isn't a problem, but the JFrame needs to know when it changes.
I hope that is clear. It seemed pretty clear to me! :-)
The following code is a simplification of what I'm trying to do.
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
public class ChangeState extends JFrame {
// This is to receive the value when changed in ButtonPanel
private JTextField answer = new JTextField(5);
private InputPanel inputPanel = new InputPanel();
public ChangeState() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(inputPanel, BorderLayout.CENTER);
add(answer, BorderLayout.SOUTH);
pack();
setVisible(true);
}
public static void main(String[] args) {
ChangeState mf = new ChangeState();
}
}
class InputPanel extends JPanel {
private ButtonPanel buttonPanel = new ButtonPanel();
InputPanel() {
add(buttonPanel);
}
}
class ButtonPanel extends JPanel implements ActionListener {
private int value;
JButton b1 = new JButton("Value *2");
JButton b2 = new JButton("Value *3");
public ButtonPanel() {
value = 1;
b1.addActionListener(this);
add(b1);
b2.addActionListener(this);
add(b2);
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == b1) {
value *= 2;
}
if (e.getSource() == b2) {
value *= 3;
}
System.out.println("Value: " + value);
/*
* How do I let ChangeState.answer know VALUE has changed and needs
* to be updated?
*/
}
}
You can make ChangeState an observer of your ButtonPanel, by being an ActionListener listening to the ButtonPanel, or by being some other custom observer that's more abstract which you can create.
I'd recommend a more abstract custom observer when the class observing is not another GUI class with only one event, but in this case since it is perhaps you can just use an ActionListener (on the other hand, ChangeState is not much of a GUI class, you could have made it be composed of a JFrame).
If you have more than one event that could be passed through that ActionListener, you will probably then need to make ChangeState implement a more abstract observer implementation so you can better distinguish between events. Otherwise you'll need to have ChangeState know the name of some of the different GUI components that created the event's, or have a reference to them which would not be that great design wise.

adding Jlabel to a a panel from an extended class

I have a default window and its constructor and know what I want all my windows to setup like. Then I want to customize the rest of the windows with their respective interfaces.
import javax.swing.*;
import java.awt.*;
public class Default_window extends JFrame {
protected BorderLayout layout = new BorderLayout();
public Default_window(){
setTitle("My Program");
setLayout(layout);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension Window_size = new Dimension(500,500);
JPanel mypanel= new JPanel();
add(mypanel, BorderLayout.CENTER);
getContentPane();
setResizable(false);
setSize(Window_size);
setVisible(true);
}
}
this is the custom screen I am trying to create:
import javax.swing.*;
public class Login_screen extends Default_window{
JLabel user_name = new JLabel("Username: ");
public Login_screen(){
// insert way to add Jlabels to the panel here.
}
}
But I can't just say add(user_name). It doesnt know where to put it.
Any help is gladly appreciated. I am arecent college grad and no one around me knows anything about programming. #forever_alone
Default_window is extending JFrame, which is a top level container. You have to add your components to your JPanel which you named mypanel.
Try to create a 3rd class that extends JPanel, for example Default_panel and add your components there. Let Login_screen extend Default_panel, and then you will be able to call add(...).
Inheritance in combination with creating a rather complex panel can be very difficult, especially if you want great flexibility (putting plugin components anywhere between the default components). I would therefore suggest to create some GUI builder class, which is responsible for collecting all component constraints, and when every component has been "registered", it can create the JPanel with a LayoutManager of your choice.
If you can avoid a pluggable GUI, avoid it.

How add JPanel in Frame

In my question, this is Refectoring Class. In this class, i'm using JPanel for adding button by a separate Method panel() and Call in the RefectClass Constactor.
public class RefectClass extends JFrame {
JButton btnHome,btnBack;
JPanel btnContainer;
public RefectClass(){
super("Title Here");
setSize(350,300);
add(this.panel(),BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void panel(){
btnContainer=new JPanel();
btnHome=new JButton("Home");
btnBack=new JButton("Back");
btnContainer.add(btnHome);
btnContainer.add(btnBack);
}
}
Now how to add panel to JFrame? it gives an error, when i try to use it. I'm unable use to the panel() Mehtod.
Your panel() method returns void - it should return your newly created JPanel instead such that you can add it to the JFrame:
public JPanel panel(){
btnContainer=new JPanel();
btnHome=new JButton("Home");
btnBack=new JButton("Back");
btnContainer.add(btnHome);
btnContainer.add(btnBack);
return btnContainer;
}
Let me point out, though, that this is a very unusual approach. You shouldn't have to initialize a member in a public method that returns the newly initialized member when the method is actually just needed inside the class. Imagine a client calling the panel method multiple times from outside (since it is public). BtnContainer, btnHOme and BtnBack would be overriden but they wouldn't appear on the screen.
The method seems fairy simply - why not just include it directly in your constructor?
Or at least make the method private.
Your panel() method doesn't return anything so you can't write
add(this.panel(),BorderLayout.CENTER);
try
this.panel();
add(this.btnContainer,BorderLayout.CENTER);

Using actionListener with Button

I am programming a menu to be used with a formerly all text based game. I am trying to use addActionListener to print a line of text when a button is clicked, so I can figure out how to implement my main code in the future. The issue I am having is with the addActionListener method on my JButton. I am performing all of this with a JFrame. From what others say, I have used this as the argument but am getting a "non-static variable this cannot be referenced from a static context" error. Here is my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Menu
{
public static void Menu()
{
JButton button = new JButton("Click to enter");
button.setBounds(125, 140, 150, 20);
JFrame frame = new JFrame("Casino");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(button);
button.addActionListener(this);
JLabel emptyLabel = new JLabel("");
emptyLabel.setPreferredSize(new Dimension(400, 300));
frame.getContentPane().add(emptyLabel, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
#Override
String s = "Welcome!";
System.out.println(s);
}
}
Currently your program is nothing more than a single static method with everything trying to be shoehorned into this method. This would be fine if you were creating the most basic console program, such as one that asks the user for 2 numbers, then adds the numbers and returns the answer, but you are no longer trying to do this. Instead you're trying to create a Swing GUI program, one whose state you wish to change if the user interacts with it in an event-driven way, in other words you want it to change state if the user presses a button or selects a menu item.
Your problem is that you're trying to mesh this simple static world with the "instance" world, but in static land, there is no this.
Since your needs and requirements are becoming more complex, your program structure will need to change to reflect this. Is this an absolute requirement that you do this? No -- something called Turing Equivalence tells that it is possible to write most complex program imaginable inside of a single static main method, but due to the increased complexity, the program would become very difficult to understand and almost impossible to debug.
What I recommend specifically is that you create one or more well behaved object-orient classes, classes with non-static variables and non-static methods, and use these to build your GUI and its model (the non-GUI nucleus that GUI programs should have). Again the main method should be short, very short, and should only involve itself in creating the above classes, and setting the GUI visible, and that's about it.
What you want to do is to study the basic concepts of Java, and in particular that on how to create Java classes. The Java tutorials can help you with this.
There are a number of issues with this class.
It does not implement ActionListener so it can't be used as parameter to JButton
The static modifier on the Menu method means you can even use this anyway
public static void Main is not a constructor, so beware
#Override should appear before the method declaration, not in it.
Something like...
public class Menu implements ActionListener
{
public Menu()
{
JButton button = new JButton("Click to enter");
button.setBounds(125, 140, 150, 20);
JFrame frame = new JFrame("Casino");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(button);
button.addActionListener(this);
JLabel emptyLabel = new JLabel("");
emptyLabel.setPreferredSize(new Dimension(400, 300));
frame.getContentPane().add(emptyLabel, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e)
{
String s = "Welcome!";
System.out.println(s);
}
}
Might be a better approach, but I'd be worried about creating a JFrame within the class, but that's just me...

Categories