JTabbedPane中固定位置或一行中的选项卡

在我们的应用程序中,JTabbedPane具有无限制的选项卡,当选项卡的宽度超过选项卡式窗格的宽度时,选项卡将开始包装成多行。 然后,当您单击其中一个上行中的选项卡时,整行将向下并到达前景。 对于在多个选项卡之间进行点击的用户,由于无法跟踪选项卡顺序,因此非常容易混淆。

我怎么能 – 将标签钉在固定位置,同时将它们的内容放到前面(虽然这会损坏标签隐喻,但我不在乎),或者 – 将行数限制为一个(因此标签变得非常好)狭窄而不是包装)?

非常快速和肮脏(肯定需要改进和更改),但我想这样的东西可能适合你(但不是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 WrapLayout with a left alignment and a * default 5-unit horizontal and vertical gap. */ public WrapLayout() { super(); } /** * Constructs a new FlowLayout with the specified alignment * and a default 5-unit horizontal and vertical gap. The value of the * alignment argument must be one of WrapLayout, * WrapLayout, or WrapLayout. * * @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. * 

* The value of the alignment argument must be one of * WrapLayout, WrapLayout, or * WrapLayout. * * @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 * visible 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 visible * 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 tabs = new ArrayList(); 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上的另一篇文章。

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

首先,找出您正在使用的UI类。 使用UIManager.setLookAndFeel()初始化L&F后,输入以下代码:

 for (Map.Entry 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无法识别该类,并且如果键入该类的全名,则会给出“访问限制”错误,请参阅以下问题: 访问限制:“应用程序”类型不是API(对所需库rt.jar的限制) )

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

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

但是,有一个问题:一些L&F没有考虑标签行的非滚动,结果很难看。

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