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?
Related
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'm currently making a GUI that makes use of the FlowLayout class. Now, this class is meant to allow components be set by their prefer sized methods and I believe, isn't supposed to have priority in setting the component size. However, when I used a setSize method for a JTextField, the FlowLayout object didn't seem to recognize the change size command. But when I used the setColumn method, the FlowLayout object did respond to the change in size command.
Why is this?
FlowLayout object didn't seem to recognize the change size command.
But when I used the setColumn method, the FlowLayout object did
respond to the change in size command. Why is this?
Form your own question i understand that you know FlowLayout works obeying component's preferred size. However to answer your question why really JTextFeild.setColumn(int) responds: Because,
As soon as setColumn(int) is called, it invalidate() the JTextFeild component and and all parents above it to be marked as needing to be laid out.
public void setColumns(int columns) {
int oldVal = this.columns;
if (columns < 0) {
throw new IllegalArgumentException("columns less than zero.");
}
if (columns != oldVal) {
this.columns = columns;
invalidate(); // invalidate if column changes
}
}
Then while laying out, FlowLayout calls the getPreferredSize() function of JTextFeild, which is overridden and implemented such that it returns the preferred width by adding the column width:
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (columns != 0) {
Insets insets = getInsets();
size.width = columns * getColumnWidth() +
insets.left + insets.right; // changing the width
}
return size;
}
Guess what! I am becoming fan of source code.
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);
}
}
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));
Bit of oddness, seen if I do the following:
import javax.swing.*;
public class FunkyButtonLayout {
public static void main(String[] args) {
JFrame frame = new JFrame("");
JPanel j0 = new JPanel(); // j0 gets added to the root pane
j0.setLayout(null);
JPanel j1 = new JPanel(); // j1 gets added to j0
j1.setLayout(null);
JButton b1 = new JButton(""); // b1 gets added to j1
j1.add(b1);
b1.setBounds(0, 0, 40, 32); // b1 is big
j0.add(j1);
j1.setBounds(0, 0, 32, 32); // j1 is not so big - b1 gets 'trimmed'
frame.getContentPane().setLayout(null); // <- seems to be needed :-(
frame.getContentPane().add(j0);
j0.setBounds(10, 10, 32, 32); // end result: a 32x32 button with
frame.setSize(125, 125); // a trimmed right border
frame.setVisible(true); // in the top-left corner
}
}
I get pretty much what I'm looking for, apart from the ability to position j0 in the root pane with a layout manager. If I change the
frame.getContentPane().setLayout(null);
line to
frame.getContentPane().setLayout(new java.awt.FlowLayout());
I see j0 draw as a 1x1 pixel # the middle of the screen :-(
Any ideas why? Note that this isn't just a FlowLayout thing - pretty much every layout manager messes this up.
I really want to have the net effect of the 'border trimmed on one side' button - it allows me to do the toolbar-button-cluster thing (the kind of thing that cage fighter tries to get rid of) with native-looking button controls - I cannot see another way of doing this, thanks to OS-level skins. So any ideas appreciated :-)
If you set the layout manager to null, you have to explicitly set the container's preferred size (that's why it's showing up so small).
If you are using setBounds on a component, you are over-riding the work that the layout manager for the parent container does.
I would remove all calls to setBounds and all calls to setLayout(null) and try to achieve the effect you are after using just layout managers.
For a really good explanation of how layout managers work, check out an old article I wrote at Sun
http://developer.java.sun.com/developer/onlineTraining/GUI/AWTLayoutMgr/
It's old, but talks about preferredSize and layout nesting pretty well.
Enjoy,
-- Scott
...any ideas why ?
Yes. That happens because when you remove the layout manager ( by setting it to null ) you're saying to the computer "I'll to all the laying work"; while using any other LayoutManager will attempt to ... well layout your components according to your needs ( based on the properties of the objects to be lay-ed )
So, I think it would be much better to instead try to create a Border instance and set it into the JButton instead of trying to tweak all the objects around it.
I'll see if I can came up with something quickly.
EDIT:
Oops, it wasn't any quick, but here it is ( I messed up with a 1px line that was annoying me )
alt text http://img22.imageshack.us/img22/8933/capturaby8.png
As I said before, setting the layout to null is not the best approach. Better is to create a custom border and set it to the button ( or set null border ).
Here's the code:
import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;
import java.awt.geom.*;
/**
* Sample usage of swing borders.
* #author Oscar Reyes
*/
public class ButtonBorderSample {
public static void main( String [] args ) {
// Pretty standard swing code
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
JPanel panel = new JPanel( new FlowLayout(
FlowLayout.CENTER, 0, 5 ) );
panel.add( createButton( "F I R S T" ) );
panel.add( createButton( "S E C O N D" ) );
panel.add( createButton( "T H I R D " ) );
frame.add( panel , BorderLayout.NORTH );
frame.pack();
frame.setVisible( true );
}
/**
* Utility method to create a button.
* Creates the button, make it square, and add our custom border.
*/
private static JButton createButton( String s ) {
JButton b = new JButton( s );
b.setPreferredSize( new Dimension( 100, 100 ) );
b.setBorder( new NoGapBorder() );
return b;
}
}
/**
* This border implementation. It doesn't have insets and draws only a
* few parts of the border
* #author Oscar Reyes
*/
class NoGapBorder implements Border {
private final Insets insets = new Insets( -1, -1 , -1, -1 );
/**
* Defines in Border interface.
* #return The default insets instace that specifies no gap at all.
*/
public Insets getBorderInsets(Component c ) {
return insets;
}
/**
* Defines in Border interface.
* #return false always, it is not relevant.
*/
public boolean isBorderOpaque() {
return false;
}
/**
* Paint the border for the button.
* This creates the difference between setting the border to null
* and using this class.
* It only draws a line in the top, a line in the bottom and a
* darker line
* in the left, to create the desired effect.
* A much more complicated strtegy could be used here.
*/
public void paintBorder(Component c, Graphics g,
int x, int y, int width, int height) {
Color oldColor = g.getColor();
int h = height;
int w = width;
g.translate(x, y);
// Color for top and bottom
g.setColor( c.getBackground().brighter() );
// draw top line
g.drawLine(1, 0, w-2, 0);
// draw bottom line
g.drawLine(0, h-1, w-1, h-1);
// change the color to make it look as a division
g.setColor( c.getBackground().darker() );
// draw the left line
g.drawLine(0, 0, 0, h-2);
// set the graphics back to its original state.
g.translate(-x, -y);
g.setColor(oldColor);
}
}
EDIT
Dave Carpeneto wrote:
**Oscar>***Unfortunately this stops working once you UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); , and this was also core to my needs (I'm looking to make this look as native as possible).*
Well, I was not trying to make your work, but to answer to your question, you thought your problems had to do with LayoutManagers and I said that was not the problem.
Perhaps I should've stopped there, but my "programmer" itch make me continue with the sample. : )
I'm glad you've solve your problem at the end ;)
Hiya - thanks everyone for your assistance.
Dan > it was your comment about preferred layout that got me to get this working - adding j0.setPreferredSize(new java.awt.Dimension(32, 32)); was all that was needed to get this to work.
Oscar > Unfortunately this stops working once you UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); , and this was also core to my needs (I'm looking to make this look as native as possible).
For example, here's what mine looks like with 3 buttons on XP:
alt text http://img19.imageshack.us/img19/8595/minems5.png
... and here's what yours looks like with the XP look:
alt text http://img102.imageshack.us/img102/5412/yoursod4.png
... which unfortunately isn't the same thing - sorry for not being clearer in my requirements :-(
FWIW, here's the code (images are transparent icons the same size as the buttons, with the vertical lines as part of the icon):
import java.awt.*;
import javax.swing.*;
public class FunkyButtonLayout {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
JFrame frame = new JFrame("x");
Container y = frame.getContentPane();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
y.setLayout(new FlowLayout());
JPanel uberButton = new JPanel();
uberButton.setLayout(null);
uberButton.setSize(98, 32);
uberButton.setPreferredSize(new Dimension(98, 32));
JButton record = new JButton(new ImageIcon("img/record.png"));
record.setBounds(0, 0, 40, 32);
record.setEnabled(true);
record.setFocusPainted(false);
JPanel _record = new JPanel();
_record.setLayout(null);
_record.setBounds(0, 0, 33, 32);
JButton pause = new JButton(new ImageIcon("img/pause.png"));
pause.setBounds(-4, 0, 44, 32);
pause.setEnabled(true);
pause.setFocusPainted(false);
JPanel _pause = new JPanel();
_pause.setLayout(null);
_pause.setBounds(33, 0, 33, 32);
JButton stop = new JButton(new ImageIcon("img/stop.png"));
stop.setBounds(-4, 0, 36, 32);
stop.setEnabled(true);
stop.setFocusPainted(false);
JPanel _stop = new JPanel();
_stop.setLayout(null);
_stop.setBounds(66, 0, 32, 32);
_record.add(record);
_pause.add(pause);
_stop.add(stop);
uberButton.add(_record);
uberButton.add(_pause);
uberButton.add(_stop);
y.add(uberButton);
frame.pack();
frame.setVisible(true);
}
}
Scott> I have been schooled :-) thanks