Vertical JPanel manual positioning: which layout? - java

Given my requirements:
Single vertical column of JPanels.
Set the vertical location* of the JPanel without using the properties of a sibling.
Component position and size are fixed when the frame is resized.
Keep other layout aspects automatic (such as preferred size calculation), as much as possible
(*) Location: I mean location as in Component.setLocation(x, y).
is there a solution which is obvious, and if this is GridBagLayout, how to do this?
Details:
I want to put components vertically in a column container (like a vertical Box) by specifying their vertical location only. What is the best way to do this without loosing the other benefits of a layout such as BoxLayout?
In a vertical Box, setting the vertical position of a component must be done using a filler, or by adjusting the size of the component just above, there is no such possibility like:
panel.setLocation(getLocation().x, y)
On the other hand using a no layout container puts on me the task manage:
The initial size of the component
The container resizing events.
Here the solution of null layout is recommended, here this is a custom one, and here this is GridBagLaout. Also MIGLayout appears to be universal one (but I'd prefer no adding another library to my project).

I have written the following program for someone who was also looking for the same requirements.
Note: Make sure to add the first element to 0 position because there will be no more components and no position will be available other than 0, 2nd to 0 or 1, 3rd to 0 or 1 or 2 and so on
public class VerticalList extends JFrame {
JPanel pnl = null;
TextField tf = new TextField(10);
Box center = Box.createVerticalBox();
JScrollPane jsp = new JScrollPane(center);
JPanel ctrl = new JPanel(new FlowLayout());
JButton send = new JButton("Send");
public VerticalList() {
jsp.setAutoscrolls(true);
ctrl.add(send);
ctrl.add(new JLabel("Position:"));
ctrl.add(tf);
Container cnt = getContentPane();
cnt.add(jsp, BorderLayout.CENTER);
cnt.add(ctrl, BorderLayout.SOUTH);
send.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pnl = new JPanel(new FlowLayout());
pnl.setBorder(BorderFactory.createLineBorder(Color.red));
pnl.add(new JLabel("Added to Position: "+tf.getText()));
pnl.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int)pnl.getPreferredSize().getHeight()));
try{
int index = Integer.parseInt(tf.getText());
center.add(pnl, index);
}catch(Exception ex){
JOptionPane.showMessageDialog(null, "Please Provide a Valid position", "Position Error", JOptionPane.ERROR_MESSAGE);
}
validate();
}
});
setPreferredSize(new Dimension(300, 300));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
pack();
}
public static void main(String[] args) {
new VerticalList();
}
}

Related

How to create a fluid 2-column GridLayout within a ScrollPane?

