Fastest way to add components to Jpanel - java

I am testing how time-consuming is placing components in a panel.
As a test example I am placing some hundreds components to a jPanel. I am looking for a faster way to place them. Here is my test code:
import java.awt.*;
import javax.swing.*;
public class MyPane {
JFrame Myframe;
JPanel Mypanel;
public void createUI()
{
Myframe = new JFrame("Test clicks");
Mypanel = new JPanel();
Myframe.setPreferredSize(new Dimension(600, 600));
Myframe.setMinimumSize(new Dimension(600, 600));
Myframe.add(Mypanel);
Myframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Myframe.setVisible(true);
//ADD 100+1 LABELS
long start_time = System.nanoTime();//START TIME
for (int i=0; i<100 ; i++){
JLabel myLabel= new JLabel();
myLabel.setText("label"+ i);
Mypanel.add(myLabel);
}
long end_time = System.nanoTime(); //START TIME
double difference = (end_time - start_time)/1000000;
//ADD EXECUTION TIME LABEL
JLabel MyInfo= new JLabel();
MyInfo.setText("milliseconds:"+ difference);
MyInfo.setBackground(Color.yellow);
MyInfo.setOpaque(true);
Mypanel.add(MyInfo);
Myframe.pack();
}
public static void main(String[] args) {
MyPane overlapPane = new MyPane();
overlapPane.createUI();
}
}
In my pc adding 1000 Jlabels takes 120 millliseconds. Of course execution time will vary greatly depending on hardware, Os etc.
My question is: Is there a faster way to place many components in a panel. Is there a way, for example, to place all of them at once? Would that make any difference?
EDIT: I would like to clarify that putting 1000 labels one just after the other is not what I am trying to achieve in any real world software I am developing, although I have the right to do it. It's just a example, and that I know that in some cases there can be more efficient ways to show large amount of text information on screen. Also, my question is not about software optimization in general, but It's only about testing this specific algorithm.

This adds 1,000,000 viewable objects in 196 nano-seconds.
Like I said, it depends on the context, and is quite an arbitrary question.
import java.awt.*;
import java.util.Vector;
import javax.swing.*;
class ManyObjects {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new BorderLayout());
Vector<Integer> v = new Vector<Integer>();
long start = System.nanoTime();
for (int ii=0; ii<=1000000; ii++) {
v.add(ii);
}
long end = System.nanoTime();
int duration = (int)((end-start)/1000000);
v.add(duration);
JList<Integer> list = new JList<Integer>(v);
System.out.println("Duration in seconds: " + duration);
JOptionPane.showMessageDialog(null, new JScrollPane(list));
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}

Related

Reordering labels

