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.
Related
I'm writing a programm with a complicated TabbedPane, with lots of elements. Since I hit the Byte-limit in one of my classes, I decided to split the class into Initialisation/Button Listeners and the actual GridBagLayout. But now I'm having trouble getting it to work. My Main Class looks like this:
public class Main{
JFrame mainFrame = new JFrame("");
JTabbedPane tabpane = new JTabbedPane();
JPanel panelTab1 = new Tab1();
JScrollPane scrollTab2 = new JScrollPane(new Tab2());
JScrollPane scrollTab3 = new JScrollPane(new Tab3());
JPanel panelTab4 = new Tab4();
JMenuBar bar = new MenuBar();
public Main(){
tabpane.add("Tab1", panelTab1);
tabpane.add("Tab2", scrollTab2);
tabpane.add("Tab3", scrollTab3);
tabpane.add("Tab4", panelTab4);
mainFrame.getContentPane().add(tabpane);
mainFrame.setSize(1920,1080);
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
mainFrame.validate();
mainFrame.setJMenuBar(bar);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable(){
public void run(){
new Main();
}
});
}}
Tab3 is the culprit so here are the two classes I split.
public class Tab3 extends JPanel {
JPanel Annex = new JPanel();
//A Bunch of Labels and Buttons
.
.
.
public Tab3(){
//ActionListeners for the Buttons
this.setLayout(new BorderLayout());
this.add(Annex,BorderLayout.WEST);
this.add(Bsp,BorderLayout.EAST);
}}
All the GridBagLayout is in the following class:
public class Tab3Layout extends Tab3{
public Tab3Layout(){
Annex.setLayout(new GridBagLayout());
GridBagConstraints co1 = new GridBagConstraints();
co1.gridx = 0;
co1.gridy = 0;
co1.anchor = GridBagConstraints.FIRST_LINE_START;
co1.weighty = 1.0;
Annex.add(Annex1, co1);
//and so on...
}}
Now my question is, how do I get this to work? Right now if I compile, Tab3 is just empty. If everything is in one class it works exactly how I want, but the code is just much too long. Seems to me like I'm missing a line in the Tab3 class, but even after hours of tinkering and searching I have no idea how to solve this. Everything I tried just produced more errors.
The problem is in iheritance. Tab3Layout has its own Annex JPanel (inherited from Tab3), which it is modifying and adding components to, while Tab3 class is inserting its Annex, which is initialized via new JPanel() and you do nothing else with it. Tab3Layout class never even touches Annex panel you intialize and add in Tab3 .
public class Tab3 extends JPanel {
JPanel Annex = new JPanel(); // <----- this, an empty JPanel
//A Bunch of Labels and Buttons
public Tab3(){
//ActionListeners for the Buttons
this.setLayout(new BorderLayout());
this.add(Annex,BorderLayout.WEST); // <----- is inserted here
this.add(Bsp,BorderLayout.EAST);
}}
You will get an error if you delete new JPanel(), which will illustrate that both classes are doing work on different objects, which just happen to be named the same. You need to pass all components/panels you want to appear in Tab3 to Tab3Layout to modify, then you can retrieve them later with getters. Another way would be to create AnnexPanel class, which itself would be extending JPanel class. It would set its own layout, components etc. and be able to be added directly. Following Single Responsibility Principle would be best, if your classes grow in size. If it's 200+ lines of code, you should consider refactoring it. If you will allow your class to grow like 3000+ lines of code, you will have a bad time. Try to forsee where you can create new class/method in early stages of development. See KISS principle .
Well..I would split those in different way.
My Tab3Layout class. Annex panel is injected via constructor, you can set a setter for that, but remember to not call createAnnex() before injection. Anyway, compiler will make you remember.
public final class Tab3Layout {
private JPanel Annex;
JPanel Annex1; //just to avoid compiler error
private GridBagLayout gridBagLayout;
private GridBagConstraints co1;
public Tab3Layout(JPanel Annex) {
this.Annex = Annex;
this.gridBagLayout = new GridBagLayout();
this.co1 = new GridBagConstraints();
}
public void createAnnex() {
this.Annex.setLayout(gridBagLayout);
this.co1.gridx = 0;
this.co1.gridy = 0;
this.co1.anchor = GridBagConstraints.FIRST_LINE_START;
this.co1.weighty = 1.0;
this.Annex.add(Annex1, co1);
}
public JPanel getAnnex() {
return this.Annex;
}
}
And then the Tab3 class:
public class Tab3 extends JPanel {
JPanel Annex;
Tab3Layout tab3Layout;
public Tab3() {
super();
this.Annex = new JPanel();
this.tab3Layout = new Tab3Layout(Annex);
this.tab3Layout.createAnnex();
this.setLayout(new BorderLayout());
this.add(tab3Layout.getAnnex(), BorderLayout.WEST);
this.add(new JPanel(), BorderLayout.EAST);
}
}
Look at my GridBagManager repo, it can give you an idea how to set layout with another class.
You can also extend Tab3Layout with JPanel. And remember to set anchor and fill with GridBagConstraints.
And Java naming convention.
JPanel Annex = new JPanel();
GridBagConstraints co1 = new GridBagConstraints();
to
JPanel annexPanel = new JPanel();
GridBagConstraints gridBagConstraints = new GridBagConstraints();
Personally I use gbc for GridBagConstraints object, as you will see in my repo I mentioned above.
Edit:
I think you should split your implementation into couple of smaller classes. That would be professional aproach. Refactor your code. For starters you can make every panel its own class extending JPanel (like i suggested with AnnexPanel class).
Edit2: createAnnex() can return JPanel type, reducing your boilerplate code, your call then would be as follows:
this.add(Tab3Layout.createAnnex(), co1);
instead of
Tab3Layout.createAnnex();
this.add(Tab3Layout.getAnnex(), co1);
Maybe even take createAnnex() to constructor (assuming class extends JPanel) if it's a singleton.
This may seem a very newbie question but I can't seem to find answers for this situation.
The How to Use Panels tutorial says there is an add() method in JPanel but I cannot implement it in my code.
public class JPanelTest extends AbstractView {
private final JPanel panel;
public JPanelTest () {
this.panel = new JPanel(new BorderLayout());
}
private void initComponents() {
JLabel label = new JLabel("label testing ");
this.panel.add(label);
}
}
The AbstractView is an abstract class that extends JPanel and implements ViewSupport, PropertyChangeListener.
In the last line of code this.panel.add(label); gives a compile error.
I don't see Panel.add() suggested in Eclipse.
The suggested add-related methods are addAncestorListener, addNotify, addVetoableChangeListener.
How can I not see a simple add method in the suggested box?
I am using 1.6 compile level of Eclipse. Would that make a difference?
Thanks in advance!
You create the JPanel, panel, but add it to nothing. So yes, it is receiving the JLabel, but since neither the JLabel nor the panel JPanel are added to anything displayed by the main GUI window (a JFrame?) none get displayed.
Solution: either add the panel JPanel or the JLabel itself to the this object (which is hopefully added to the top level window/JFrame).
So either:
private void initComponents() {
JLabel label = new JLabel("label testing ");
this.panel.add(label);
this.add(panel);
}
or
private void initComponents() {
JLabel label = new JLabel("label testing ");
// this.panel.add(label);
this.add(label);
}
Where did you get the AbstractView? It is not a build in to Swing hierachy.
Base on your class name maybe you want to extend JPanel instead of AbstractView
public class JPanelTest extends JPanel {
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.
Any idea why my code is not connected to the frame?
Frame:
import javax.swing.*;
import java.awt.*;
public class CourseGUI extends JFrame {
public CourseGUI()
{
super("CourseGUI Frame");
JPanel topPane = new TopPanel();
JPanel topPanel = new JPanel();
topPanel.setBackground(java.awt.Color.WHITE);
Dimension d = new Dimension(800,600);
topPanel.setPreferredSize(d);
this.setLayout(new BorderLayout());
this.add(topPanel, BorderLayout.NORTH);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(800,600);
this.setVisible(true);
}
public static void main(String[] args)
{
new CourseGUI();
}
}
Panel:
import javax.swing.*;
import java.awt.*;
public class TopPanel extends JPanel {
public TopPanel() {
TopPanel tp = new TopPanel();
tp.add(new JLabel("Course Info"));
tp.setSize(300,300);
tp.setVisible(true);
}
}
With the panel, I tried to get it where it will be on the NORTH of the Frame, sadly, it's not working. I am a beginner programmer, well In SCHOOL this year to actually learn this, Our teacher taught us this 4 days ago and I can not be anymore confused. Tried to get help several times on this, even from professor, with no avail, someone please help me out and explain. Thanks in advance.
public class TopPanel extends JPanel {
public TopPanel() {
TopPanel tp = new TopPanel();
tp.add(new JLabel("Course Info"));
tp.setSize(300,300);
tp.setVisible(true);
}
}
By creating a TopPanel object inside its own constructor you are causing near infinite recursion:
your TopPanel constructor will create a new TopPanel object
whose constructor will create a new TopPanel object
whose constructor will create a new TopPanel object
whose constructor will create a new TopPanel object whose constructor ,....
... etc -- recursion until your memory runs out. Don't do this.
Instead, don't do this:
public class TopPanel extends JPanel {
public TopPanel() {
// get rid of the recursion
// TopPanel tp = new TopPanel();
// add the label to the current TopPanel object, the "this"
add(new JLabel("Course Info"));
// setSize(300,300); // generally not a good idea
// tp.setVisible(true); // not needed
}
// this is an overly simplified method. You may wish to calculate what the
// preferred size should be, or simply don't set it and let the components
// size themselves.
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
}
Edit
Also, while not in error, this:
JPanel topPane = new TopPanel();
JPanel topPanel = new JPanel();
is very confusing since the two JPanel variables are close enough in name to confuse us, and more importantly to confuse your future self. You will want to use more logical and distinct variable names.
This is not a repeat question. Yes there are similar, but none have provided a working answer.
public class Tool extends JPanel implements ActionListener{
public JPanel Panel;
public Tool() {
}
public void show(){
displayStuff();
Panel.setVisible(true);
revalidate();
repaint();
}
}
Tool MyTool = new Tool();
JPanel Master = new JPanel();
JPanel Dash = = new JPanel();
JTabbedPane Tabs = new JTabbedPane();
JTabbedPane Tabs.addTab("Dash", Dash);
JTabbedPane Tabs.addTab("Tool", MyTool.Panel);
Master.add(Tabs);
The real code is much more complex. But the basic issue is that when changes occurs on MyTool.Panel as a result of user pressing some buttons.
MyTool.Panel does NOT get repainted until I use mouse to move Master.
How can I force it to repaint?
Its not a perfect solution but you can validate and redraw the entire application, thats what I have done in the past. I've used something like
class MyPanel extends JPanel{
public void doRedraw(){
getTopLevelAncestor().revalidate();
getTopLevelAncestor().repaint();
}
}
Hope this helps.