Using JTabbedPane with JPanels that need to share data - java

In my application, I have several custom JPanels added to a JTabbedPane. A single JPanel offers a piece of functionality, I've added them to JTabbedPane so that the user can switch between modules easily.
All of those custom JPanels operate on the same set of data, meaning if one of the modules has to change something in the data (for instance, a List), all other panels should be aware of that change and react accordingly.
However, with JTabbedPane, you first need to instantiate those JPanels to add them to JTabbedPane - and you do it once.
I have one problem - suppose user adds something to the collection in panel A (which is shared by all those panels) and switches to panel B. What should happen in this case? How is B supposed to know that something has been added to that collection?
My idea was to simply detect a tab switch event, and call the method of B to take the new data into account. But I feel this is not how it should be done.
What could you suggest?

In the example below, each panel in the tabbed pane has its own JComboBox that listens to a common ComboBoxModel. When the common model is updated, by clicking Update, each listening JComboBox sees the change.
I never need any two combo boxes to share a model. My panels contain completely different JComponent instances, but the look and data those components display depend on several collections common to all of those panels.
You may be able to leverage the observer pattern, examined here. The exact details depend on your use case, but the PropertyChangeListener examples are worth studying.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.Date;
import javax.swing.AbstractAction;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
/**
* #see https://stackoverflow.com/a/37514928/230513
* #see https://stackoverflow.com/questions/8752037
* #see https://stackoverflow.com/a/37222598/230513
*/
public class TabTest {
private static final int N = 5;
private final JTabbedPane pane = new JTabbedPane();
private final DefaultComboBoxModel model = new DefaultComboBoxModel(
new String[]{"Alpher", "Bethe", "Gamow", "Dirac", "Einstein"});
public TabTest() {
for (int i = 0; i < N; i++) {
Color color = Color.getHSBColor((float) i / N, 1, 1);
pane.add("Tab " + String.valueOf(i), new TabContent(i, color));
}
}
private class TabContent extends JPanel {
private TabContent(int i, Color color) {
setOpaque(true);
setBackground(color);
add(new JComboBox(model));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
}
private void display() {
JFrame f = new JFrame("TabColors");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(pane);
JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT));
p.add(new JButton(new AbstractAction("Update") {
#Override
public void actionPerformed(ActionEvent e) {
model.addElement(new Date());
}
}));
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TabTest().display();
}
});
}
}

Related

How to call a panel from a button with ActionListener

So I'm making a simple program that jumps from panel to panel and am using an actionlistener Button to make the jump. What kind of method or operation do I use to jump from panel to panel?
I tried to use setVisible(true); under the action listener, but I get just a blanks screen. Tried using setContentPane(differentPanel); but that doesn't work.
ackage Com.conebind.Characters;
import Com.conebind.Tech.TechA16;
import Com.conebind.Overviews.OverviewA16;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Char_A16 extends JFrame {
private JButton combosButton16;
private JButton techButton16;
private JButton overviewButton16;
private JLabel Image16;
private JPanel panel16;
private JPanel panelOverviewA16;
public Char_A16() {
overviewButton16.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
OverviewA16 overview16 = new OverviewA16();
overview16.setVisible(true);
overview16.pack();
overview16.setContentPane(new Char_A16().panelOverviewA16);
}
});
techButton16.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Todo
}
});
}
private void createUIComponents(){
Image16 = new JLabel(new ImageIcon("Android 16.png"));
}
public static void main (String[] args){
JFrame frame = new JFrame("Android 16");
frame.setContentPane(new Char_A16().panel16);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);}
}
The setContentPane(OverviewA16) doesn't work because there's not an object that defines the panel.
Please check this demo project showing how to use CardLayout with IntelliJ IDEA GUI Designer.
The main form has a method that switches between 2 forms displayed inside it:
public void showPanel(String id) {
final CardLayout cl = (CardLayout) cardPanel.getLayout();
cl.show(cardPanel, id);
}
Both forms are added to the card layout during the main form initialization:
FormOne one = new FormOne();
one.setParentForm(this);
cardPanel.add(one.getPanel(), FORM_ONE);
FormTwo two = new FormTwo();
two.setParentForm(this);
cardPanel.add(two.getPanel(), FORM_TWO);
final CardLayout cl = (CardLayout) cardPanel.getLayout();
cl.show(cardPanel, FORM_ONE);
A reference to the main parent form is passed to these 2 forms using setParentForm() method so that FormOne and FormTwo classes can access the showPanel() method of the MainForm.
In a more basic case you may have a button or some other control that switches the forms
located directly on the MainForm, then you may not need passing the main form reference to the subforms, but it can be still useful depending on your app logic.

Rather annoying issue with a Swing combo box