I'm creating labels dynamically from an array in a FlowLayout JPanel, storing them in a JLabel array for future reference. They are displayed from left to right as intended.
I want to move one of the labels to the beginning (leftmost) of the panel.
I don't mind if the whole array shifts or just two labels swap places:
apple orange pear cherry melon
|
cherry apple orange pear melon
or
cherry orange pear apple melon
I've swapped array entries, then revalidate() and repaint(), but nothing happens.
Is there an easy way to move swing components around without removing all and then re-adding them to the panel or copying all the properties from one label to the other (I have others defined, not just the text)?
Here is a stripped down version of my code:
import javax.swing.*;
public class Test extends JPanel {
public Test () {
String entries[] = { "apple", "orange", "pear", "cherry", "melon" };
JLabel[] lbls = new JLabel[entries.length];
for (int i = 0; i < entries.length; ++i) {
lbls[i] = new JLabel();
lbls[i].setText(entries[i]);
add(lbls[i]);
}
// swap array entries
JLabel tmplbl = new JLabel();
tmplbl = lbls[3];
lbls[3] = lbls[0];
lbls[0] = tmplbl;
revalidate();
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test");
frame.setContentPane(new Test());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
});
}
}
I've swapped array entries
Swapping entries in an array does nothing. The Array has nothing to do with the panel.
So you need to adjust the components on the panel.
I want to move one of the labels to the beginning (leftmost) of the panel.
Well that is a different requirement than "swapping". It is also easier.
You can add a component to a panel and specify its position in the panel, so adding a component to the beginning is easy because its position will always be zero.
So to move the 3rd component to the beginning the code would be something like:
Component component = panel.getComponent(2);
panel.add(component, 0);
panel.revalidate();
panel.repaint();
If you really want a swap, then the code would be similar. You would get the component at both locations and then add the one component back to the lower location first and the add the other component back to the higher location.
There are a couple of things to fix before fixing your error:
Here are 2 errors in this line: public class Test extends JPanel {
Class name, do you know how many people call their classes Test? A LOT! Make it more descriptive, like SwapLabelsTest.
extends JPanel, you're not changing the behavior of the JPanel so there's no need to extend it in this case, just create a new instance of JPanel.
Don't put everything in the constructor, it's better to have an initialize() method or something like that (createAndShowGUI() in the code below) to handle GUI construction. It may seem like the easiest way, but separating that part will come handy later on when the project becomes bigger.
Move your variables to a bigger scope, for easier handling, unless those variables are local to the method, this will improve performance and readability.
Include a component that detects events, such as a JButton so that your swapping execution will happen when that event is triggered (a button click).
Your swapping logic seems a little bit odd, you have created new JLabels there and are trying to swap them, but it's better to have a MVC kind of pattern here, so that you swap the values in the array and then just update the UI after with those changes.
You may be asking, but how do I do that? Well like this:
String tmpString = entries[3];
entries[3] = entries[1];
entries[1] = tmpString;
The above code swaps the values in the entries array, all we have to do now is update each label with lbl[i].setText(entries[i]) inside of a for-loop.
So, you end up with something like this in the end:
import java.awt.BorderLayout;
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.SwingUtilities;
public class Test {
private JFrame frame;
private JPanel panel;
private String entries[] = { "apple", "orange", "pear", "cherry", "melon" };
private JLabel[] lbls = new JLabel[entries.length];
JButton button;
private void createAndShowGUI() {
panel = new JPanel();
for (int i = 0; i < entries.length; ++i) {
lbls[i] = new JLabel();
lbls[i].setText(entries[i]);
panel.add(lbls[i]);
}
button = new JButton("Swap 1 and 3");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String tmpString = entries[3];
entries[3] = entries[1];
entries[1] = tmpString;
reloadLabels();
}
});
frame = new JFrame("Test");
frame.add(panel);
frame.add(button, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
private void reloadLabels() {
for (int i = 0; i < entries.length; ++i) {
lbls[i].setText(entries[i]);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().createAndShowGUI();
}
});
}
}
Everytime you click the button, the items 1 & 3 (indexes) will be swapped and the UI will be updated (as .setText triggers an UI update).

How does the sizing of a JFrame actually work?

