Java Swing GridBagLayout panel resize issues - java

I am working on a simple little swing application for a relative, but I'm having trouble implementing animations on it...
Here is a description of my layout:
I have a main window (created by instantiating, packing, and displaying a JFrame).
I have told the content panel of the main window to use GridBagLayout as its layout.
My grid is 2 grids wide, and 3 grids tall. In the first column I have three JButtons (A, B, and C), each taking up one row of vertical grid space, and one column of horizontal grid space. In the second column, I have another JPanel, which is one column wide, and three rows tall.
This second JPanel is also set to use GridBagLayout as its layout. In this case, there are two columns, and one row. The left column has a one collumn wide, one row tall JPanel with Button 1 inside of it. The right column consists of just a single JButton (Button 2), which is also one column wide, and one row tall.
Here is a screenshot of what I just described:
Now that you understand what the layout is, let me explain what I've been trying to do:
I've been trying to use the Universal Tween Engine to resize the Jpanel which contains Button 1. However, in my attempts, I get this as a result:
As you can probably see, the JPanel containing Button 1 has successfully resized! However, Button 2's size and position have not change at all, and, in fact, button 2 is getting cut off by the JPanel containing Button 1!
This is a problem, as I expected resizing the JPanel containing Button 1 to simultaneously change the cell sizes of its containing GridBagLayout. I was sorely mistaken on this matter, it seems.
So that brings me to my question... What do I need to do in order to "Update" my GridBagLayout so that it resizes its grids to accommodate the increase in size from the Jpanel containing Button 1? I have tried calling GridBagLayout.invalidatelayout() on my GridBaglayout, and I have also tried calling JFrame.getContentPane().invalidate() on my main window. Neither seemed to have had any effect.
Here is the compilable source code of my project:
ImageManager.java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import aurelienribon.tweenaccessors.swing.*;
import aurelienribon.utils.swing.*;
import aurelienribon.tweenengine.*;
import aurelienribon.tweenengine.equations.*;
public class ImageManager
{
/**
* #param args
*/
public static JFrame mainwindow;
public static TweenManager tweenManager;
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(new Runnable() //For thread safety!
{
public void run()
{
InitGUI();
InitTween();
}
});
}
private static void InitTween()
{
((Window)mainwindow).addWindowListener(new WindowAdapter() {
#Override public void windowOpened(WindowEvent e)
{
new DrawingCanvas()
{
#Override protected void update(int elapsedMillis)
{
tweenManager.update(elapsedMillis);
}
}.start();
}
});
}
private static void InitGUI()
{
//Init the window and layout systems
mainwindow = new JFrame("Imaffect");
Container pane = mainwindow.getContentPane();
pane.setLayout(new GridBagLayout());
//Begin creating the UI!
pane.add(new JButton("Button A"), new GridBagConstraints(0, 0, 1, 1, 0, 1, GridBagConstraints.PAGE_START, GridBagConstraints.VERTICAL, new Insets(0,0,0,0), 100, 0));
pane.add(new JButton("Button B"), new GridBagConstraints(0, 1, 1, 1, 0, 0, GridBagConstraints.PAGE_START, GridBagConstraints.VERTICAL, new Insets(0,0,0,0), 100, 100));
pane.add(new JButton("Button C"), new GridBagConstraints(0, 2, 1, 1, 0, 0, GridBagConstraints.PAGE_START, GridBagConstraints.VERTICAL, new Insets(0,0,0,0), 100, 20));
pane.add(InitPreviewGUI(), new GridBagConstraints(1, 0, 1, 3, 1, 1, GridBagConstraints.PAGE_START, GridBagConstraints.BOTH, new Insets(0,0,0,0), 0, 0));
//Finalize the window
mainwindow.pack();
mainwindow.setVisible(true);
}
private static JPanel InitPreviewGUI()
{
JPanel panel = new JPanel(new GridBagLayout());
panel.add(InitSettingsGUI(), new GridBagConstraints(0, 0, 1, 1, 0, 1, GridBagConstraints.PAGE_START, GridBagConstraints.BOTH, new Insets(0,0,0,0), 0, 0));
panel.add(new JButton("Button 2"), new GridBagConstraints(1, 0, 1, 1, 1, 1, GridBagConstraints.PAGE_START, GridBagConstraints.BOTH, new Insets(0,0,0,0), 0, 0));
return panel;
}
private static JPanel InitSettingsGUI()
{
JPanel panel = new JPanel();
SetupSettingsTweens(panel);
SetupSettingsContent(panel);
return panel;
}
private static void SetupSettingsTweens(JPanel panel)
{
Tween.registerAccessor(Component.class, new ComponentAccessor());
tweenManager = new TweenManager();
Tween.to(panel, ComponentAccessor.WIDTH, 1000)
.target(200)
.ease(Cubic.INOUT)
.repeatYoyo(-1, 200)
.delay(500)
.start(tweenManager);
}
private static void SetupSettingsContent(JPanel panel)
{
panel.add(new JButton("Button 1"));
}
}
If you want to compile the code yourself, you will need the following three resources:
Universal Tween Engine library jar
DrawingCanvas.java (package aurelienribon.utils.swing)
ComponentAccessor.java (package aurelienribon.tweenaccessors.swing)
If you would like to experience my problem first hand, without complicated project setup, you may download the Eclipse Juno project, or the runnable jar file:
Imaffect Eclipse Juno Project
Imaffect Runnable Jar File
If you would prefer to stay away from downloads, here is a list of what's going on:
Set up GUI structure
Set up requirements for tween (nothing special)
Set up a thread that uses SwingUtilities.invokeAndWait to call a function called update()
Whenever update() is called, it uses Container.setSize() to animate size change on the JPanel that contains Button 1.
Any help with this problem is greatly appreciated! Thankyou :)
P.S. If there is a duplicate of this question, I am very sorry. I googled around quite a bit, but I was unable to find anything even related to my problem.