I have done tons of searching over the past two hours, and I've given up. The image below shows what I am trying to achieve (don't judge my drawing it's late and I made it in paint quickly):
Basically, I want a JScrollPane to have a JPanel with a 2-column GridLayout, and as I add elements I want the GridLayout to expand downward. I want the elements to use their Preferred Size and to NOT expand within the GridLayout.
Currently I have a JScrollPane and a JPanel with a GridLayout, and a JPanel containing the grid with a FlowLayout. As a test, I add 10 buttons to the grid. Here's my current code:
// Setup main panel
JPanel pnlUsers = new JPanel(new GridLayout(0, 2));
pnlUsers.setOpaque(true);
pnlUsers.setBackground(Color.GREEN);
// Setup GridLayout Container
JPanel pnl2 = new JPanel();
pnl2.setOpaque(false);
pnl2.add(pnlusers);
// Setup scrollpane
JScrollPane scrUsers = new JScrollPane(pnl2);
scrUsers.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrUsers.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrUsers.setOpaque(false);
scrUsers.getViewport().setOpaque(false);
scrUsers.setBorder(null);
// Add users
for (int i = 0; i < 10; i++) {
pnlUsers.add(new JButton("Button " + (i + 1));
}
This gives a very good result, with the buttons using their preferred size as seen in the image below:
Unfortunately, the buttons are still not filling the horizontal space. So, I attempted to make pnl2 a BoxLayout instead and add some vertical glue...
// Setup GridLayout Container
JPanel pnl2 = new JPanel();
pnl2.setLayout(new BoxLayout(pnl2, BoxLayout.Y_AXIS));
pnl2.setOpaque(false);
pnl2.add(pnlusers);
pnl2.add(Box.createVerticalGlue());
I also created my own temporary button class that sets the preferred size to use the minimum size:
public class TempButton extends JButton {
public PLTempButton(String msg) {
super(msg);
this.setPreferredSize(this.getMinimumSize());
}
}
Which resulted in the following:
This is much better, but there's still a problem. If there are not enough buttons to cause the JScrollPane to scroll, the buttons height are not consistent and will resize as you resize the window vertically. Why?
Obviously when I add 100 buttons, they use their preferred size:
Maybe I'm just not understanding the differences between minimum, preferred and maximum size? But I want the buttons to use the height they're set even if there aren't enough to cause the scroll-pane to, well, scroll. What can I do to fix this?
1) How do I even get a GridLayout within a ScrollPane?
Create a JPanel
Apply a GridLayout to the JPanel
Wrap the JPanel in a JScrollPane
For example
JPanel panel = new JPanel(new GridLayout(0, 2));
JScrollPane scrollPane = new JScrollPane(panel);
// Add the scroll pane to what ever parent container you're using
2) How do I get said GridLayout to expand horizontally, including the added components?
That doesn't make sense with regards to all previous part of the question, you said "and have it slowly expand downward the more things I add"
Having said that, the "basic" answer is, you configure the GridLayout and let it do it's job. The above example is configured for 2 columns and n number of roes
3) How would I add a "margin" to the components?
That's a broad answer, you could:
Make use of the horizontal and vertical gap properties of the GridLayout
Use a compound layout and adjust the insets of the an appropriate layout manager (like GridBagLayout)
Recommendations
I would recommend reading through Laying Out Components Within a Container to get a better understanding of the layout managers.
Remember, you not stuck to using one.
I would also recommend reading through How to Use Scroll Panes as you're asking basic questions about the API which are better covered through the tutorials
The following mre demonstrates creating a JPanel with GridLayout warped by JScrollPane and setting its horizontal and vertival gaps.
The Jpanel is added using a BorderLayout (the default layout manager of JFrame content pane) which allows it to expand:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class SwingMain {
private String text ="Growing ";
private JPanel grid;
private JFrame f;
SwingMain() {
creategui();
}
void creategui(){
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
JButton addComponentBtn = new JButton("Add Component");
addComponentBtn.addActionListener(e-> addComponent());
f.add(addComponentBtn, BorderLayout.PAGE_START);
grid = new JPanel(new GridLayout(0, 2, 10, 10)); //any number of rows, 2 columns, H and V gap
f.add(new JScrollPane(grid), BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
void addComponent() {
grid.add(new JLabel(text) );
text +="."; //make text longer so JLable grows
f.pack();
}
public static void main(String[] args) {
new SwingMain();
}
}

Scrollable Pane does not show contents

I cannot find out why this will not show the contents:
public class Bans implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
draw();
}
public static JFrame frame;
public static void draw() {
frame = new JFrame("Ban History");
frame.setPreferredSize(new Dimension(575,250));
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JLabel label = new JLabel("Ban History");
label.setFont(Main.header);
label.setHorizontalAlignment(SwingConstants.CENTER);
JPanel container = new JPanel();
JPanel border = new JPanel();
JScrollPane scrPane = new JScrollPane(border);
border.add(container);
container.setLayout(new GridLayout(0,1));
scrPane.setBorder(null);
for(Ban ban : Main.banlist) {
System.out.println(ban.id);
JPanel whitespace = new JPanel();
JPanel panel = new JPanel();
panel.setBackground(Color.getHSBColor(176F, 25.46F, 65.12F));
panel.setPreferredSize(new Dimension(510,0));
panel.setLayout(new GridLayout(0,2));
JLabel banDate = new JLabel();
banDate.setText("(#" + ban.id + ") Ban Date: " + ban.banDate.toString() + " ");
banDate.setFont(Main.body);
panel.add(banDate);
JLabel banName = new JLabel();
banName.setText("Banned By: " + ban.bannedByName);
banName.setFont(Main.body);
panel.add(banName);
JLabel banReason = new JLabel();
banReason.setText("Reason: " + ban.banReason);
banReason.setFont(Main.body);
panel.add(banReason);
JLabel banTime = new JLabel();
banTime.setText("Ban Time: " + ban.banTime);
banTime.setFont(Main.body);
banTime.setHorizontalAlignment(SwingConstants.RIGHT);
panel.add(banTime);
container.add(whitespace);
whitespace.add(panel);
}
frame.add(label,BorderLayout.PAGE_START);
frame.add(scrPane, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}
It was working earlier, however now none of the contents of the ScrollPane show. I'm fairly new with Swing, however I would think that this should work. Yes, there are contents in the table. When ran it does print out 5 id numbers, which correspond to the correct numbers on the MySQL server, so that should be working fine.
The content is being added, but isn't being displayed because of how you have designed your layout, as the panels representing your bans have no height:
panel.setPreferredSize(new Dimension(510,0));
By following #FredK's suggestions in the comments, you can maintain the effect of your whitespace panel by applying a vertical gap between the rows in your layout. This is provided by another constructor available within GridLayout: GridLayout(int rows, int cols, int hgap, int vgap)
There are some demos available here: How to Use GridLayout
By mocking up some bans, I got the following with your code:
Note: By applying the suggestions (with a vgap of 5) the output was the same as the above
Side note: If you find something was working earlier it tends to be from a change you've made. If you use IntelliJ as your IDE there is a helpful feature for this called a Local History. By right clicking on the class you can see line-by-line comparisons of changes you made to the class with time stamps, similar to source control. You can read more here: Using Local History
I'm not saying you have to change your IDE, but as a beginner if you are doing a lot of experimentation you may find some benefit in it

Is there a way of moving JPanel in BorderLayout?

I am using a BorderLayout for the frame (the first one that "caught" my attention in the tuts) and a FlowLayout for the labels (the one I found appropriate for what I do), and the result shows up like this:
My objective is to push the "2*1" a little bit down, to sort of "center" it.
I looked around and found a lot of people saying to use a null layout, but then saying it's not the best alternative (even though my window is not resizable), and the other solution I found was using a combo of layouts (unless I misunderstood).
The question is the one on top of this, plus if not, what really is the best alternative? (The following is the code that makes this window (minus the vars and other methods, to simplify visualization).
public Frame() {
super("Jogo de Multiplicar!");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
setSize(300, 200);
setResizable(false);
getContentPane().setBackground(pink);
mensagem = new TransparentPanel();
operacao = new TransparentPanel();
//added stuff in mensagem and operacao
add(operacao);
add(mensagem, BorderLayout.SOUTH);
}
My objective is to push the "2*1" a little bit down, to sort of "center" it.
If you just want more space at the top then you can use a Border:
operacao.setBorder( new EmptyBorder(...) );
Read the section from the Swing tutorial on How to Use Borders for more information.
If you want to actually center it you can use a BoxLayout:
Box box = Box.createVerticalBox();
box.add( Box.createVerticalGlue() );
box.add( topPanel );
box.add( Box.createVerticalGlue() );
box.add( bottomPanel );
The tutorial also has a section on How to Use BoxLayout. Search the table of contents.
You could use MigLayout as your only LayoutManager. It's pretty mighty and usually offers everything that the other managers do too.
With this it's pretty simple to center the components:
public class MultiplyExample extends JFrame{
private static final long serialVersionUID = 1L;
JLabel testLabel = new JLabel("2*2 = 4");
public MultiplyExample(){
super("Example");
setBounds(300, 50, 200, 200);
// Set the MigLayout, so that columns and then rows get centered
setLayout(new MigLayout("center, center"));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(testLabel);
}
public static void main(String[] args) {
JFrame testFrame = new MultiplyExample();
testFrame.setVisible(true);
}
}
Result:
Here is a demo what the MigLayout has to offer:
http://www.miglayout.com/swingdemoapp.jnlp
Here is a quickstart-guide:
http://www.miglayout.com/QuickStart.pdf
If you have to use BorderLayout, you could put your components onto another panel and put this one into the center by using BorderLayout.CENTER:
pane.add(button, BorderLayout.CENTER);

JButton Appears on One Computer, But Not Others (BorderLayout)

I am new to Swing. I am building a JFrame with a JScrollPane inside it using Eclipse IDE. Inside of the JScrollPane is a JPanel in Border Layout. I tried to add a JButton (called "submitAnswers") to the JFrame using the code below, but for some reason the button only appears at the end of the frame on my computer, but not on other computers (my friend tried it on his Mac and I tried it on a separate Windows OS like mine). Some proposed solutions that I have tried and from other sites that have not worked include:
Use the pack() method. Reason: since the preferred size of the JPanel is much longer in height than the JFrame (hence I employed a JScrollPane), packing the JFrame only causes the text to be not visible on the desktop.
Place button on content JPanel. Reason: I don't know. It just wouldn't appear on another desktop computer or my friend's mac computer.
Use BorderLayout.SOUTH instead of BorderLayout.PAGE_END. Reason: There was absolutely no change. The button would still be visible on my computer, but invisible on others.
Place button directly on JFrame. Reason: I don't know.
In addition, my JFrame is nested within a static method; hence, I've only included the relevant code for the specific method I'm having issues with.
Has anyone had this issue before? I would really appreciate your insight.
Code:
public static void createTestPage() {
JFrame testFrame = new JFrame("testing...1,2,3");
//Customizes icon to replace java icon
try {
testFrame.setIconImage(ImageIO.read(new File("src/icon.png")));
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//Centers location of introFrame to center of desktop
Dimension screenDimensions = Toolkit.getDefaultToolkit().getScreenSize();
testFrame.setLocation(screenDimensions.width / 16,screenDimensions.height / 14);
//Size and display the introFrame.
Insets insets = testFrame.getInsets();
//Format size of screen itself
testFrame.setSize(1200 + insets.left + insets.right,
400 + insets.top + 250 + insets.bottom);
//Temporarily set screen so that it cannot be resized
testFrame.setResizable(false);
//Set background color of testFrame
testFrame.getContentPane().setBackground(new Color(75, 0, 130));
testFrame.setLayout(new BorderLayout());
//Set layout of testFrame
testFrame.setLayout(new BorderLayout(10, 1));
//Test content
JPanel testContentPanel = new JPanel();
testContentPanel.setBackground(new Color(75, 0, 130));
testContentPanel.setSize(new Dimension(900,2060));
testContentPanel.setPreferredSize(new Dimension(900, 2060));
//Test content pane layout
testContentPanel.setLayout(new BoxLayout(testContentPanel, BoxLayout.PAGE_AXIS));
//Create panel to hold instructions text
JPanel instructionsPanel = new JPanel();
instructionsPanel.setBackground(new Color(75, 0, 130));
instructionsPanel.setLayout(new BorderLayout(10,1));
//Create JPanel for submit answers button
JPanel submitAnswersPanel = new JPanel(new BorderLayout());
submitAnswersPanel.setBackground(new Color(75, 0, 130));
submitAnswersPanel.setVisible(true);
//Create button to submit personality test answers
JButton submitAnswers = new JButton("Submit Answers");
submitAnswers.setVisible(true);
submitAnswers.setBorder(new EmptyBorder(10, 400, 10, 400));
//Add submitAnswers button to panel
submitAnswersPanel.add(submitAnswers);
//Add submitAnswersPanel to test content panel
testContentPanel.add(submitAnswersPanel);
//Create scroll pane to allow for scrollable test (contents cannot fit one page)
JScrollPane testScrollPane = new JScrollPane();
testScrollPane.setViewportView(testContentPanel);
//Get rid of horizontal scroll bar and add vertical scrollbar
testScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
testScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//Speed up scrolling
testScrollPane.getVerticalScrollBar().setUnitIncrement(16);
testFrame.add(testScrollPane);
//Experiment to show button
testFrame.setVisible(true);
}
I've refactored your code a little to use method to create the individual components of the GUI. You can find the full code at this ideone link
What I saw when I first copied your code to my machine was that the only thing visible was the button. So I create all the components in their own methods and then added them to the frame and panels using the Border Layout. This then enabled me to put the instructions in the NORTH sections, the button in the SOUTH section and then the main bits would go in the CENTER section.
One thing to note about the sections: (From the documentation)
The components are laid out according to their preferred sizes and the constraints of the container's size. The NORTH and SOUTH components may be stretched horizontally; the EAST and WEST components may be stretched vertically; the CENTER component may stretch both horizontally and vertically to fill any space left over.
So you should add the component you want to scale in size to the CENTER section.
My main method now looks like this:
public static void main(final String[] args) {
final JButton submitAnswers = createSubmitAnswersButton();
final JPanel instructionsPanel = createInstructionsPanel();
final JPanel testContentPanel = createContentPanel();
testContentPanel.add(instructionsPanel, BorderLayout.NORTH);
testContentPanel.add(submitAnswers, BorderLayout.SOUTH);
final JScrollPane scrollingContentPane = createScrollPaneFor(testContentPanel);
final JFrame testFrame = createJFrame();
testFrame.add(scrollingContentPane, BorderLayout.CENTER);
testFrame.setVisible(true);
}

Creating a text field that dynamically resizes its width

I'm making what is basically a toolbar that contains a search field and some buttons. I'd like for the search field to grow in size (just the width) when the parent container gets wider, like when the user adjusts the split pane. Currently, the text field and the buttons will remain the same size and whitespace is added on either side as the container is widened. I can achieve this growing effect by using a BorderLayout in the container and putting the buttons on LINE_END and the text field in the CENTER. The problem I have with this is that the text field now becomes taller than a standard text field and it looks ugly. This behavior makes sense as the BorderLayout manager will give all the extra space (this includes vertical and hortizontal space) to the CENTER text field. I've tried to restrict this vertical growth by placing a maximum size on the text field, but BorderLayout will not honor it.
Here's what I've got:
final JTextField searchField = new JTextField("Enter your search terms");
searchField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));
final JPanel controls = new JPanel(new BorderLayout());
controls.add(searchField, BorderLayout.CENTER);
controls.add(new JPanel(){{
add(new JButton("Search")); add(new JButton("I'm Feeling Lucky"));
}}, BorderLayout.LINE_END);
It would seem that this behavior is a commonly desired one and it should be easy to implement, but I've had no luck after looking through all the Oracle/Sun tutorials and Google search results.
Anybody have any solutions to this? I need to stick with standard Java Swing components - no third party libraries please.
Thank you!
I would suggest you to use GridBagLayout , it is complicated but it is the most powerful layout.. When you learn it, you would not have any layout issue.
Here is the sample use of gridbaglayout for this question...
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class GridBagLayoutExample extends JFrame {
GridBagLayoutExample() {
initUI();
}
private void initUI() {
final JTextField searchField = new JTextField("Enter your search terms");
final JPanel controls = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.weightx=1.0;
c.fill=GridBagConstraints.HORIZONTAL;
controls.add(searchField,c);
controls.add(new JPanel(){
{
add(new JButton("Search")); add(new JButton("I'm Feeling Lucky")); }});
setLayout(new BorderLayout());
add(controls, BorderLayout.NORTH);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new GridBagLayoutExample().setVisible(true);
}
});
}
}
it's a two part problem:
a) all xxSize (xx = min/max/pref) are mere hints to the LayoutManager. Each and every LayoutManager has its own behaviour if/how/when it respects those hints and if/how/when it violates one or more if the overall space is more or less than would fit the sum of pref for all components in the container. No way but learn which does what (documentation is ... ehem ... suboptimal)
b) JTextField is a bit crazy in allowing its height to grow indefinitely if space allows. Implying that even LayoutManagers which respect max (like f.i. BoxLayout) have no chance to do so, max is Integer.MAX_VALUE (or Short.MAX? forgot). To make it behave, subclass and override maxSize to return the pref height:
#Override
Dimension getMaximumSize() {
Dimension max = super.getMaximumSize();
max.height = getPreferredSize().height;
return max;
}
You should be able to use a horizontal BoxLayout.
searchField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));
You should not guess at the height of the text field.
Dimension d = searchField.getPreferredSize();
d.width = Integer.MAX_VALUE;
searchField.setMaximumSize(d);
I know you said you don't want to use 3rd party libraries, but as a side note you might want to look at Text Field Prompt. It allows you to display a message that will disapear when you start typing in the text field.
Using BoxLayout.
JTextField search_field = new JTextField("Enter search term");
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
panel.add(search_field);
This will dynamically change the size of textfield when resizing the frame.

Categories

Resources