https://youtu.be/8djixdHNoEQ - this video shows what I am facing, in that the JComboBox seems to be display multiple times/leaving an after-image.
Here is how I have setup the combo box:
private String[] list = { "Inches/Centimeters", "Miles/Kilometres", "Pounds/Kilograms", "Gallons/Litres", "Feet/Metres", "Celcius/Kelvin", "Acres/Hectare" }; //the String array the ComboBox uses
private JComboBox<String> conversionCombo; //defining the JComboBox.
conversionCombo = new JComboBox<String>(list); // creating JComboBox
Other than defining the list it uses, the comb box itself and creating the combo box, there is no other code which interacts with this component. (other than the code which sets the Combo box up, and adds it to the screen etc.)
Example program below.
thing1 (driver class, sets up JFrame):
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class thing1 {
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame("Thing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thing2 thing2 = new thing2();
frame.getContentPane().add(thing2);
frame.pack();
frame.setVisible(true);
}
}
thing2 (sets up UI element - in this case just the JComboBox):
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JComboBox;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class thing2 extends JPanel {
/**
* Declaring all variables and components to be used within the GUI
*/
private String[] list = { "Inches/Centimeters", "Miles/Kilometres", "Pounds/Kilograms", "Gallons/Litres", "Feet/Metres", "Celcius/Kelvin", "Acres/Hectare" };
thing2() {
JComboBox<String> conversionCombo = new JComboBox<String>(list); // creating JComboBox
add(conversionCombo);
setPreferredSize(new Dimension(800, 80));
setBackground(Color.WHITE);
}
}
Should be able to compile this and see the issue. In this case if you are to select anything from the dropdown and move your mouse to the right of the combobox whilst your mouse is 'within' it (ex: https://i.imgur.com/Z2Slrl3.gifv), "after-images" will appear.

Jtabbedpane layout for button does not work

The below swing interface using tabbedpane works fine until i set layout for button, that is when i set the content pane loginpage to null (loginpage.setlayout(null)) the buttons disappear from the pane but works when i replace button with textfield or textarea.
package atmg;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
public class newgui extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
public JPanel clickfn= new JPanel(),gui= new JPanel(),trasgui= new JPanel(),contentpanel = new JPanel();
public static JTabbedPane Tabs = new JTabbedPane();
public JButton loginpage, filloginfo,createlogin ;
public JLabel label1 ,label2;
private CardLayout cardlayout = new CardLayout();
//static JPanel panel1 = new JPanel();
newguilogin nw;
public static void main(String[] args) {
newgui tf = new newgui();
tf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tf.setSize(700,700);
tf.setVisible(true);
tf.setLocation(400,20);
}
public newgui(int a)
{
System.out.println(a);
}
public newgui() {
super("ATM");
Initialize();
}
public void Initialize()
{
nw= new newguilogin();
nw.setgui(this);
loginpage = new JButton("Go to loginpage");
filloginfo = new JButton("go to fill log info");
createlogin = new JButton("create a new user");
// TODO Auto-generated constructor stub
actionListener a1 = new actionListener();
loginpage.addActionListener(a1);
filloginfo.addActionListener(a1);
createlogin.addActionListener(a1);
clickfn.add(loginpage);
clickfn.setSize(20,20);
clickfn.setLocation(50,50);
clickfn.add(filloginfo);
contentpanel.setLayout(cardlayout);
contentpanel.add(Tabs, "tab");
Tabs.add(clickfn,"panel1");
//Tabs.add(trasgui,"panel3");
this.setContentPane(contentpanel);
cardlayout.show(contentpanel, "tab");
}
public class actionListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
JButton src = (JButton)e.getSource();
if(src.equals(loginpage))
//threader = new Threading();
//Tabs.addTab("panel2", gui);
nw = new newguilogin();
nw.initialize();
}
}
}
that is when i set the content pane loginpage to null (loginpage.setlayout(null)) the buttons disappear
A good rule of thumb to follow with Swing layouts: never use null layouts.
While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
Understand also that you can nest JPanels, each using its own simple layouts, and thereby create complex GUI's with simple layout managers.

Make a custom JButton that can be customized?