I would say in your update() method you should change preferred size of Button1 and call
container.revalidate();
container.repaint();
Changing the preferred size and revalidate() updates layout and repaint() reflects the changes.
Alternatively you can call setSize() for Button1 increasing width and in the same time decrease width of Button2 and again call repaint() (without revalidate() because revalidation would restore original layout).

Related

JPanel losing focus and listeners aren't firing

So in my window, I set the JFrame to undecorated(true) and have my own custom header at the top (with closing and minimizing buttons). The only problem I'm having is making the window move when you drag this 'custom header'. The entire header is in a JPanel which is then added to the JFrame on the North side (BorderLayout.NORTH). I have a MouseListener and MouseMotionListener added to this JPanel, but it does not recognize any events. The only thing I can assume is how I have the layout figured out. Below is the code for the header, along with a visual to go along with it.
CODE:
private void addHeader()
{
headPane = new JPanel();
headPane.setLayout(new BoxLayout(headPane, BoxLayout.LINE_AXIS));
buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 2));
buttonPane.setBackground(mouseLineColor);
headPane.setBackground(Color.GREEN);
Font buttonFont = new Font("", Font.PLAIN, 18);
minimize.setFocusable(false);
minimize.setPreferredSize(new Dimension(30, 20));
minimize.setMargin(new Insets(0, 0, 0, 0));
minimize.setOpaque(false);
minimize.setBorder(null);
minimize.setForeground(Color.WHITE);
minimize.setOpaque(true);
minimize.setFont(buttonFont);
minimize.setBackground(buttonColor);
quit.setFocusable(false);
quit.setPreferredSize(new Dimension(30, 20));
quit.setMargin(new Insets(0, 0, 0, 0));
quit.setOpaque(false);
quit.setBorder(null);
quit.setForeground(Color.WHITE);
quit.setOpaque(true);
quit.setFont(buttonFont);
quit.setBackground(buttonColor);
back.setFocusable(false);
back.setPreferredSize(new Dimension(30, 20));
back.setMargin(new Insets(0, 0, 0, 0));
back.setOpaque(false);
back.setBorder(null);
back.setForeground(Color.WHITE);
back.setOpaque(true);
back.setFont(buttonFont);
back.setBackground(buttonColor);
if(screen != GAME_MENU)
buttonPane.add(back);
else
buttonPane.remove(back);
buttonPane.add(minimize);
buttonPane.add(quit);
headTitle = new JLabel("Bouncy Ball Version " + VERSION);
headTitle.setBorder(new EmptyBorder(0, 5, 0, 0));
headTitle.setFont(new Font("", Font.BOLD, 14));
headTitle.setForeground(Color.BLACK);
headTitle.setBackground(Color.YELLOW);
headTitle.setOpaque(true);
headTitle.setFocusable(false);
headPane.setFocusable(false);
buttonPane.setFocusable(false);
buttonPane.setBackground(Color.RED);
headPane.add(headTitle);
headPane.add(Box.createHorizontalGlue());
headPane.add(buttonPane);
if(callOnce)
{
minimize.addActionListener(this);
quit.addActionListener(this);
back.addActionListener(this);
minimize.addMouseListener(this);
quit.addMouseListener(this);
back.addMouseListener(this);
headPane.addMouseListener(this);
headPane.addMouseMotionListener(this);
callOnce = false;
}
headPane.setPreferredSize(new Dimension(headPane.getPreferredSize().width, 24));
frame.add(headPane, BorderLayout.NORTH);
}
LISTENERS:
MousePressed:
Object source = e.getSource();
if(source == headPane)
{
mouseX = e.getX();
mouseY = e.getY();
movingWindow = true;
}
MouseDragged:
Object source = e.getSource();
if(source == headPane)
{
if(movingWindow)
{
int x = e.getXOnScreen();
int y = e.getYOnScreen();
frame.setLocation(x - mouseX, y - mouseY);
}
}
I'll also add that when I click on the headPane, the JButtons will cease to work as well. I don't know why it's doing this, or if the answer is really simple and I'm just being stupid, but nothing I have tried has worked.
I'm fairly new to Java so thanks in advance for any help.
I see no need for the "callOnce" variable. The frame and the components added to the frame should only be created once when the class is created. If you are calling the "addHeader()" method more than once, then I suggest you have a design problem.
Also you should not be adding the ActionListeners to your buttons twice.
The only problem I'm having is making the window move when you drag this 'custom header'.
Check out Moving Windows for a general purpose class that allows you to drag any component. Typically you would drag a component within a panel.
However, the class also has a feature that allows you to drag a window on the desktop by dragging a component added to the window.

