suitable LayoutManager for resizable components
Asked Answered
R

4

7

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?

Roar answered 13/10, 2011 at 7:5 Comment(3)
Are you trying to make mouse resizable components? If so, why?Unstring
@Andrew Thompson: I have a sort of canvas where the user should be able to insert components of different shapes, positioning and resizing them. If you know a better way to do that without allowing components to be mouse resizable, please put me in the right direction. (I'd prefer to use components instead of drawing shapes into a JPanel because I can use mouse events to move and resize them)Roar
for most complex GUI I plaing with GridBagLaout, but where I put to the first line (horizontal matrix) 50-80 small JLabels, then I never ever must solving AbsolutePositioning, then I put JComponent to the row - column and for height/weight size again with numbers of column & row,Overelaborate
L
3

A layout manager really does 3 things:

  1. 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.

  2. 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.

  3. 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 );
    }

}
  1. 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.

  2. 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.

  3. 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.

Leontine answered 13/10, 2011 at 21:55 Comment(2)
+1: thank you, that's really great! When I have time I will refactor all my code with your ComponentResizer. What about the license of it? Can I use it inside closed source commercial application too?Roar
Check out Drag Layout for the up to date version of this code. The code is available as is and you can use the code however you please.Leontine
W
4

As alternatives, also consider

Winch answered 13/10, 2011 at 13:42 Comment(1)
+1: thank you so much. I think that resizing components and moving windows are exactly what I was looking for!Roar
L
3

A layout manager really does 3 things:

  1. 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.

  2. 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.

  3. 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 );
    }

}
  1. 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.

  2. 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.

  3. 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.

Leontine answered 13/10, 2011 at 21:55 Comment(2)
+1: thank you, that's really great! When I have time I will refactor all my code with your ComponentResizer. What about the license of it? Can I use it inside closed source commercial application too?Roar
Check out Drag Layout for the up to date version of this code. The code is available as is and you can use the code however you please.Leontine
A
1

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

Avian answered 13/10, 2011 at 8:42 Comment(0)
A
0

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?

Avian answered 13/10, 2011 at 7:31 Comment(2)
thanks for the answer but I'm looking something different. Look at the link I posted. I need to absolute positioning components and dynamically changhe their bounds. I already implemented it in way similar to the linked article but I know that using a null layout manager is discouraged. So I was wondering if there is a layout manager that allows such operations or I have to implement it.Roar
Sorry, I made too many assumptions and answered the wrong questions... I've added a new answer.Avian

© 2022 - 2024 — McMap. All rights reserved.