I have no purpose for what I'm really doing, just trying things out with Java Swing. What I have right now are these three variables:
int[] gridIterations = {10,10}; //how many JButtons are made each row/column
int[] frameSize = {600,600}; //size of the JFrame
int[] gridSize = {60,60}; //by gridSize, I mean size of the JButtons
I also have a nested for loop which uses these variables to create a grid of JButtons. I would expect the grids to perfectly fit the JFrame, however this is the result:
After some testing I realized that the frame will actually only fit all the JButtons if the size is (615, 631) But I'm wondering, why does it fit only with these parameters, and why, of all numbers, would it be those? To my understanding a simply calculation of 60 * 10 should equal 600 and successfully have all buttons fit into the JFrame, but I am most likely overlooking something. What could that be? Thanks.
A lot comes down to the requirements of the content and the layout manager. Rather then looking "how big" you'd like the frame to be, focus on the amount of space the content needs. The frame will then "pack" around this.
This means that the frame will "content size + frame border size" big.
JFrame#pack takes into consideration the content's preferred size and adds in the frames border insets automatically.
So, the only thing you need to is call JFrame#pack AFTER you finished adding the content to it
So, based on your code:
public class Testing {
public static void main(String[] args) {
JFrame frame = new JFrame("hi");
for (int i = 0; i < 10; i++) {
JButton a = new JButton();
a.setSize(20,20);
a.setLocation(20*i, 0);
frame.getContentPane().add(a);
}
frame.setLayout(null);
frame.pack();
frame.setVisible(true);
}
}
You are using a null layout. JFrame#pack will use the information provided by the layout manager to determine the "preferred" size of the overall content.
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify.
Rather the focusing on the absolute, which would be variable between OS's (and even different PC's with the same OS), focus on the user experience.
As has, already, been demonstrated, you can easily get this to work using a GridLayout
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridLayout(10, 10, 0, 0));
for (int index = 0; index < 10 * 10; index++) {
JButton btn = new JButton(String.valueOf(index)) {
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 50);
}
};
add(btn);
}
}
}
}
If, you need a more complex management (ie depth by breadth), you could use a GridBagLayout instead
Take a look at Laying Out Components Within a Container, How to Use GridLayout and How to Use GridBagLayout for more details ;)
The size of a JFrame includes its insets. This basically means the title bar and borders.
GridLayout will do this perfectly for you with much less effort involved.
class GridButtons implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new GridButtons(4, 5));
}
final int rows;
final int cols;
GridButtons(int rows, int cols) {
this.rows = rows;
this.cols = cols;
}
#Override
public void run() {
JFrame frame = new JFrame();
JPanel grid = new JPanel(new GridLayout(rows, cols));
for (int i = 0; i < (rows * cols); ++i) {
grid.add(new JButton() {
#Override
public Dimension getPreferredSize() {
return new Dimension(60, 60);
}
});
}
frame.setContentPane(grid);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

JTabbedPane within JScrollPane only showing one item at a time

When I launch my application, it launches the JFrame and loads up the JTabbedPane which contains the JScrollPane, yet it only shows one component inside it at a time. I have tried everything, and still I cannot solve the problem...
Here is my code:
package test;
import java.awt.*;
import javax.swing.*;
public class Main extends JFrame{
public Main()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400,500);
JPanel pane=new JPanel();
pane.setLayout(new BorderLayout());
UIManager.put("TabbedPane.contentOpaque", false);
JTabbedPane tabbedPane=new JTabbedPane();
JScrollPane scrollPane=new JScrollPane(pane);
tabbedPane.setPreferredSize(new Dimension(getWidth(),getHeight()));
for(int i = 0; i < 10; i++) pane.add(new JLabel("label22222222222222222222222222222222222222222222222222222222"+i));
//pane.add(scrollPane,BorderLayout.CENTER);
tabbedPane.add("Test",scrollPane);
add(tabbedPane);
}
public static void main(String[] args) {
Main main=new Main();
main.setVisible(true);
}
}
Please help me, I have no idea what I am doing wrong.
Your pane JPanel uses BorderLayout and you're adding components in a default fashion, or BorderLayout.CENTER. This is the expected behavior to show only the last component added.
You should consider using another layout such as GridLayout. Also, Google and read the "laying out components in a container" tutorial and understand the layouts that you're using.
Also, consider using a JList to display your data rather than a grid of JLabels.
As an aside, you should format your code for readability, not compactness. Don't put for loops on one line only. In fact all loops and blocks should go into curly braces to prevent your later editing your code, adding another line and thinking that it's in the loop when it's not.
Edit
For example, using a JList:
import javax.swing.*;
public class Main2 {
private static final int MAX_CELLS = 30;
private static void createAndShowGUI() {
final DefaultListModel<String> listModel = new DefaultListModel<>();
final JList<String> myList = new JList<>(listModel);
myList.setVisibleRowCount(8);
for (int i = 0; i < MAX_CELLS; i++) {
listModel.addElement("label22222222222222222222222222222222222222222222222222222222" + i);
}
JTabbedPane jTabbedPane = new JTabbedPane();
jTabbedPane.add("Test", new JScrollPane(myList));
JFrame frame = new JFrame("Main2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(jTabbedPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}

CardLayout performance?

I thought that CardLayout uses a HashMap to store its pairs (panels and associated string identifiers) but looking through the CardLayout class I noticed that it actually uses a Vector. This is how I understand it: when the show method is called it loops through the contents of the vector checking with equals to find out if this is the name of the appropriate card, and if it is, it then loops through all the panels of the container to find out which one is currently visible, it hides it, and then it displays the appropriate card.
If I was making a gui app that has a lot of different panels wouldn't that be kind of slow technique to flip to the desired panel? Should I better use my own way of showing my panels like storing them to an array and manually using add/remove or setVisible instead of using CardLayout? This is actually the way I was using at the beginning before I ended up to CardLayout.
This will never be an issue: you don't normally flip between components very frequently, and when you do then scanning a list with a smallish number of compoenents (usually 3-100?) is going to take a negligible amount of time compared with other operations that will have to happen (e.g. drawing the new component). Choice of data structure is basically irrelevant from a performance perspective - you could use a linked list and nobody would notice.
Also note that a HashMap wouldn't be appropriate for a CardLayout as it needs to preserve the order of the cards so that you can use first/next/previous etc.
So basically, don't worry and don't waste your time rolling your own CardLayout clone - CardLayout works just fine.
I don't find any performance issue in CardLayout. Even if you have a 1000 child components, it still feels very fast. Either by using previous/next or using show, it goes really fast.
Try to post an SSCCE that reproduces your problem and then we may help you. Here is something to start from:
import java.awt.BorderLayout;
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.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestCardLayout {
protected void initUI() {
JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final CardLayout layout = new CardLayout();
final JPanel panel = new JPanel(layout);
for (int i = 0; i < 1000; i++) {
panel.add(new JLabel("Label " + i), getLabelConstraint(i));
}
JButton next = new JButton("Next");
next.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
layout.next(panel);
}
});
JButton previous = new JButton("Previous");
previous.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
layout.previous(panel);
}
});
final JButton choose = new JButton("Choose");
choose.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String value = JOptionPane.showInputDialog(choose, "Enter a number between 0 and 999");
try {
int i = Integer.valueOf(value);
if (i > -1 && i < 1000) {
layout.show(panel, getLabelConstraint(i));
}
} catch (NumberFormatException e1) {
e1.printStackTrace();
}
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(previous);
buttonPanel.add(next);
buttonPanel.add(choose);
frame.add(buttonPanel, BorderLayout.SOUTH);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private String getLabelConstraint(int i) {
return "ComponentConstraint" + i;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestCardLayout().initUI();
}
});
}
}

