适合LayoutManager的可调整大小的组件



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

作者使用空的LayoutManager以便允许绝对组件定位。我知道不应该使用null布局,所以我的问题是:

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

作为备选方案,还可以考虑

  • 如何使用内部框架.

  • 调整组件大小移动窗口

  • 已存在的框架,如JGraph或JUNG。

布局管理器实际上做三件事:

  1. 设置组件的位置。由于您需要能够拖动组件,因此您不会希望布局管理器这样做。

  2. 设置组件的大小。因为您需要能够调整组件的大小,所以您不会希望这样做。但是,您可能希望根据组件的首选大小为组件提供默认大小。这样你就不需要在创建组件时指定它的大小了。

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

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

我一直在玩一个布局管理器,是接近你的需要。它被设计为与来自trashgod提供的Moving Windows链接的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. 如果你把一个组件拖到顶部或左侧边界之外,那么所有的组件都被移动(向右或向下),确保没有组件有一个负的位置。

我想这取决于你想让它表现的具体方式。

不鼓励使用null布局管理器的主要原因是,使用null布局管理器构建的界面只能以设计时的大小使用——你不能调整UI的大小。如果这对你来说没问题,那就用吧。

我知道的另一个选项是Netbeans分发的绝对布局。你可以在这里获得更多信息:http://www.java-tips.org/other-api-tips/netbeans/can-i-distribute-absolutelayout-with-my-applica.html。我认为这可能正是你正在寻找的,但正如你可以从那个链接中看到的,他们建议使用Null布局…我认为这两种方式都没有太大的区别。

如果您需要允许用户定义组件将如何调整大小,那么您最终将构建类似Netbeans Matisse表单设计器的东西,这可能是多余的,并且不会让我感到那么有趣:)

这个问题有点模糊,所以我可能完全没有抓住要点。我假设您正在寻找一种布局,它将允许您使用绝对定位,但仍然允许您调整组件的大小并使用所有可用空间。

如果你是手工编码,我已经成功使用MIGLayout (http://www.miglayout.com/)和TableLayout(这是不那么绝对,但非常容易使用- http://java.sun.com/products/jfc/tsc/articles/tablelayout/)

如果您正在使用一些表单设计器,使用GroupLayout可能是一个不错的选择,但您不想手工编写它。看看这个问题:GroupLayout:值得学习吗?

最新更新