【问题标题】:Tabs at fixed positions in JTabbedPane or in one rowJTabbedPane 或一行中固定位置的选项卡
【发布时间】:2012-03-09 15:27:54
【问题描述】:

In our application that has a JTabbedPane with unlimited tabs, when the width of the tabs exceeds the tabbed pane's width, the tabs start wrapping into several rows.然后,当您单击上面一行中的一个选项卡时,整个行会下降到前台。对于在多个选项卡之间单击的用户来说,这是非常令人困惑的,因为无法跟踪选项卡顺序。

我该怎么做 - 将标签固定到固定位置,同时将其内容放在前面(尽管这会在视觉上破坏标签隐喻,但我不在乎),或者 - 将行数限制为一(这样标签会变得很窄而不是环绕)?

【问题讨论】:

  • 换成另一种控件怎么样?像列表还是树?
  • setTabLayoutPolicy()可能就是你要找的东西
  • @Guillaume,setTabLayoutPolicy() 只有两个值:scroll 或 wrap,这里都不好。
  • @Randy,这里似乎不可能
  • @Guillaume,SCROLL 政策实际上我也在考虑,但我想知道是否有更好的选择。

标签: java swing tabs jtabbedpane


【解决方案1】:

经过几个小时的研究,我终于找到了一个干净的解决方案。

首先,确定您使用的是哪个 UI 类。在使用UIManager.setLookAndFeel() 初始化 L&F 后输入以下代码:

for (Map.Entry<Object, Object> entry : UIManager.getDefaults().entrySet()) {
    boolean isStringKey = entry.getKey().getClass() == String.class ;
    String key = isStringKey ? ((String) entry.getKey()):"";   
    if (key.equals("TabbedPaneUI")) {
        System.out.println(entry.getValue());
    }
}

在我的例子中,它打印com.sun.java.swing.plaf.windows.WindowsTabbedPaneUI。如果您使用不同的 L&F,这可能是另一个类(或者即使您使用的是其他操作系统,如果您采用操作系统默认值)。

接下来,只需实例化该类(它扩展了 BasicTabbedPaneUI),并覆盖有问题的方法:

WindowsTabbedPaneUI jtpui = new WindowsTabbedPaneUI() {
    @Override protected boolean shouldRotateTabRuns(int i) {
        return false;
    }
};

如果 eclipse 无法识别该类,并且在您键入该类的全名时会出现“访问限制”错误,请参阅此问题:Access restriction: The type 'Application' is not API (restriction on required library rt.jar)

最后,只需为您的 JTabbedPane 设置该 UI:

JTabbedPane jtp = new JTabbedPane();
jtp.setUI(jtpui);

但是有一个问题:有些L&F没有考虑到tabs行的不滚动,结果很丑。

要解决这个问题(我只在 Windows L&F 上测试过),请在初始化 L&F 后立即添加以下内容: UIManager.getDefaults().put("TabbedPane.tabRunOverlay", 0);

