【问题标题】:How to use SpringLayout to arrange components vertically如何使用SpringLayout垂直排列组件
【发布时间】:2022-02-03 04:45:04
【问题描述】:

显然我对 SpringLayout 有一些不明白的地方。我正在尝试创建一个类,它是 JPanel 的扩展,它允许我添加组件并让它们垂直显示,宽度相同。

我对 JPanel 的扩展会将其 LayoutManager 设置为使用 SpringLayout,并且每次添加组件时,它都会放入 SpringLayout 约束以将其附加到第一个组件的面板,然后将每个组件附加到前一个组件。

首先,这是一个 Oracle 编写的使用 SpringLayout 的示例,我将其更改为垂直放置组件而不是水平放置:

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import javax.swing.SpringLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import java.awt.Container;

public class SpringDemo3
{
  /**
   * Create the GUI and show it. For thread safety, this method should be
   * invoked from the event-dispatching thread.
   */
  private static void createAndShowGUI()
  {
    // Create and set up the window.
    JFrame frame = new JFrame("SpringDemo3");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // Set up the content pane.
    Container contentPane = frame.getContentPane();
    SpringLayout layout = new SpringLayout();
    contentPane.setLayout(layout);

    // Create and add the components.
    JLabel label = new JLabel("Label: ");
    JTextField textField = new JTextField("Text field", 15);
    contentPane.add(label);
    contentPane.add(textField);

    // Adjust constraints for the label so it's at (5,5).
    layout.putConstraint(SpringLayout.WEST, label, 5, SpringLayout.WEST, contentPane);
    layout.putConstraint(SpringLayout.NORTH, label, 5, SpringLayout.NORTH, contentPane);

    // Adjust constraints for the text field so it's at
    // (<label's right edge> + 5, 5).
//    layout.putConstraint(SpringLayout.WEST, textField, 5, SpringLayout.EAST, label);
//    layout.putConstraint(SpringLayout.EAST, textField, 5, SpringLayout.EAST, contentPane);
    layout.putConstraint(SpringLayout.NORTH, textField, 5, SpringLayout.SOUTH, label);

    // Adjust constraints for the content pane: Its right
    // edge should be 5 pixels beyond the text field's right
    // edge, and its bottom edge should be 5 pixels beyond
    // the bottom edge of the tallest component (which we'll
    // assume is textField).
    layout.putConstraint(SpringLayout.EAST, contentPane, 5, SpringLayout.EAST, textField);
    layout.putConstraint(SpringLayout.SOUTH, contentPane, 5, SpringLayout.SOUTH, textField);

    // Display the window.
    frame.pack();
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    // Schedule a job for the event-dispatching thread:
    // creating and showing this application's GUI.
    javax.swing.SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        createAndShowGUI();
      }
    });
  }
}

根据我对 SpringLayout 要求的理解,我写了以下内容:

import java.awt.Component;
import java.awt.LayoutManager;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.SpringLayout;

import static javax.swing.SpringLayout.NORTH;
import static javax.swing.SpringLayout.EAST;
import static javax.swing.SpringLayout.SOUTH;
import static javax.swing.SpringLayout.WEST;

public class OneWidthPanel extends JPanel
{
  private static final long serialVersionUID = 1L;
  
  private int padding = 5;
  SpringLayout springLayout = new SpringLayout();
  
  public OneWidthPanel() { super(); setLayout(springLayout); }
  public OneWidthPanel(boolean isDoubleBuffered) { super(isDoubleBuffered); }
  public OneWidthPanel(LayoutManager layout) { throw new IllegalArgumentException("Cannot set a layout manager on the OneWidthPanel class"); }
  public OneWidthPanel(LayoutManager l, boolean isDoubleBuffered) { throw new IllegalArgumentException("Cannot set a layout manager on the OneWidthPanel class"); }
  
  private ArrayList<Component> componentList = new ArrayList<>();
  
