Dave Carpeneto
Dave Carpeneto

Reputation: 1080

java swing - layout oddness when using different layout managers

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 :-)

Upvotes: 1

Views: 4301

Answers (4)

Dave Carpeneto
Dave Carpeneto

Reputation: 1080

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

Upvotes: 0

OscarRyz
OscarRyz

Reputation: 199264

...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 <a href="http://stackoverflow.com/users/20654">Oscar Reyes</a>
 */
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 <a href="http://stackoverflow.com/users/20654">Oscar Reyes</a>
 */
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 ;)

Upvotes: 1

Scott Stanchfield
Scott Stanchfield

Reputation: 30642

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

Upvotes: 1

Dan Dyer
Dan Dyer

Reputation: 54495

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.

Upvotes: 4

Related Questions