【讨论】:

    【解决方案2】:

    真的又快又脏(肯定需要改进和改变),但我想这样的东西可能对你有用(但不是 JTabbePane):

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.FlowLayout;
    import java.awt.Insets;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        /**
         * FlowLayout subclass that fully supports wrapping of components.
         */
        public static class WrapLayout extends FlowLayout {
            private Dimension preferredLayoutSize;
    
            /**
             * Constructs a new <code>WrapLayout</code> with a left alignment and a
             * default 5-unit horizontal and vertical gap.
             */
            public WrapLayout() {
                super();
            }
    
            /**
             * Constructs a new <code>FlowLayout</code> with the specified alignment
             * and a default 5-unit horizontal and vertical gap. The value of the
             * alignment argument must be one of <code>WrapLayout</code>,
             * <code>WrapLayout</code>, or <code>WrapLayout</code>.
             * 
             * @param align
             *            the alignment value
             */
            public WrapLayout(int align) {
                super(align);
            }
    
            /**
             * Creates a new flow layout manager with the indicated alignment and
             * the indicated horizontal and vertical gaps.
             * <p>
             * The value of the alignment argument must be one of
             * <code>WrapLayout</code>, <code>WrapLayout</code>, or
             * <code>WrapLayout</code>.
             * 
             * @param align
             *            the alignment value
             * @param hgap
             *            the horizontal gap between components
             * @param vgap
             *            the vertical gap between components
             */
            public WrapLayout(int align, int hgap, int vgap) {
                super(align, hgap, vgap);
            }
    
            /**
             * Returns the preferred dimensions for this layout given the
             * <i>visible</i> components in the specified target container.
             * 
             * @param target
             *            the component which needs to be laid out
             * @return the preferred dimensions to lay out the subcomponents of the
             *         specified container
             */
            @Override
            public Dimension preferredLayoutSize(Container target) {
                return layoutSize(target, true);
            }
    
            /**
             * Returns the minimum dimensions needed to layout the <i>visible</i>
             * components contained in the specified target container.
             * 
             * @param target
             *            the component which needs to be laid out
             * @return the minimum dimensions to lay out the subcomponents of the
             *         specified container
             */
            @Override
            public Dimension minimumLayoutSize(Container target) {
                Dimension minimum = layoutSize(target, false);
                minimum.width -= getHgap() + 1;
                return minimum;
            }
    
            /**
             * Returns the minimum or preferred dimension needed to layout the
             * target container.
             * 
             * @param target
             *            target to get layout size for
             * @param preferred
             *            should preferred size be calculated
             * @return the dimension to layout the target container
             */
            private Dimension layoutSize(Container target, boolean preferred) {
                synchronized (target.getTreeLock()) {
                    // Each row must fit with the width allocated to the containter.
                    // When the container width = 0, the preferred width of the
                    // container
                    // has not yet been calculated so lets ask for the maximum.
    
                    int targetWidth = target.getSize().width;
    
                    if (targetWidth == 0) {
                        targetWidth = Integer.MAX_VALUE;
                    }
    
                    int hgap = getHgap();
                    int vgap = getVgap();
                    Insets insets = target.getInsets();
                    int horizontalInsetsAndGap = insets.left + insets.right + hgap * 2;
                    int maxWidth = targetWidth - horizontalInsetsAndGap;
    
                    // Fit components into the allowed width
    
                    Dimension dim = new Dimension(0, 0);
                    int rowWidth = 0;
                    int rowHeight = 0;
    
                    int nmembers = target.getComponentCount();
    
                    for (int i = 0; i < nmembers; i++) {
                        Component m = target.getComponent(i);
    
                        if (m.isVisible()) {
                            Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
    
                            // Can't add the component to current row. Start a new
                            // row.
    
                            if (rowWidth + d.width > maxWidth) {
                                addRow(dim, rowWidth, rowHeight);
                                rowWidth = 0;
                                rowHeight = 0;
                            }
    
                            // Add a horizontal gap for all components after the
                            // first
    
                            if (rowWidth != 0) {
                                rowWidth += hgap;
                            }
    
                            rowWidth += d.width;
                            rowHeight = Math.max(rowHeight, d.height);
                        }
                    }
    
                    addRow(dim, rowWidth, rowHeight);
    
                    dim.width += horizontalInsetsAndGap;
                    dim.height += insets.top + insets.bottom + vgap * 2;
    
                    // When using a scroll pane or the DecoratedLookAndFeel we need
                    // to
                    // make sure the preferred size is less than the size of the
                    // target containter so shrinking the container size works
                    // correctly. Removing the horizontal gap is an easy way to do
                    // this.
    
                    Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
    
                    if (scrollPane != null) {
                        dim.width -= hgap + 1;
                    }
    
                    return dim;
                }
            }
    
            /*
             *  A new row has been completed. Use the dimensions of this row
             *  to update the preferred size for the container.
             *
             *  @param dim update the width and height when appropriate
             *  @param rowWidth the width of the row to add
             *  @param rowHeight the height of the row to add
             */
            private void addRow(Dimension dim, int rowWidth, int rowHeight) {
                dim.width = Math.max(dim.width, rowWidth);
    
                if (dim.height > 0) {
                    dim.height += getVgap();
                }
    
                dim.height += rowHeight;
            }
        }
    
        public static class MyTabbedPane extends JPanel {
            private JPanel buttonPanel;
            private JPanel currentview;
    
            private Tab currentTab;
    
            private class Tab {
                String name;
                JComponent component;
            }
    
            private List<Tab> tabs = new ArrayList<Tab>();
    
            public MyTabbedPane() {
                super(new BorderLayout());
                buttonPanel = new JPanel(new WrapLayout());
                currentview = new JPanel();
                add(buttonPanel, BorderLayout.NORTH);
                add(currentview);
            }
    
            public void addTab(String name, JComponent tabView, int index) {
                if (index < 0 || index > tabs.size()) {
                    throw new IllegalArgumentException("Index out of bounds");
                }
                final Tab tab = new Tab();
                tab.component = tabView;
                tab.name = name;
                tabs.add(index, tab);
                JButton b = new JButton(name);
                b.addActionListener(new ActionListener() {
    
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        setCurrentTab(tab);
                    }
    
                });
                buttonPanel.add(b, index);
                buttonPanel.validate();
            }
    
            public void removeTab(int i) {
                Tab tab = tabs.remove(i);
    
                if (tab == currentTab) {
                    if (tabs.size() > 0) {
                        if (i < tabs.size()) {
                            setCurrentTab(tabs.get(i));
                        } else {
                            setCurrentTab(tabs.get(i - 1));
                        }
                    } else {
                        setCurrentTab(null);
                    }
                }
                buttonPanel.remove(index);
            }
    
            void setCurrentTab(final Tab tab) {
                if (currentTab == tab) {
                    return;
                }
                if (currentTab != null) {
                    currentview.remove(currentTab.component);
                }
                if (tab != null) {
                    currentview.add(tab.component);
                }
                currentTab = tab;
                currentview.validate();
            }
        }
    
        public static void main(String[] args) {
            JFrame frame = new JFrame();
            MyTabbedPane tabbedPane = new MyTabbedPane();
            for (int i = 0; i < 100; i++) {
                tabbedPane.addTab("Button " + (i + 1), new JLabel("Dummy Label " + (i + 1)), i);
            }
            frame.add(tabbedPane);
            frame.pack();
            frame.setSize(new Dimension(1000, 800));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    
    }
    

    WrapLayout 取自 SO 上的另一篇文章。

    【讨论】:

    • 好答案,假设没有简单的方法来修改 JTabbedPane。谢谢!
    • 如果你真的想要,你可以扩展 JTabbedPane 并覆盖 JTabbedPane 中的每个方法(没有那么多)并将它们映射到上面的那些。
    猜你喜欢
    • 1970-01-01
    • 2014-11-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-24
    • 2011-12-13
    • 2014-12-06
    • 2017-03-09
    • 1970-01-01
    相关资源
    最近更新 更多