前段时间我读了这篇文章,它展示了一种在 Swing 中实现鼠标可调整大小组件的方法。
作者使用空的 LayoutManager 以允许绝对组件定位。我知道永远不应该使用空布局,所以我的问题是:
是否有任何已经实现的 LayoutManager 允许组件的绝对定位,或者我必须自己实现它?
前段时间我读了这篇文章,它展示了一种在 Swing 中实现鼠标可调整大小组件的方法。
作者使用空的 LayoutManager 以允许绝对组件定位。我知道永远不应该使用空布局,所以我的问题是:
是否有任何已经实现的 LayoutManager 允许组件的绝对定位,或者我必须自己实现它?
布局管理器确实做了 3 件事:
设置组件的位置。由于您需要能够拖动组件,因此您不希望您的布局管理器执行此操作。
设置组件的大小。由于您需要调整组件大小的能力,因此您不想这样做。但是,您可能希望根据组件的首选大小为组件指定默认大小。这样您就不需要在创建组件时指定大小。
根据添加到其中的组件确定父面板的首选大小。这将允许滚动窗格正常运行,因为可以根据需要添加/删除滚动条。因此,您需要确定拖动应该如何工作的行为。也就是说,您是否允许将组件拖动到面板的当前边界之外。如果是这样,面板的首选尺寸应该会自动增加。
是否有任何已经实现的 LayoutManager 允许组件的绝对定位
我一直在玩一个接近您需求的布局管理器。它被设计为与垃圾神提供的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 );
}
}
对于此布局,尺寸始终假定为首选尺寸。你需要改变这个。当大小为 (0, 0) 时,可能将大小设置为首选大小。在确定父容器的首选大小时,您还需要使用组件的大小(而不是其首选大小)。
ComponentMover 类可以配置为允许您将组件拖动到父容器的边界之外或将组件保持在边界内。如果您允许将组件移动到边界之外,则会自动调整首选大小以考虑组件的新位置。
如果将组件拖动到顶部或左侧边界之外,则所有组件都会移动(向右或向下),请确保没有组件具有负位置。
我想这将取决于你希望它如何表现的细节。
不鼓励使用空布局管理器的主要原因是因为使用它构建的界面只能以它们设计的大小使用 - 你不能调整 UI 的大小。如果这对您来说很好,请使用它。
我知道的另一个选项是与 Netbeans 一起分发的 AbsoluteLayout。您可以在这里获得更多信息: 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:值得学习吗?