  @Override
  public Component add(Component comp)
  {
    super.add(comp);
    
    componentList.add(comp);
    int listSize = componentList.size();
    
    String topConstraint;
    Component northComponent;
    if (listSize == 1)
    {
      topConstraint = NORTH;
      northComponent = this;
    }
    else
    {
      topConstraint = SOUTH;
      northComponent = componentList.get(listSize - 2);
    }

    springLayout.putConstraint(topConstraint, northComponent, padding, SpringLayout.NORTH, comp);
    springLayout.putConstraint(WEST, this, padding, WEST, comp);
    springLayout.putConstraint(EAST, this, padding, EAST, comp);
    
    return comp;
  }
  
  public void finishedAdding()
  {
    Component lastComponent = componentList.get(componentList.size()-1);
    springLayout.putConstraint(EAST,   this, padding, EAST,  lastComponent);
    springLayout.putConstraint(SOUTH,  this, padding, SOUTH, lastComponent);
  }
}

这是一个测试它的小程序:

import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JRadioButton;

import rcutil.layout.OneWidthPanel;

public class OneWidthPanelTester extends JFrame
{
  private static final long serialVersionUID = 1L;

  public static void main(String[] args)
  {
    OneWidthPanelTester tester = new OneWidthPanelTester();
    tester.go();
  }
  
  public void go()
  {
    OneWidthPanel panel = new OneWidthPanel();
    JButton button1 = new JButton("ONE new button");
    JButton button2 = new JButton("second b");
    JRadioButton rButton = new JRadioButton("choose me");
    
    panel.add(button1);
    panel.add(button2);
    panel.add(rButton);
    panel.finishedAdding();
    
    add(panel, BorderLayout.WEST);
    pack();
    setVisible(true);
  }

}

组件在面板顶部彼此重叠显示。我想设置每个约束,当我添加它时,将每个组件的北端连接到前一个组件的南边缘,将它们按照添加的顺序垂直排列。我有finishedAdding() 方法,这样我就可以包装最后一个组件与其容器的连接,如https://docs.oracle.com/javase/tutorial/uiswing/layout/spring.html 的“如何使用SpringLayout”教程中所述,并在我复制的演示程序中完成。

我不明白为什么我的组件相互重叠,但(两个)演示组件垂直相邻。我是否能够满足我最初的愿望,即让垂直组件在面板中拉伸成相同的大小?

