I have a Java Applet with a GridLayout containing widgets which I wish to be square, and remain tightly packed to each other (so their sizes are unrestricted).
However, I wish for the GridLayout to take up as much space as possible before being too large for the screen or unable to preserve widget 'squareness'.
Note that the number of rows and columns in the GridLayout are not necessarily equal (the Grid as a whole can be non-square)
This Applet is displayed via this html file;
<html>
<body>
<applet code=client.Grid.class
archive="program.jar"
width=100% height=95%>
</applet>
</body>
</html>
Currently, this makes the Applet expand into the window it is put in; the Grid can be resized by resizing the window, but this causes the geometry of each widget to be changed (losing 'squaredness').
So; where and how do I place these geometrical restrictions?
It can't be in the html file alone, since it has no knowledge of row/column count, and so doesn't know the best size to make the Applet.
However, I don't know how to set the size on the GridLayout or the Panel containing it, since it must know the viewing-browser's page size (to make it as large as possible) and I'm of the impression that the html specified geometry overrides the Applet specified.
EDIT:
Attempting to implement Andrew's suggestion;
screen = new JPanel(new GridLayout(rows, columns)) {
public Dimension getPreferredSize() {
Dimension expected = super.getPreferredSize();
// calculate preferred size using expected, rows, columns
return new Dimension(100, 100) // testing
}
public Dimension getSize() {
return getPreferredSize();
}
};
I understand this ignores the 'minimum size' stuff, but that doesn't matter at the moment.
Screen is placed in the center of a border layout, containing other widgets
getContentPane().add(screen, BorderLayout.CENTER);
getContentPane().add(otherWidgets, BorderLayout.PAGE_END);
I know this doesn't make screen centered in the space it has, but that's not entirely necessary at the moment so I want to keep things as simple as possible.
This isn't at all working; there's no visible difference from what I had before (when viewed through Eclipse; I haven't even reached the html stage yet) excepting the minimum size stuff. The screen component is still being re-sized by the applet at leisure, making the cells 'unsquare'. What am I doing wrong?
Put the grid layout container into a grid bag layout as the only component with no constraint, as seen in this answer. That will center it.
Update
And of course, put it in a component that returns a preferred size equating to the maximum square size it can manage depending on the parent size. Such as in SquarePanel.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
/**
* A square panel for rendering. NOTE: To work correctly, this must be the only
* component in a parent with a layout that allows the child to decide the size.
*/
class SquarePanel extends JPanel {
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
System.out.println("Preferred Size: " + d);
int w = (int) d.getWidth();
int h = (int) d.getHeight();
// Set s to the larger of the mimimum component width or height
int s = (w > h ? w : h);
Container c = getParent();
if (c != null ){
Dimension sz = c.getSize();
if ( d.getWidth()<sz.getWidth() ) {
// Increase w to the size available in the parent container
w = (int)sz.getWidth();
System.out.println("WxH: " + w + "x" + h);
// recalculate s
s = (w < h ? w : h);
}
if ( d.getHeight()<sz.getHeight()) {
// Increase h to the size available in the parent container
h = (int)sz.getHeight();
System.out.println("WxH: " + w + "x" + h);
// recalculate s
s = (w < h ? w : h);
}
}
// Use s as the basis of a square of side length s.
System.out.println("Square Preferred Size: " + new Dimension(s, s));
return new Dimension(s, s);
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
#Override
public Dimension getSize() {
return getPreferredSize();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
// A single component added to a GBL with no constraint
// will be centered.
JPanel gui = new JPanel(new GridBagLayout());
gui.setBackground(Color.BLUE);
SquarePanel p = new SquarePanel();
p.setBorder(new EmptyBorder(5,15,5,15));
p.setLayout(new GridLayout(3,0,2,2));
for (int ii=1; ii<13; ii++) {
p.add(new JButton("" + ii));
}
p.setBackground(Color.red);
gui.add(p);
JFrame f = new JFrame("Demo");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://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);
}
}
Related
AdoptOpenJDK 11 on Windows 10. JFrame descendant constructor:
getGraphicsConfiguration().getBounds()
java.awt.Rectangle[x=0,y=0,width=2560,height=1440]
Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration())
java.awt.Insets[top=0,left=0,bottom=40,right=0]
set*Size(new Dimension(2560, 1400));
pack();
getSize();
java.awt.Dimension[width=2560,height=1400]
getInsets();
java.awt.Insets[top=31,left=8,bottom=8,right=8]
getContentPane().getSize()
java.awt.Dimension[width=2544,height=1361]
... and yet the window does not cover full desktop space minus the task bar (because of those extra insets of 8). Why?
Example code using my answer:
package javaapplication2;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public final class TestFrame extends JFrame {
private static final long serialVersionUID = 1L;
private static final class CustomPanel extends JPanel {
private static final long serialVersionUID = 1L;
public CustomPanel(Dimension d) {
setMinimumSize(d);
setPreferredSize(d);
setMaximumSize(d);
//this is just an example, there is custom layout code here, not using Swing, but it needs Dimension d to work
}
}
public TestFrame() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
GraphicsConfiguration gc = getGraphicsConfiguration();
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
Dimension expectedFrameSize = new Dimension(screenBounds.width - screenInsets.left - screenInsets.right, screenBounds.height - screenInsets.top - screenInsets.bottom);
setExtendedState(JFrame.MAXIMIZED_BOTH);
setVisible(true);
Dimension frameSize = getSize();
Insets frameInsets = getInsets();
setVisible(false);
Dimension contentSize = new Dimension(frameSize.width - frameInsets.left - frameInsets.right, frameSize.height - frameInsets.top - frameInsets.bottom);
getContentPane().add(new CustomPanel(contentSize));
setVisible(true);
setResizable(false);
System.out.println("screenBounds " + screenBounds);
System.out.println("screenInsets " + screenInsets);
System.out.println("expectedFrameSize " + expectedFrameSize);
System.out.println("frameSize " + frameSize);
System.out.println("frameInsets " + frameInsets);
System.out.println("contentSize " + contentSize);
System.out.println("assert expectedFrameSize == frameSize " + expectedFrameSize.equals(frameSize));
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TestFrame().setVisible(true);
}
});
}
}
I would like to be able to calculate Dimension dimension without making the window temporarily visible. But it's impossible to arrive at correct sizes using methods available in the JDK:
getGraphicsConfiguration().getBounds()
Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration())
The insets of a frame describe size of the window decoration, which may include semi-transparent borders. On Microsoft Windows, there’s also a connection to the fact that window borders used to be 8 pixels thick in earlier versions.
When you maximize the window, its bounds will be set in a way that these insets are outside the visible areas, except for some parts of the title bar, which stays visible. To make predictability even worse, the title bar changes its layout when the window has been maximized, having less margin space.
But when you are looking for a Windows-only solution, the legacy aspects may help here. Consider the article “Why does a maximized window have the wrong window rectangle?”. As it explains, the window will always be at (-n,-n) and have (2n×2n) more than the actual visible area, for the border size n, for compatibility with old software. But, as it also explains, the maximized mode is special, as these borders are always cut off, so they do not show up in other monitors nor over the task bar.
That’s why it is impossible to emulate the same behavior by setting bounds only. Not only does the rendition of the title bar differ, you would also make parts of the window appear in other monitors on multi-monitor systems and over the task bar.
So, using that knowledge, you can predict the content pane size:
JFrame frame = new JFrame("Max");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
Timer t = new Timer(1000, ev -> System.out.println("actual size is "
+frame.getContentPane().getWidth()+" x "+frame.getContentPane().getHeight()));
t.setRepeats(false);
t.start();
Rectangle scrBounds = frame.getGraphicsConfiguration().getBounds();
Insets scrInsets = frame.getToolkit().getScreenInsets(frame.getGraphicsConfiguration());
Insets winInsets = frame.getInsets();
int width = scrBounds.width - scrInsets.left - scrInsets.right;
int height = scrBounds.height - scrInsets.top - scrInsets.bottom
- winInsets.top + winInsets.bottom;
System.out.println("content pane size will be "+width+" x "+height);
frame.setVisible(true);
This assumes that the historical border thickness is the same for all edges, so we can use the bottom size to determine the top size with the title bar size, so top - bottom gives us the remaining title bar size.
Apparently maximized frame has size larger than actual screen, and frame insets include 8,8,8,8 pixels (on my Windows) of which some are outside actual frame. The only way to get actual frame size that is used for maximized window is to make it visible for a while. In the end I resorted to this cheat:
setExtendedState(JFrame.MAXIMIZED_BOTH);
setVisible(true);
Dimension size = getSize();
Insets insets = getInsets();
setVisible(false);
dimension = new Dimension(size.width - insets.left - insets.right, size.height - insets.top - insets.bottom);
This allows me to find out what will be the dimension of the frame's content pane with frame taking all of desktop space (screen minus taskbar). And setResizable(false) has to be called only after final setVisible(true).
See if those numbers make sense for you:
screen bounds java.awt.Rectangle[x=0,y=0,width=2560,height=1440]
screen insets java.awt.Insets[top=0,left=0,bottom=40,right=0]
frame size java.awt.Dimension[width=2576,height=1416]
frame insets java.awt.Insets[top=31,left=8,bottom=8,right=8]
calculated dimension java.awt.Dimension[width=2560,height=1377]
Frame size is larger than screen space (screen bounds minus screen insets) by 16 in each direction. And those extra pixels are included in frame insets, even though they are not in the frame.
There are many questions of the converse, inserting a JTextPane into a JPanel. This is not my question. I need to be able to insert a custom JPanel (with drag and drop, drag, and mouse click listeners) into a JTextPane, which is then put into a JScrollPane, and finally put into a JFrame for displaying. The reason is because I need to have an image with support for resizing by dragging it within a chat client, which is itself primarily text.
Conveniently enough, there is a relevant method in JTextPane: insertComponent(Component c), but whenever I use it, my components end up being squished to exactly one line of text worth of space (even though they report having a larger size). This is perfect for plain buttons, but if I need anything larger, I'm out of luck. I can insert images by themselves just fine, with ImageIcons, but images wrapped inside a JPanel don't work at all (plus I can't add any listeners to ImageIcons, since they're not GUI elements; overriding one isn't an option).
Whenever a user drags an image into the chat client, this bit of code inserts the custom JPanel:
private void sendImage(BufferedImage im, int cl) throws IOException {
if(output == null) return;
//Send the image itself over to your friend
byte[] toSend = toBytes(im, cl);
sendString(nickname.hashCode() + "image"); //Header for image
output.writeInt(toSend.length); //Tells how many bytes to read.
output.write(toSend);
//Let the user know that the image was sent
float linmb = (float)(toSend.length / 1048576.0); //Size of file sent
addText("\n" + nickname + " sent an image! (" + linmb + " MB)\n", Color.RED.darker());
//Show the image itself
DraggerPanel d = new DraggerPanel(im, true);
text.insertComponent(d);
d.repaint();
//Spacer
addText("\n");
}
This is the source for DraggerPanel, the custom JPanel that holds an image:
public class DraggerPanel extends JPanel {
private BufferedImage image; //The image we're drawing
private Point startingPoint = null; //Starting point for resizing
private boolean first = true; //Is this the first drag?
private boolean lockedDrag; //If true, then lock x and y to be proportionally dragged.
public DraggerPanel(BufferedImage image, boolean lockedDrag) {
super();
this.image = image;
this.lockedDrag = lockedDrag;
//The listener for dragging events.
addMouseMotionListener(new MouseMotionListener() {
private int inWidth = 0, inHeight = 0; //Initial height and width values
private double ratio = 0; //Ratio of height to width for locked drag.
public void mouseDragged(MouseEvent m) {
if (first) { //If we're first, record initial position.
startingPoint = m.getPoint();
first = false;
inWidth = getWidth();
inHeight = getHeight();
ratio = (double)inHeight / inWidth;
} else { //Otherwise, change the size of the window.
if (!lockedDrag) {
int w = (int)startingPoint.getX() - m.getX();
int h = (int)startingPoint.getY() - m.getY();
setSize(Math.abs(inWidth - w), Math.abs(inHeight - h));
} else {
int w = (int)startingPoint.getX() - m.getX();
int h = (int)((double)ratio * w);
setSize(Math.abs(inWidth - w), Math.abs(inHeight - h));
}
}
repaint();
}
public void mouseMoved(MouseEvent m){
}
});
//Lets us know when you're not dragging anymore.
addMouseListener(new MouseAdapter(){public void mouseReleased(MouseEvent m){first = true;}});
//Set appropriate size.
if(image != null) setSize(image.getWidth(), image.getHeight());
else setSize(200,200);
//We're live, baby.
setVisible(true);
}
public void paint(Graphics g) {
if (image == null) super.paint(g);
else g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
}
Update 1: I followed #camickr 's advice, and updated the DraggerPanel to use setPreferredSize instead of setSize, as well as overrode paintComponent() instead of paint(). Now, the image has the proper height, but is stretched to the width of the JTextPane (which seems like what it was doing before). Furthermore, resizing doesn't seem to matter- the image doesn't change its size at all. Mouse events are definitely going through, but not affecting the size. It seems as though the original problem isn't fully resolved, since the JPanel's size isn't what I need it to be, and the solution to that will also lead to a solution to the resizing issue.
Update 2: I did it! I finally did it. To the future time travelers who have this issue, I basically yelled at the JTextPane by not only using setSize() in my overridden JPanel, but also setPreferredSize() and setMaximumSize(). The preferred one works well with height, and the maximum sets the width (God knows why). Thanks for your tips, #camickr!
my components end up being squished to exactly one line of text worth of space (even though they report having a larger size).
I would guess the size is not important.
I would think you need to override the getPreferredSize() method of your DraggerPanel to return the preferred size of the panel so the text pane can display the panel.
Also, custom painting is done by overriding the paintComponent(...) method NOT the paint() method.
I have got this piece of code:
frame.setTitle("SideScroller");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
Dimension dim = new Dimension(1000, 500);
frame.setContentPane(gamePanel); //gamePanel is an extended JPanel
gamePanel.setPreferredSize(dim);
frame.pack();
After I run this code and fill the gamePanel with parts of 50x50p, which should fill the gamePanel and thus the frame completely, there are some empty rows of pixels at the right and bottom of the screen.
frame.getSize(); //gives us: 1016 x 539
frame.getInsets(); //gives: Top: 31 | Left: 8 | Bottom: 8 | Right: 8
An image of the result:
As you can see, there is still some empty room in the JFrame.
The code to fill the gamePanel:
//Fill the panel with landscape
//One part : 50x50 px
//Panel : 1000x500 px
//Width : 20 prts
//Height : 10 prts
#Override
public void paint(Graphics g){
super.paint(g);
for(int i = 0; i < 20; i++){
for(int j = 0; j < 10; j++){
//Decide which landscape-img should be used
switch(levelOne[j][i]){
case 0:g.drawImage(new ImageIcon("resources\\landscape-img\\air.png")
.getImage(), 50 * i, 50 * j, null);
break;
case 1:g.drawImage(new ImageIcon("resources\\landscape-img\\ground.png")
.getImage(), 50 * i, 50 * j, null);
break;
}
}
}
g.dispose();
}
I do not see why the frame isn't filled fully. Hope someone can help me!
Few hints to help you to solve your problem.
1) If you need your game panel have a preferred size, then just override getPreferredSize() like this:
public class GamePanel extends JPanel {
...
#Override
public Dimension getPreferredSize() {
return isPreferredSizeSet()
? super.getPreferredSize() : new Dimension(1000, 500);
}
...
}
See also Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
2) Be aware that you should not override paint() method but paintComponent() instead. See A Closer Look at the Paint Mechanism.
public class GamePanel extends JPanel {
...
#Override
protected void paintComponent(Graphics g) {
// Your implementation here
}
...
}
3) As already suggested just add your gamePanel to the frame's content pane directly:
frame.getContentPane().add(gamePanel);
This way you will take advantage of its default Layout Manager: BorderLayout to make your panel fill the whole center area.
4) Finally, while you can perfectly use Swing to make games it's not the best API to do so because it is designed for desktop applications. You might want to consider use some library intended to games development such as libgdx
Found the answer here. I called frame.setResizable(false) after I called frame.pack(), apparently that messes the frame up. You have to call that before pack() (or `setVisible(true)).
My question is how can I specify size of the parts on my layout?
I need somehow set size of "parts" not using preferedSize, maybe in layout managers, doesn't matter where - only I need is stable size.
I want to create layout for game. I've already created one but I'm dealing with problem with size of components. So I considered that it would be better to make better concept of my layout.
Let's look at my draft.
+-----------+
| UPPER |
+-----+-----+
| A | |
+-----+ C |
| B | |
+-----+-----+
| Footer |
+-----------+
A+B+C make together Center.
Main part consist of this tree parts:
Upper- there will be menu.
Center - this consists of 3 parts A,B,C
Footer - there will be status bar
My idea is to be able to set the size of each component.
All layout is dependent on part C it could have size 450x450 px or 600x600 px.
For part A and B i need specify only the width, because there will be only some text info - it should be about 300 px.
I tryed to use GridBagLayout for Center part but setSize for C didn't worked well.
I make the parts in Containers (java.awt.Container) - in them I add the content of each part and then add the Container to the upper level.
The simplest way: use BorderLayout for the contentPane (which already is)
- Upper panel goes to North
- Footer panel goes to South
- Panels A and B goes into a Panel ab with GridLayout(2,1)
- Panel ab and C goes into a Panel abc with GridLayout(1,2)
- Panel abc goes into the Center
And setPrefferedSize() of your A, B, C
In general, GridBagLayout ignores the values you set for controls with setSize, instead it asks the controls for their preferred size (by calling getPreferredSize) and uses that for calculating the overall layout. Simply setting that preferred size yourself is not recommended, since most controls tend to recalculate those values whenever a layout is triggered, so you will have a hard time getting them to "stick".
If you really want to make sure the UI element C has a certain size, implement it as a custom class deriving from a suitable base (JPanel, for example) and override the getPreferredSize method to make it return the size you want/need for that part of your UI.
Edit: Here's a little example for a wrapper that can contain another UI element and can be set to a fixed size (using the setSize method which has been overridden), which should be respected by layout managers:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JComponent;
import javax.swing.JPanel;
public class FixedSizeComponent extends JPanel {
private Dimension size;
private final JComponent content;
public FixedSizeComponent(JComponent content) {
super(new BorderLayout());
this.content = content;
super.add(content, BorderLayout.CENTER);
}
#Override
public void setSize(Dimension d) {
size = d;
}
#Override
public void setSize(int width, int height) {
size = new Dimension(width, height);
}
#Override
public Dimension getSize() {
if (size != null) return size;
return content.getSize();
}
#Override
public Dimension getSize(Dimension rv) {
if (size != null) {
if (rv == null) rv = new Dimension();
rv.height = size.height;
rv.width = size.width;
return rv;
}
return content.getSize(rv);
}
#Override
public Dimension getPreferredSize() {
if (size != null) return size;
return content.getPreferredSize();
}
#Override
public Dimension getMaximumSize() {
if (size != null) return size;
return content.getMaximumSize();
}
#Override
public Dimension getMinimumSize() {
if (size != null) return size;
return content.getMinimumSize();
}
}
I had a similar problem, with a status tool bar at the bottom containing a number of other components. My problem was that it would get taller. So what I did was to override the maximum size setting the maximum height to be the minimum height.
JPanel status = new JPanel( new SpringLayout() ) {
#Override
public Dimension getMaximumSize() {
Dimension max = super.getMaximumSize();
Dimension min = getMinimumSize();
return new Dimension( max.width, min.height );
}
};
This answer is too little and WAY too late,
(maybe this method did not exist at the time of asking of this question)
just like getPreferredSize, there is also a setPreferredSize method which takes a Dimension object.
By default, your layout will ignore your components sizes (which you may have set using setSize), instead it will use the preferred sizes.
By using setPreferredSize, you will be able to override the default preferred sizes of the component
I hope my answer can help you in some way. From experience with setting JPanel or JFrame size, I have always used setPreferredSize(new Dimension(WIDTH,HEIGHT));
sometime ago I read this article that shows a way to implement mouse resizable components in Swing.
The author uses a null LayoutManager in order to allow absolute component positioning.
I know that a null layout should never be used, so my question is:
is there any already implemented LayoutManager that allow component's absolute positioning, or I have to implement it my own?
As alternatives, also consider
How to Use Internal Frames.
Resizing Components in conjunction with Moving Windows.
An exisiting framework, such as JGraph or JUNG.
A layout manager really does 3 things:
Set the location of a component. Since you need the ability to drag the component around, you would not want your layout manager to do this.
Set the size of a component. Since you need the ability to resize the component then you would not want to do this. However, you might want to give the component a default size based on the components preferred size. This way you don't need to specify the size when you create the component.
Determine the preferred size of the parent panel based on the components added to it. This will allow scroll panes to function properly as scrollbars can be added/removed as required. So you need to determine the behaviour of how dragging should work. That is, are you allowed to drag the component outside the current bounds of the panel. If so the the preferred size of the panel should automatically increase.
is there any already implemented LayoutManager that allow component's absolute positioning
I've been playing around with a layout manager that is close to your needs. It was designed to be used with the ComponentMover class from the Moving Windows link provided by trashgod.
Here is my test code for this class:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
/**
*/
public class DragLayout implements LayoutManager, java.io.Serializable
{
public DragLayout()
{
}
/**
* Adds the specified component with the specified name to the layout.
* #param name the name of the component
* #param comp the component to be added
*/
#Override
public void addLayoutComponent(String name, Component comp) {}
/**
* Removes the specified component from the layout.
*
* #param comp the component to be removed
*/
#Override
public void removeLayoutComponent(Component component)
{
}
/**
* Determine the minimum size on the Container
*
* #param target the container in which to do the layout
* #return the minimum dimensions needed to lay out the
* subcomponents of the specified container
*/
#Override
public Dimension minimumLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
return preferredLayoutSize(parent);
}
}
/**
* Determine the preferred size on the Container
*
* #param parent the container in which to do the layout
* #return the preferred dimensions to lay out the
* subcomponents of the specified container
*/
#Override
public Dimension preferredLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
return getLayoutSize(parent);
}
}
/*
* The calculation for minimum/preferred size it the same. The only
* difference is the need to use the minimum or preferred size of the
* component in the calculation.
*
* #param parent the container in which to do the layout
*/
private Dimension getLayoutSize(Container parent)
{
Insets parentInsets = parent.getInsets();
int x = parentInsets.left;
int y = parentInsets.top;
int width = 0;
int height = 0;
// Get extreme values of the components on the container
for (Component component: parent.getComponents())
{
if (component.isVisible())
{
Point p = component.getLocation();
Dimension d = component.getPreferredSize();
x = Math.min(x, p.x);
y = Math.min(y, p.y);
width = Math.max(width, p.x + d.width);
height = Math.max(height, p.y + d.height);
}
}
// Width/Height is adjusted if any component is outside left/top edge
if (x < parentInsets.left)
width += parentInsets.left - x;
if (y < parentInsets.top)
height += parentInsets.top - y;
// Adjust for insets
width += parentInsets.right;
height += parentInsets.bottom;
Dimension d = new Dimension(width, height);
return d;
// return new Dimension(width, height);
}
/**
* Lays out the specified container using this layout.
*
* #param target the container in which to do the layout
*/
#Override
public void layoutContainer(Container parent)
{
synchronized (parent.getTreeLock())
{
Insets parentInsets = parent.getInsets();
int x = parentInsets.left;
int y = parentInsets.top;
// Get X/Y location outside the bounds of the panel
for (Component component: parent.getComponents())
{
if (component.isVisible())
{
Point location = component.getLocation();
x = Math.min(x, location.x);
y = Math.min(y, location.y);
}
}
x = (x < parentInsets.left) ? parentInsets.left - x : 0;
y = (y < parentInsets.top) ? parentInsets.top - y : 0;
// Set bounds of each component
for (Component component: parent.getComponents())
{
if (component.isVisible())
{
Point p = component.getLocation();
Dimension d = component.getPreferredSize();
component.setBounds(p.x + x, p.y + y, d.width, d.height);
}
}
}}
/**
* Returns the string representation of this column layout's values.
* #return a string representation of this layout
*/
public String toString()
{
return "["
+ getClass().getName()
+ "]";
}
public static void main( String[] args )
{
ComponentMover cm = new ComponentMover();
cm.setEdgeInsets( new Insets(-100, -100, -100, -100) );
// cm.setEdgeInsets( new Insets(10, 10, 10, 10) );
cm.setAutoLayout(true);
JPanel panel = new JPanel( new DragLayout() );
panel.setBorder( new MatteBorder(10, 10, 10, 10, Color.YELLOW) );
createLabel(cm, panel, "North", 150, 0);
createLabel(cm, panel, "West", 0, 100);
createLabel(cm, panel, "East", 300, 100);
createLabel(cm, panel, "South", 150, 200);
createLabel(cm, panel, "Center", 150, 100);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane(panel) );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void createLabel(ComponentMover cm, JPanel panel, String text, int x, int y)
{
JLabel label = new JLabel( text );
label.setOpaque(true);
label.setBackground( Color.ORANGE );
label.setLocation(x, y);
panel.add( label );
cm.registerComponent( label );
}
}
For this layout the size is always assumed to be the preferred size. You would need to change this. Maybe set the size to be the preferred size when the size is (0, 0). You will also need to use the size of the component (not its preferred size) when determining the preferred size of the parent container.
The ComponentMover class can be configured to allow you to drag comopnents outside the bounds of the parent container or to keep the component inside the bounds. If you allow components to be moved outside the bounds, then the preferred size is automatically adjusted to take into account the new location of the component.
If you drag a component outside the top or left bounds, then all the components are shifted (right or down) do make sure no component has a negative location.
I guess it would depend on the specifics of how you wanted it to behave.
The main reason the null layout manager is discouraged is because of the fact that interfaces built using that can only be used in the size they were designed - You can't resize the UI. If this is fine for you, use it.
Another option I know of is the AbsoluteLayout that Netbeans is distributed with. You can get more info here:
http://www.java-tips.org/other-api-tips/netbeans/can-i-distribute-absolutelayout-with-my-applica.html.
I think this might be exactly what you are looking for, but as you can see from that link, they recommend rather using a Null layout... I don't think it makes much of a difference either way.
If you need to be able to allow users to define how the components will resize as well, you'll end up building something like the Netbeans Matisse form designer, which is probably overkill and does not strike me as much fun :)
The question is somewhat vague, so I might be missing the point completely. I assume that you are looking for a layout that will allow you to use absolute positioning, but will still allow you to resize the component and use all available space.
If you are handcoding it, I've had success with MIGLayout (http://www.miglayout.com/) and TableLayout (Which is less absolute but very easy to use - http://java.sun.com/products/jfc/tsc/articles/tablelayout/)
If you are using some Form designer, using GroupLayout might be a good choice, but you do not want to hand-code it. See this question :
GroupLayout: Is it worth learning?