GridBagLayout anchor & JScrollPane issues

I have the following class
public class Demo {
private JFrame mainFrame;
static public Color BGCOLOUR1 = new Color(240, 240, 240);
public Demo() {
mainFrame = new JFrame("Demo");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setLayout(new BorderLayout());
mainFrame.setSize(900, 800);
JPanel centralPanel = new JPanel();
centralPanel.setOpaque(true);
centralPanel.setBackground(BGCOLOUR1);
centralPanel.setLayout(new GridBagLayout());
centralPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20),
BorderFactory.createTitledBorder("Panel")));
JPanel insidePanel = new JPanel();
insidePanel.setOpaque(true);
insidePanel.setBackground(BGCOLOUR1);
insidePanel.setLayout(new BorderLayout());
insidePanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Inside panel"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
JTextArea insidePanelText = new JTextArea(6, 50);
insidePanelText.setLineWrap(true);
insidePanel.add(insidePanelText);
centralPanel.add(insidePanel, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0));
JScrollPane scrollPane = new JScrollPane(centralPanel);
mainFrame.add(scrollPane);
}
Why is the inside panel positioned in the centre of the centralPanel (vertically) when I set the GridBagConstraints anchor to NORTH? I would like it positioned at the top.
Also, if I add the centralPanel in a JScrollPane before adding it to the mainFrame as per the example I can resize the application larger just fine, but as soon as I resize it smaller (even though it is still larger than I originally started it) a scroll bar appears. How can I prevent that from happening?
Edit: To illustrate the scrolling problem (I packed the frame when I took these screens):
When I start the application it has no scrollbars
I resize the window larger, and then smaller again. As soon as I make it smaller, the scrollbar appears.
As far as GridBagLayout is concerned, based on the properties you've supplied, it is been laid out to the NORTH of the cell.
GridBagLayout works mostly to the preferred size of the components and calculates the positions of each component around the center of the parent container.
If, instead of:
new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0)
I use something like:
new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0)
it will produce the desired result.
Basically, by using gridy and setting it to 1, we're asking GridBagLayout to give all the remaining vertical space to this cell, but because we're not filling the cell, the contents is pushed to the NORTH of the cell
Also, if I add the centralPanel in a JScrollPane before adding it to the mainFrame as per the example I can resize the application larger just fine, but as soon as I resize it smaller (even though it is still larger than I originally started it) a scroll bar appears. How can I prevent that from happening?
I couldn't really replicate this particular problem, I could use either pack or setSize and resized the window smaller to its "packed" size and the scroll bars would appear, but once I resized the window beyond the "packed" size the scroll bars would disappear