Spacing Labels and Buttons in Java

I'm still brushing up on the old Java GUI and ran into sort of a stump. It's just that the whole GUI thing is still fresh and I've only used FlowLayout() and I guess what I'm looking for cannot be done with it. This isn't for homework or anything, just something I am working on. Anyways, my problem:
Basically, I want it to look like this
Welcome!
Today's Date is:
(space)
(space)
Exit button
My problem is I don't know enough of any of the layouts to get this done. I've been reading and messing with GridBagLayout and I can't get it to do anything and I've tried another way and the button was as big as the dang program. Anyways, here is the code that I have, even though it shouldn't really matter.
private void welcomeTab(){
welcomePanel = new JPanel(new FlowLayout());
String currentTime = SimpleDateFormat.getInstance().format(
Calendar.getInstance().getTime());
final JLabel welcomeLabel = new JLabel("Welcome!", JLabel.CENTER);
final JLabel dateLabel = new JLabel ("Today's date is: " + currentTime, JLabel.CENTER);
welcomePanel.add(welcomeLabel);
welcomePanel.add(dateLabel);
welcomePanel.add(createExitButton());
}
Thank you. I've been reading so much and it seems all of the examples are for creating panes with all buttons and it's driving me insane.
Something like this?
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.util.Calendar;
import java.text.SimpleDateFormat;
class WelcomeLayout {
private JPanel welcomePanel;
WelcomeLayout() {
welcomeTab();
welcomePanel.setBorder(new TitledBorder("The Welcome Panel"));
JOptionPane.showMessageDialog(null, welcomePanel);
}
private void welcomeTab() {
welcomePanel = new JPanel(new GridLayout(0,1,1,1));
String currentTime = SimpleDateFormat.getInstance().format(
Calendar.getInstance().getTime());
final JLabel welcomeLabel = new JLabel("Welcome!", JLabel.CENTER);
final JLabel dateLabel = new JLabel ("Today's date is: " + currentTime, JLabel.CENTER);
welcomePanel.add(welcomeLabel);
welcomePanel.add(dateLabel);
// one (kludgy) way to addd space.
welcomePanel.add(new JLabel(""));
welcomePanel.add(new JLabel(""));
welcomePanel.add( createExitButton() );
}
private JComponent createExitButton() {
JButton exit = new JButton("Exit");
// the FlowLayout is to center the JButton;
JPanel exitPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
exitPanel.add(exit);
return exitPanel;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
WelcomeLayout wl = new WelcomeLayout();
}
});
}
}
Using a BoxLayout as suggested by Talha Ahmed Khan/ZĂ©ychin
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.util.Calendar;
import java.text.SimpleDateFormat;
class WelcomeBoxLayout {
private JPanel welcomePanel;
WelcomeBoxLayout() {
welcomeTab();
welcomePanel.setBorder(new TitledBorder("The Welcome Panel"));
JOptionPane.showMessageDialog(null, welcomePanel);
}
private void welcomeTab() {
welcomePanel = new JPanel();
BoxLayout layout = new BoxLayout(welcomePanel, BoxLayout.Y_AXIS);
welcomePanel.setLayout(layout);
String currentTime = SimpleDateFormat.getInstance().format(
Calendar.getInstance().getTime());
final JLabel welcomeLabel = new JLabel("Welcome!", JLabel.CENTER);
final JLabel dateLabel = new JLabel ("Today's date is: " + currentTime, JLabel.CENTER);
welcomePanel.add(welcomeLabel);
welcomePanel.add(dateLabel);
welcomePanel.add( Box.createVerticalStrut(20) );
welcomePanel.add( new JButton("Exit") );
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
WelcomeBoxLayout wl = new WelcomeBoxLayout();
}
});
}
}
Try addming a Box.createHorizontalStrut(i_width)
welcomePanel.add(welcomeLabel);
welcomePanel.add( Box.createHorizontalStrut(10) );
welcomePanel.add(dateLabel);
welcomePanel.add( Box.createHorizontalStrut(10) );
welcomePanel.add(createExitButton());
It looks like you want to use a vertical BoxLayout. I'm not sure what Talha Ahmed Khan had in mind,
because horizontal struts enforce the amount of horizontal space between two elements.
This link should help:
http://download.oracle.com/javase/tutorial/uiswing/layout/box.html
and here's a direct link to the source for the first example on that page:
http://download.oracle.com/javase/tutorial/uiswing/examples/layout/BoxLayoutDemoProject/src/layout/BoxLayoutDemo.java
GridBagLayout at its best with Netbeans 7.0. Check that out, you will not regret.
Suggestion:
Sort your problem out by using the Netbeans GridBagLayout Designer, then go read the generated code to understand the fix.
Disclaimer:
Writing custom code can be very hairy. You need to familiarise yourself with that. It provides hooks to add custom code, in most of the places. But still I find it very cumbersome. You need to sort that on your own.

Categories

Resources