I want to create my own custom buttons (or that is have a template to create mine own custom buttons). What methods do I need to override to make the same sized button that the JButton makes if I want it to look that same. Because I thought that if you extend a JButton to another button class, a custom button, that had the same size and look as the JButton. But when I put my button up and in the form it makes it really tiny and does not look like a normal button.
What I want is to have at least the same looking button as the JButton has. But I also want to have the add in borders, text and color to foreground and background? What are all of the methods that I would need to override and does anyone have a simple example?
Class: (JButton) -- My Button
import javax.swing.JButton;
public class DougCustomButton extends JButton {
DougCustomButton() {
super();
setBorder(BorderFactory.createLineBorder(Color.BLUE));
}
#Override
public void setPreferredSize(int a, int b) {
//What would I put in here, do I need super()
}
}
Class: (JPanel)
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class genericPanel2 extends JPanel {
private static final long serialVersionUID = 1L;
DougCustomButton b2 = new DougCustomButton();
public genericPanel2() {
add(b2);
setPreferredSize(new Dimension(50, 30));
}
}
Class: (JFrame)
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class genericFrame2 {
Dimension dimension = new Dimension(450,450);
JFrame f = new JFrame("Not resizable");
genericPanel2 genericPanel = new genericPanel2();
public static boolean RIGHT_TO_LEFT = false;
public genericFrame2() {
initUI();
}
public static void addComponentsToPane(Container pane) {
}
public final void initUI() {
f.add(genericPanel);
f.setSize(dimension);
f.setResizable(true);
f.setLocationRelativeTo(null);
f.setTitle("Draw Line Test");
//addComponentsToPane(f.getContentPane());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
genericFrame2 ex = new genericFrame2();
ex.f.setVisible(true);
}
}
Screen Shot:
My custom button is the size of a pixel? What is probably causing that?
Sizing information is provided to the layout manager via the getPreferred/Minimum/MaximumSize methods. These generally delegate to the installed UI delegate, which uses, things like, the text, icon and margin properties to determine the size of the button.
Generally speaking, if you want to "customise" a button, you can use the Action API, which provides the means to provide a self contained entity which can easily be applied to different components, such as menus, buttons and even used by the key bindings API
Take a look at How to use Actions for more details
You can set a Border on a regular JButton, just like every other JComponent - perhaps add a Constructor like
public DougCustomButton() {
super();
setBorder(BorderFactory.createLineBorder(Color.BLUE));
// and a font
Font aFont = new Font("Serif", Font.ITALIC | Font.BOLD);
setFont(aFont.deriveFont(18.0f));
}
Read more on How to Use Borders and Font.deriveFont(float).

Understanding layout managers -- I'm not, please enlighten me