【问题讨论】:

    标签: java swing layout-manager springlayout


    【解决方案1】:

    你需要改变

    springLayout.putConstraint(topConstraint, comp, padding, SpringLayout.NORTH, northComponent);
    

    springLayout.putConstraint(NORTH, comp, padding, topConstraint, northComponent);
    

    【讨论】:

    • 这解决了垂直堆叠问题,但所有组件都是它们的首选宽度;我希望它们被拉伸到面板的宽度。如果您对此有任何建议,我将不胜感激。
    • 您可以使用 SpringUtilities.java 中的 makeCompactGrid 方法(来自教程)并通过在您的 finishedAdding 方法中添加以下行来设置相同的宽度:SpringUtilities.makeCompactGrid(this, 3, 1, padding, padding, padding, padding);
    【解决方案2】:

    正如 Hitesh 所指出的,您在 add 方法中将参数交换为 SpringLayout.putConstraints。 The documentation 状态:

     public void putConstraint(String e1,
                               Component c1,
                               int pad,
                               String e2,
                               Component c2)
    

    参数:
    e1 - 依赖的边缘
    c1 - 依赖的组件
    pad - 依赖和锚点之间的固定距离
    e2 - 锚的边缘
    c2 - 锚的组件

    前两个参数是“从属”,即您要定位的组件。最后两个参数是“anchor”——即被定位的组件所依赖的另一个组件(或容器)。

    但这不是问题的全部。要使所有组件具有相同的宽度,您需要使用 Spring 聚合它们的所有高度,该 Spring.max 是使用 Spring.max 构建的。

    Spring based on a component 是一个“活动”对象:确切的最小值、首选值和最大值在相应组件中发生变化时都会发生变化。因此,所有这些基于组件的 Spring 对象的最大值将是您要应用于所有这些对象的宽度。

    首先将组件的宽度保留在私有字段中:

    private Spring width = Spring.constant(0);
    

    然后,在您的add 方法中,您只需调用一次putConstraints。暂时不要附加组件的水平尺寸:

    springLayout.putConstraint(NORTH, comp, padding, topConstraint, northComponent );
    width = Spring.max(width, Spring.width(comp));
    

    最后,在finishedAdding 中,使用那个“智能”Spring 作为容器的宽度和每个组件的宽度。

    public void finishedAdding()
    {
      Component lastComponent = componentList.get(componentList.size()-1);
      springLayout.putConstraint(EAST,   this, width,   WEST,  this);
      springLayout.putConstraint(SOUTH,  this, padding, SOUTH, lastComponent);
    
      // Make every component's width fill this container.
      for (Component comp : componentList) {
        springLayout.putConstraint(WEST, comp, 0, WEST, this);
        springLayout.putConstraint(EAST, comp, width, WEST, this);
      }
    }
    

    首先,我们将容器的宽度绑定到所有组件的最大宽度(并将其高度绑定到最后一个组件)。然后,为了确保每个组件都拉伸以填充容器的宽度,我们将其东侧和西侧都附加到容器上。

    对于它的价值,你实际上并不需要 SpringLayout。 Box 可以做你想做的事,不需要专门的课程:

    JButton button1 = new JButton("ONE new button");
    JButton button2 = new JButton("second b");
    JRadioButton rButton = new JRadioButton("choose me");
    
    int padding = 5;
    
    Box panel = Box.createVerticalBox();
    panel.add(button1);
    panel.add(Box.createVerticalStrut(padding));
    panel.add(button2);
    panel.add(Box.createVerticalStrut(padding));
    panel.add(rButton);
    
    for (Component comp : panel.getComponents()) {
        Dimension size = comp.getMaximumSize();
        size.width = Integer.MAX_VALUE;
        comp.setMaximumSize(size);
    }
    

    【讨论】:

    • 非常完整;比我接受的更好的答案。我对更改接受的标志感到很痛苦,Hitesh 确实回答了我的问题,虽然不是那么完整,而且更早。无论如何,你有我的感谢和支持。
    【解决方案3】:

    这是我最终想到的:

    import static javax.swing.SpringLayout.EAST;
    import static javax.swing.SpringLayout.NORTH;
    import static javax.swing.SpringLayout.SOUTH;
    import static javax.swing.SpringLayout.WEST;
    
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.LayoutManager;
    import java.util.ArrayList;
    
    import javax.swing.BoxLayout;
    import javax.swing.JPanel;
    import javax.swing.SpringLayout;
    
    public class OneWidthPanel extends JPanel
    {
      private static final long serialVersionUID = 1L;
      
      private int padding = 5;
      SpringLayout springLayout = new SpringLayout();
      BoxLayout boxLayout = new BoxLayout(this, BoxLayout.PAGE_AXIS);
      
      public OneWidthPanel() 
      { 
        super();
        setLayout(springLayout);
      }
      public OneWidthPanel(boolean isDoubleBuffered) { super(isDoubleBuffered); }
      public OneWidthPanel(LayoutManager layout) { throw new IllegalArgumentException("Cannot set a layout manager on the OneWidthPanel class"); }
      public OneWidthPanel(LayoutManager l, boolean isDoubleBuffered) { throw new IllegalArgumentException("Cannot set a layout manager on the OneWidthPanel class"); }
      
      private ArrayList<Component> componentList = new ArrayList<>();
      
      @Override
      public Component add(Component comp)
      {
        super.add(comp);
        
        componentList.add(comp);
        int listSize = componentList.size();
    
        String topConstraint;
        Component northComponent;
        if (listSize == 1)
        {
          topConstraint = NORTH;
          northComponent = this;
        }
        else
        {
          topConstraint = SOUTH;
          northComponent = componentList.get(listSize - 2);
        }
    
        springLayout.putConstraint(NORTH, comp, padding, topConstraint, northComponent);
        springLayout.putConstraint(WEST, comp, padding, WEST, this);
    //    springLayout.putConstraint(EAST, comp, padding, EAST, this);
        
        return comp;
      }
      
      public void finishedAdding()
      {
        Component lastComponent = componentList.get(componentList.size()-1);
    //    springLayout.putConstraint(EAST,   this, padding, EAST,  lastComponent);
        springLayout.putConstraint(SOUTH,  this, padding, SOUTH, lastComponent);
        springLayout.putConstraint(WEST,   this, padding, WEST,  lastComponent);
    
        int maxComponentWidth = 0;
        int panelHeight = padding;
        
        // calculate overall height and maximum width
        for (Component component: componentList)
        {
          Dimension componentDimension = component.getPreferredSize();
          maxComponentWidth = Math.max(maxComponentWidth, componentDimension.width);
          panelHeight = panelHeight + componentDimension.height + padding;
        }
        
        // for each component, set the component width and preferred height,
        //    and set maximum width to MAX_VALUE; I was trying to get the components
        //    to stretch, but that doesn't work.
        for (Component component: componentList)
        {
          Dimension componentDimension = component.getPreferredSize();
          Dimension newD = new Dimension(maxComponentWidth, componentDimension.height);
    //      Dimension maxD = new Dimension(Integer.MAX_VALUE, componentDimension.height);
          component.setPreferredSize(newD);
          component.setMinimumSize(newD);
          component.setMaximumSize(newD);
        }
        
        // set panel dimensions
        Dimension newD = new Dimension(maxComponentWidth + (padding*2), panelHeight);
        this.setPreferredSize(newD);
        this.setMinimumSize(newD);
      }
    }
    

    以及当前的测试人员:

    import java.awt.BorderLayout;
    import java.awt.Color;
    
    import javax.swing.BorderFactory;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JRadioButton;
    import javax.swing.JTextField;
    import javax.swing.SwingUtilities;
    
    import rcutil.layout.OneWidthPanel;
    
    public class OneWidthPanelTester extends JFrame implements Runnable
    {
      private static final long serialVersionUID = 1L;
    
      public static void main(String[] args)
      {
        OneWidthPanelTester tester = new OneWidthPanelTester();
        SwingUtilities.invokeLater(tester);
      }
      
      public void run()
      {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        JButton button1 = new JButton("ONE new button");
        JButton button2 = new JButton("second b");
        JRadioButton rButton = new JRadioButton("choose me");
        JLabel label = new JLabel("I'm last");
        label.setBorder(BorderFactory.createLineBorder(Color.black));
        JTextField textField = new JTextField(25);
        
        OneWidthPanel panel = new OneWidthPanel();
        panel.add(button1);
        panel.add(button2);
        panel.add(rButton);
        panel.add(label);
        panel.add(textField);
        panel.finishedAdding();
        
        add(panel, BorderLayout.CENTER);
        pack();
        setVisible(true);
      }
    
    }
    

    这些组件以添加到面板的最宽组件的宽度(按首选大小)显示。按钮不会随面板拉伸,但文本字段会 - 我曾认为设置按钮的最大大小并添加和 EAST 方向约束会拉伸它们,但似乎没有,而且我不需要这就是我现在正在做的事情。

    【讨论】:

    • 请在下面的答案中参考我的评论。使用makeCompactGrid,您也可以拉伸按钮。
    猜你喜欢
    • 1970-01-01
    • 2020-11-07
    • 2021-03-29
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 2018-05-04
    • 1970-01-01
    • 2018-04-21
    相关资源
    最近更新 更多