JLabel text Y paint coordinate

The default JLabel draws its text at the middle of its bounds. For example, if height of the label is 20, font height is 14, the Y coordinate would be (20 - 14)/2 = 3. Like this:
What should I do if want to align the text to the TOP of the JLabel bounds? Like this:
UPD:
public class LabelTest extends JFrame {
public LabelTest() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(500, 500);
setLocationRelativeTo(null);
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.X_AXIS));
contentPanel.add(Box.createHorizontalStrut(10));
final JLabel label1 = new JLabel("JLabel");
label1.setVerticalAlignment(SwingConstants.TOP); // by the answer of Kevin Workman, doesn't help
label1.setBorder(BorderFactory.createLineBorder(Color.RED));
label1.setFont(new Font("Arial", Font.PLAIN, 14));
contentPanel.add(label1);
setContentPane(contentPanel);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new LabelTest();
}
});
}
}
You should be packing the frame. If you so this, there should be no unused space in the label. If you want empty space, use an empty border
label.setBorder(new EmptyBorder(0, 0, 5, 0));
top, left, bottom, right
Also, don't set sizes, Use Layout Mangers and let them do the sizing for you. Setting sizes will give you. Setting sizes will give you a rigid look that may look and perform differently on different platforms. Layout Managers will allow your GUI to be more fluid and adaptable to different environments.
See Laying out Components Within a Container for more information on working with layouts
Also see Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
As always, the API is your best friend: http://docs.oracle.com/javase/7/docs/api/javax/swing/JLabel.html#setVerticalAlignment(int)
Edit- Based on your updated SSCCE, the problem is that your BoxLayout is shrinking the JLabel as small as it will go, so the vertical text position doesn't really matter. Try using a BorderLayout to check that.
The problem is that the insets of the JLabel are adding a small space to the top and bottom of the JLabel, so your text looks centered even though it's at the top. Here's a fix for the insets problem: How to change gap in swing label

Creating Square Buttons in Swing