I'm just not understanding why things are being resized when I call the validate() and repaint() methods. I'm struggling to understand this. Essentially, my program is meant to display like this. I have a main frame into which I plug the various JPanels that I'm extending for the various functions of my photo album. The class below is the NewAlbum class that is supposed to allow the user to select files and make a new album out of them.
The code for choosing files works nicely. Once the files are selected, the change to the NewAlbum panel should be the select files button is replaced by a done button. Under the done button is a JSplitPane with the horizontal splitter just off center with the right side being larger than the left. The left side will eventually have a thumbnail of each photo as metadata about the photo is entered into the right side.
The right side pane is to be a JScrollPane with a single JPanel which has, in a grid form, the 4 entries that the user is asked for data about. After adding everything, the dimensions are where I want them to be, but when I call the validate/repaint combination the dimensions become "messed up." I'm pretty sure it's because I'm not understanding how the default layout managers for the various classes I'm using, or extending. Please help me understand. Also, tell me if the GridBagLayout is what I want, or if a different one is what I'm looking for.
The NewAlbum code is below.
I apologize for the uncompilable code. I figured that you'd be able to just look at the class and tell me, "Oh, yeah, this is the problem." Below is compilable and does demonstrate the problem. Once the files are selected, the split pane window is too thin and too long. I want it to fit inside the frame. Actually, it should fit inside the JPanel which is inside the JFrame.
Thanks,
Andy
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
class Main extends JFrame {
static JPanel transientPanel = null;
public Main() {
super();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(640, 480);
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Example");
JMenuItem albumMenu = new JMenuItem("New Album");
albumMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
transientPanel = new NewAlbum();
add(transientPanel);
validate();
repaint();
}
});
menu.add(albumMenu);
menuBar.add(menu);
setJMenuBar(menuBar);
validate();
}
public static void main(String[] args) {
final Main m = new Main();
m.setVisible(true);
}
}
/**
* #description NewAlbum is the window that is presented to the user
* to select new photographs for the album. Once selected, the user
* will be presented a form, of sorts, to complete the metadata for this
* album.
* #author Andy
*/
class NewAlbum extends JPanel {
JButton selectFiles;
JButton done;
JButton nextButton = new JButton("Next Image");
ArrayList<File> filesArray;
JSplitPane splitWindow = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
JScrollPane scrollWindow;
JPanel rightSidePanel = new JPanel();
JPanel leftSidePanel = new JPanel();
JLabel subjectLabel = new JLabel("Image subject:");
JLabel locationLabel = new JLabel("Image location:");
JLabel commentLabel = new JLabel("Comments:");
JLabel dateLabel = new JLabel("Date (mm/dd/yyyy):");
JTextField subjectText = new JTextField(25);
JTextField locationText = new JTextField(25);
JTextArea commentText = new JTextArea(4, 25);
JTextField dateText = new JTextField(10);
public NewAlbum() {
super();
selectFiles = new JButton("Select Photos");
selectFiles.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
selectFilesForAlbum();
}
});
add(selectFiles);
}
private void configureRightPanel() {
int jPanelX = getParent().getWidth();
int jPanelY = getParent().getHeight() - 30; // this should account for buttons
// now, resize this panel so that it will be the right size for the split pane
jPanelX = jPanelX - (int)(jPanelX * .31);
rightSidePanel.setSize(jPanelX, jPanelY);
rightSidePanel.add(subjectLabel);
rightSidePanel.add(subjectText);
rightSidePanel.add(locationLabel);
rightSidePanel.add(locationText);
rightSidePanel.add(commentLabel);
rightSidePanel.add(commentText);
rightSidePanel.add(dateLabel);
rightSidePanel.add(dateText);
rightSidePanel.add(nextButton);
// iterate over the photos selected, make bogus info for now
}
private ArrayList<File> makeFileIntoArrayList(File[] f) {
ArrayList<File> a = new ArrayList<File>();
a.addAll(Arrays.asList(f));
return filesArray = a;
}
/**
* selectFilesForAlbum
* This method is private to the NewAlbum class. It is the handler for
* when the user clicks on the "select photos" button. When the function
* executes, it displays the JFileChooser so that the user may select
* the desired photos. The files selected are assigned to a class variable
* of type File[] which is used by the enterPhotoInfo method.
*
* #return void
*/
private void selectFilesForAlbum() {
JFileChooser jfc = new JFileChooser();
jfc.setMultiSelectionEnabled(true);
jfc.showOpenDialog(this);
makeFileIntoArrayList(jfc.getSelectedFiles());
changeButtonToDone();
enterPhotoInfo();
// TODO write the photo album to the disk
}
private void changeButtonToDone() {
remove(selectFiles);
done = new JButton("Done");
add(done);
// by the time this gets called, we'll have a parent container
getParent().validate();
getParent().repaint();
}
private void enterPhotoInfo() {
splitWindow.setSize(this.getWidth(), this.getHeight() - 30);
// remove when the left side panel actually has something
Dimension iewDims = splitWindow.getSize();
int leftX = iewDims.width - (int)(iewDims.width * .69);
int leftY = iewDims.height;
leftSidePanel.setSize(leftX, leftY);
configureRightPanel();
scrollWindow = new JScrollPane(rightSidePanel);
scrollWindow.setSize(rightSidePanel.getSize());
splitWindow.setRightComponent(scrollWindow);
splitWindow.setLeftComponent(leftSidePanel);
splitWindow.setDividerLocation(.31);
System.out.println("Printing dimensions of before validate/repaint: this, splitWindow, scrollWindow, LSP, RSP");
debugPrintDimensions(this);
debugPrintDimensions(splitWindow);
debugPrintDimensions(scrollWindow);
debugPrintDimensions(leftSidePanel);
debugPrintDimensions(rightSidePanel);
//infoEntryWindow.add(infoScroller);
this.add(splitWindow);
this.validate();
this.repaint();
System.out.println("Printing dimensions of: this, splitWindow, scrollWindow, LSP, RSP");
debugPrintDimensions(this);
debugPrintDimensions(splitWindow);
debugPrintDimensions(scrollWindow);
debugPrintDimensions(leftSidePanel);
debugPrintDimensions(rightSidePanel);
}
private void debugPrintDimensions(Container c) {
System.out.println("DEBUG: Containers (x,y): (" +
String.valueOf(c.getWidth()) +
"," +
String.valueOf(c.getHeight()) +
")");
}
}
Also, tell me if the GridBagLayout is what I want, or if a different one is what I'm looking for.
You use the appropriate layout manager for the job. This can also mean using different layout managers on different panels.
splitWindow.setSize(this.getWidth(), this.getHeight() - 30);
You should NEVER use setSize(). That is the job of the layout manager, to determine the size of the component based on the rules of the layout manager.
All components have a preferred size which is used by the layout manager. At times you can use the setPreferredSize() to change the default.
By selecting a LayoutManager, you are handing over control of the layout to that layout manager. You can give the LayoutManager hints via layout constraints and restrictions by setting the preferred dimensions on the components you are arranging, but essentially, the layout manager will call the shots.
With GridBagLayoutManager you can achieve almost anything with constraints and component dimension settings, but it can still be tricky to get right. Try setting the preferred size on your components.
I used to use GridBagLayoutManager for everything, but then I came across MigLayout which really is a huge step forward in terms of easy configuration and layout consistency. I recommend you give it a look.

Categories

Resources