适合可调整大小组件的布局管理器

7

前段时间,我阅读了这篇文章,展示了一种在Swing中实现鼠标可调整大小的组件的方式。

作者使用了空布局管理器,以便允许绝对组件定位。我知道空布局永远不应该使用,因此我的问题是:

是否有任何已经实现的布局管理器允许组件的绝对定位,或者我必须自己实现它?


你是否正在尝试制作可调整大小的鼠标组件?如果是,为什么要这样做? - Andrew Thompson
@Andrew Thompson:我有一个画布,用户应该能够插入不同形状的组件,并对它们进行定位和调整大小。如果您知道一种更好的方法,在不允许组件可通过鼠标调整大小的情况下完成此操作,请指引我正确的方向。(我更喜欢使用组件而不是将形状绘制到JPanel中,因为我可以使用鼠标事件来移动和调整它们) - Heisenbug
对于最复杂的GUI,我使用GridBagLayout进行布局。但是,在第一行(水平矩阵)放置50-80个小JLabel时,我永远不必解决AbsolutePositioning问题。然后,我将JComponent放置在行-列中,并根据列和行的数量再次设置高度/宽度大小。 - mKorbel
4个回答

4

+1:非常感谢。我认为调整组件大小和移动窗口正是我正在寻找的! - Heisenbug

3

一个布局管理器实际上有三个作用:

  1. 设置组件的位置。由于需要拖动组件,因此您不希望布局管理器执行此操作。

  2. 设置组件的大小。由于需要调整组件的大小,因此您不希望这样做。但是,您可能希望根据组件的首选大小为组件提供默认大小。这样,创建组件时无需指定大小。

  3. 根据添加到其中的组件确定父面板的首选大小。这将使滚动窗格正常工作,因为可以根据需要添加/删除滚动条。因此,您需要确定拖动的行为方式。也就是说,是否允许将组件拖动到面板的当前边界之外。如果是,则面板的首选大小应自动增加。

是否有已经实现的LayoutManager允许组件的绝对定位?

我一直在使用一个与您需求接近的布局管理器。它是设计用于与trashgod提供的移动窗口链接中的ComponentMover类一起使用。

这是我为该类编写的测试代码:

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. 对于这个布局,大小始终被认为是首选大小。您需要更改此设置。也许在大小为(0,0)时将大小设置为首选大小。在确定父容器的首选大小时,还需要使用组件的大小(而不是其首选大小)。

  2. ComponentMover类可以配置为允许您拖动组件超出父容器的边界或将组件保持在边界内。如果允许组件移动到边界外,则首选大小会自动调整以考虑组件的新位置。

  3. 如果将组件拖动到顶部或左侧边界之外,则所有组件都将向右或向下移动,以确保没有组件具有负位置。


+1:谢谢,这真的很棒!等我有时间了,我会用你的ComponentResizer重构我的所有代码。那它的许可证呢?我能在闭源商业应用程序中使用它吗? - Heisenbug
请查看拖动布局以获取此代码的最新版本。该代码可按原样使用,您可以随意使用该代码。 - camickr

1

我猜这会取决于你想要它如何运作的具体情况。

使用null布局管理器被不鼓励的主要原因是因为使用该接口构建的界面只能以设计时的大小使用-您无法调整UI的大小。如果这对您来说可以接受,请使用它。

我知道的另一个选项是NetBeans附带的AbsoluteLayout。您可以在此处获取更多信息:http://www.java-tips.org/other-api-tips/netbeans/can-i-distribute-absolutelayout-with-my-applica.html。我认为这可能正是您要寻找的,但是从该链接中可以看出,他们推荐使用Null布局…但我认为无论哪种方法都没有太大区别。

如果您需要允许用户定义组件的调整方式,您最终将构建类似于Netbeans Matisse表单设计器的东西,这可能过度了,并且并不让我感到有趣:)


0
问题有点模糊,所以我可能完全没有抓住重点。我假设您正在寻找一种布局,它将允许您使用绝对定位,但仍将允许您调整组件的大小并使用所有可用空间。
如果您正在手动编码,我已经成功地使用了MIGLayout(http://www.miglayout.com/)和TableLayout(它不太绝对,但非常易于使用 - http://java.sun.com/products/jfc/tsc/articles/tablelayout/
如果您正在使用某个表单设计器,则使用GroupLayout可能是一个不错的选择,但您不想手动编码。请参见此问题: GroupLayout:值得学习吗?

谢谢你的回答,但我正在寻找不同的东西。看一下我发布的链接。我需要绝对定位组件并动态更改它们的边界。我已经以类似于链接文章的方式实现了它,但我知道使用空布局管理器是不被鼓励的。所以我想知道是否有布局管理器允许这样的操作,或者我必须自己实现它。 - Heisenbug
抱歉,我做了太多的假设并回答了错误的问题...我已经添加了一个新的答案。 - Riaan Cornelius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接