I would like to create something like the following in Swing:
The top part is relatively easy: I can just create a table and display it. What I'm having trouble with is the square plus and minus buttons at the bottom, which are designed to add a new item or remove the selected item respectively. In particular, I haven't been able to make the square shape because on Mac OS X and some other platforms, JButtons are rectangles with rounded corners and I can't find a way to change that. Also, I'm wanting to make sure it's a perfect square and without any space in between buttons.
How can this be accomplished in a cross-platform way on Swing?
JButtons are rectangles with rounded corners and I can't find a way to change that.
Change the Border:
button.setBorder( new LineBorder(Color.BLACK) );
Edit.
Another approach is to create your own icon from an existing button. Something like the following:
JButton button = new JButton("+");
Dimension size = button.getPreferredSize();
size.x += 6;
size.y += 6;
button.setPreferredSize(size);
Rectangle rectangle = new Rectangle(3, 3, size.x - 3, size.y - 3);
ScreenImage buttonImage = ScreenImage(button, rectangle);
ImageIcon icon = new ImageIcon(buttonImage);
JButton plus = new JButton(icon);
plus.setBorder( ... );
The above code should create an image of your button on any platform. I increased the preferred size to avoid taking an image of the border.
You will need to use the Screen Image class.
This is most easily achieved by returning a preferred size that is NxN - where N is the larger of preferred width or height.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
class SquareButton extends JButton {
SquareButton(String s) {
super(s);
}
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
int s = (int)(d.getWidth()<d.getHeight() ? d.getHeight() : d.getWidth());
return new Dimension (s,s);
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
JComponent gui = new JPanel(new FlowLayout());
for (int ii=0; ii<5; ii++) {
gui.add(new SquareButton("" + ii));
}
gui.setBorder(new EmptyBorder(4, 8, 4, 8));
JFrame f = new JFrame("Square Buttons");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
You can set the size of a button using using setPreferredSize():
JButton button = new JButton("+");
button.setPreferredSize(new Dimension(10, 10));
You might be able to remove the rounded corners using:
button.setBorder(BorderFactory.createEmptyBorder());
If this does not work then you can override the paintComponent() method on JButton.
Well in order to make them square, you have 2 options: 1. Make the button hold an icon image of just a square image of a transparent image. 2. you could set the button dimensions on your own. I am not sure how to set the dimensions, but that is a an option you could choose. You can just create a JToolBar that is set on the BorderLayout.SOUTH end of the window whenever you add, and whatever buttons are added onto that, will be right next to each other. To add the buttons do this:
JButton button1 = new JButton("+");
JButton button2 = new JButton("-");
JToolBar toolbar = new JToolBar();
<JPanel,JFrame,Whatever>.add(toolbar, BorderLayout.SOUTH);
toolbar.add(button1);
toolbar.add(button2);
This will add the toolbar onto the JFrame, JPanel, or whatever you're adding it onto, and it will set it to the bottom of the screen as you see above.
Hope this helps!

How to get GridBagLayout to respect minimum size of a button or panel?

In the following GridBagLayout code, I'm expecting the specified minimum size of JButton btn2 to be respected when the JFrame is resized to be made smaller. But when I make the JFrame smaller, the btn2 gets smaller than its minimum size and then vanishes.
Can someone point me in the right direction of what I'm doing wrong? Maybe I have to set the minimum size of the JPanel that contains the buttons?
Any help is appreciated, thanks!
JFrame frame = new JFrame();
frame.setSize(400,300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setMinimumSize(new Dimension(400,300));
panel.setBackground(Color.RED);
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = null;
JButton btn1 = new JButton("btn1");
btn1.setPreferredSize(new Dimension(150,50));
btn1.setMinimumSize(new Dimension(150,50));
gbc = new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
new Insets(0,0,0,0), 0, 0);
panel.add(btn1, gbc);
JButton btn2 = new JButton("btn2");
btn2.setPreferredSize(new Dimension(150,150));
btn2.setMinimumSize(new Dimension(150,150));
gbc = new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
new Insets(0,0,100,100), 0, 0);
panel.add(btn2, gbc);
frame.getContentPane().add(panel);
frame.setVisible(true);
In addition to Andrew Thompson's answer:
The GBL DOES NOT respect the minimum size set on a component if the component's preferred size can and will be respected.
The GBL DOES respect the minimum size set on a component when the component's preferred size cannot be respected, that is: the component prefers to be of size (w,h) but the GBL sees this is impossible due to lack of space, and therefore it falls back on the advertised minimum size of the component.
It is therefore very much possible to make your component suddenly become larger than it was before when you downsize the containing frame. You only have to set a minimum size on it that is larger than the preferred size. So you are making the frame smaller, and suddenly your component becomes larger. That is... weird but possible :) All hail GBL.
Here is an example of a panel that implements this behavior (GBC is an easier class to use than the original GridBagConstraints):
private static JPanel getPanel() {
JPanel panel = new JPanel(new GridBagLayout());
final JTextField textField = new JTextField("test");
final String text = "text";
final JLabel label = new JLabel(text);
final JButton button = new JButton(new AbstractAction("Enlarge label!") {
private String actText = text;
#Override
public void actionPerformed(ActionEvent arg0) {
actText = actText + text;
label.setText(actText);
}
});
GBC g;
g = new GBC().locate(0, 0).weight(0.0, 0.0).align(GBC.WEST, GBC.HORIZONTAL, null);
textField.setMinimumSize(new Dimension(200,20));
panel.add(textField, g);
g = new GBC().locate(1, 0).weight(1.0, 0.0);
panel.add(label, g);
g = new GBC().locate(0, 1).weight(1.0, 1.0).span(2, 1);
panel.add(button, g);
return panel;
}
In general, I would say to not use components that must be fixed in size within a GBL. Wrap it inside other panels with different layout managers.
If you really want to use your fixed-sized component within a GBL, override its getPreferredSize() method and return the size you want. But I prefer to ignore these methods.
Maybe I have to set the minimum size of the JPanel that contains the buttons?
AFAIR GBL was notorious for ignoring sizing hints. No, a correction on that. To get sensible resizing of components within GBL, use the GridBagConstraints with appropriate values. Beware though, the behavior of the layout to not display any component that would be forced to less than its minimum size.
I would pack() the frame then set the minimum size on the frame. Here is how it might look, changing the last line to..
frame.pack();
frame.setVisible(true);
frame.setMinimumSize(frame.getSize());
Given the layout though, I would tend to put btn1 into the PAGE_START of the BorderLayout of a panel that is then added to the LINE_START of another BL. btn2 would go in the CENTER of the outer BL.

Categories

Resources