JTabbedPane:选项卡位置设置为LEFT,但图标未对齐。

3

我有一个JTabbedPane,选项卡放置位置设置为左侧。问题在于每个选项卡中的图标没有垂直对齐。

考虑这张图片:

enter image description here

如您所见,“Editar Protocolo”(第二个选项卡)的图标与“Distribuir Protocolo”(第一个选项卡)的图标不完全对齐,其他选项卡也存在这种情况。我希望所有图标都垂直对齐到左侧。

这是我用于设置选项卡组件的代码:

...
jtabbedPane.setTabComponentAt(1, configurarJtabbedPane("Editar Protocolo", iconEditarProtocolo));
...

public JLabel configurarJtabbedPane(String title, ImageIcon icon) {
    JLabel l = new JLabel(title);
    l.setIcon(icon);
    l.setIconTextGap(5);
    l.setHorizontalTextPosition(SwingConstants.RIGHT);
    return l;
}

这段代码摘自以下问答:JTabbedPane:选项卡左侧的图标


  1. 为了更快地获得更好的帮助,请发布一个MCVE(Minimal Complete Verifiable Example)(最小完整可验证示例)。
  2. 获取示例图像的一种方法是热链接到在此Q&A中看到的图像。
- Andrew Thompson
这段代码为什么不是 MCVE?除此之外,你还想看到哪些内容?JFrame frame = new JFrame(); - user2582318
2
它是M而不是C、V或E。点击链接,阅读有关MCVE的内容。 - Andrew Thompson
我不确定我理解这个问题。这似乎是默认行为。请参阅Swing教程中关于如何使用TabbedPanes的部分,以获取更多信息和示例。 - camickr
我指的是完全靠左对齐,不像JLabel文本大小那样依赖于它。看看“编辑协议”图标不是精确地在“分发协议”下面吗? - user2582318
我认为其他人误解了你的问题。虽然问题的质量可以改进,但我认为它不必关闭,因为这是一个非常有趣的话题。 - dic19
5个回答

13
我想要的是:将所有图标都放置在左侧,不基于文本大小 [...] 在典型的实现中,选项卡的内容居中对齐 很有意义,因为需要适应该内容的区域直到选项卡被有效呈现。由于该区域取决于内容,而不同的选项卡可能具有不同的标题长度,因此必须制定如何呈现这些选项卡的策略。标准是使选项卡内容居中,并将选项卡区域适应此内容。当我们使用默认选项卡面板并将选项卡放置在顶部时,我们不太关心图标/文本对齐方式:唯一需要考虑的是选项卡具有不同的长度,但谁关心呢?毕竟,图标和文本是可见的,选项卡面板看起来足够好。但是,当您将选项卡放置在LEFTRIGHT时,情况就不同了,它看起来不吸引人。显然,这种默认行为是长期存在的问题,有一个非常有趣的讨论here。其中涉及了一些SO成员:@camickr、@kleopatra和@splungebob。如该帖子中所讨论的,没有简单的解决方案,并提出了几种解决方法:基本上是自定义UI实现或使用面板作为渲染器并根据文本长度玩弄首选宽度/高度。这两种选择都需要相当多的工作。为了避免处理UI代理并利用setTabComponentAt(...)方法,我前一段时间开始了一个选项卡扩展,我想在此分享。该方法基于Swing概念的渲染器:一个类必须生成一个组件以呈现另一个组件的部分,并且其目标是提供一种灵活的机制来添加自定义选项卡组件。下面包括一个使用我的自定义选项卡面板的示例,以及提供上述机制所需的所有接口/类概述。 ITabRenderer接口 第一步是定义一个接口,提供呈现选项卡组件的契约。 AbstractTabRenderer类 提供基本方法来帮助实现getTabRendererComponent(...)方法的抽象类。此抽象类具有三个主要属性: prototypeText:用于定义生成默认渲染器组件的原型文本。 prototypeIcon:用于定义原型图标以生成默认渲染器。 horizontalTextAlignment:选项卡文本的水平对齐方式。 请注意,此类是抽象的,因为它不实现getTabRendererComponent(...)方法。 DefaultTabRenderer类

通过扩展 AbstractTabRenderer 类来实现具体的实现。请注意,如果您想在教程演示中包含关闭按钮,则在此类中进行一些工作就足够了。事实上,我已经做了,但我不会将其包含在内,以免扩展这篇(已经很长的)文章。

JXTabbedPane

最后,标签页的扩展包括标签渲染器支持并覆盖了 addTab(...) 方法。

示例

我已经使用以下 PLAF 运行了这个示例,并取得了积极的结果:

  • WindowsLookAndFeel
  • WindowsClassicLookAndFeel
  • NimbusLookAndFeel
  • MetalLookAndFeel
  • SeaglassLookAndFeel

此外,如果您将标签放置从 LEFT 切换到 TOP(默认)或 BOTTOM,则所有标签仍然具有相同的宽度,解决了本答案第二段描述的问题。

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class Demo {

    private void createAndShowGUI() {

        JXTabbedPane tabbedPane = new JXTabbedPane(JTabbedPane.LEFT);
        AbstractTabRenderer renderer = (AbstractTabRenderer)tabbedPane.getTabRenderer();
        renderer.setPrototypeText("This text is a prototype");
        renderer.setHorizontalTextAlignment(SwingConstants.LEADING);

        tabbedPane.addTab("Short", UIManager.getIcon("OptionPane.informationIcon"), createEmptyPanel(), "Information tool tip");
        tabbedPane.addTab("Long text", UIManager.getIcon("OptionPane.warningIcon"), createEmptyPanel(), "Warning tool tip");
        tabbedPane.addTab("This is a really long text", UIManager.getIcon("OptionPane.errorIcon"), createEmptyPanel(), "Error tool tip");

        JFrame frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(tabbedPane);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

    }

    private JPanel createEmptyPanel() {
        JPanel dummyPanel = new JPanel() {

            @Override
            public Dimension getPreferredSize() {
                return isPreferredSizeSet() ?
                            super.getPreferredSize() : new Dimension(400, 300);
            }

        };
        return dummyPanel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Demo().createAndShowGUI();
            }
        });
    }

    class JXTabbedPane extends JTabbedPane {

        private ITabRenderer tabRenderer = new DefaultTabRenderer();

        public JXTabbedPane() {
            super();
        }

        public JXTabbedPane(int tabPlacement) {
            super(tabPlacement);
        }

        public JXTabbedPane(int tabPlacement, int tabLayoutPolicy) {
            super(tabPlacement, tabLayoutPolicy);
        }

        public ITabRenderer getTabRenderer() {
            return tabRenderer;
        }

        public void setTabRenderer(ITabRenderer tabRenderer) {
            this.tabRenderer = tabRenderer;
        }

        @Override
        public void addTab(String title, Component component) {
            this.addTab(title, null, component, null);
        }

        @Override
        public void addTab(String title, Icon icon, Component component) {
            this.addTab(title, icon, component, null);
        }

        @Override
        public void addTab(String title, Icon icon, Component component, String tip) {
            super.addTab(title, icon, component, tip);
            int tabIndex = getTabCount() - 1;
            Component tab = tabRenderer.getTabRendererComponent(this, title, icon, tabIndex);
            super.setTabComponentAt(tabIndex, tab);
        }
    }

    interface ITabRenderer {

        public Component getTabRendererComponent(JTabbedPane tabbedPane, String text, Icon icon, int tabIndex);

    }

    abstract class AbstractTabRenderer implements ITabRenderer {

        private String prototypeText = "";
        private Icon prototypeIcon = UIManager.getIcon("OptionPane.informationIcon");
        private int horizontalTextAlignment = SwingConstants.CENTER;
        private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

        public AbstractTabRenderer() {
            super();
        }

        public void setPrototypeText(String text) {
            String oldText = this.prototypeText;
            this.prototypeText = text;
            firePropertyChange("prototypeText", oldText, text);
        }

        public String getPrototypeText() {
            return prototypeText;
        }

        public Icon getPrototypeIcon() {
            return prototypeIcon;
        }

        public void setPrototypeIcon(Icon icon) {
            Icon oldIcon = this.prototypeIcon;
            this.prototypeIcon = icon;
            firePropertyChange("prototypeIcon", oldIcon, icon);
        }

        public int getHorizontalTextAlignment() {
            return horizontalTextAlignment;
        }

        public void setHorizontalTextAlignment(int horizontalTextAlignment) {
            this.horizontalTextAlignment = horizontalTextAlignment;
        }

        public PropertyChangeListener[] getPropertyChangeListeners() {
            return propertyChangeSupport.getPropertyChangeListeners();
        }

        public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
            return propertyChangeSupport.getPropertyChangeListeners(propertyName);
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            propertyChangeSupport.addPropertyChangeListener(listener);
        }

        public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
            propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
        }

        protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
            PropertyChangeListener[] listeners = getPropertyChangeListeners();
            for (int i = listeners.length - 1; i >= 0; i--) {
                listeners[i].propertyChange(new PropertyChangeEvent(this, propertyName, oldValue, newValue));
            }
        }
    }

    class DefaultTabRenderer extends AbstractTabRenderer implements PropertyChangeListener {

        private Component prototypeComponent;

        public DefaultTabRenderer() {
            super();
            prototypeComponent = generateRendererComponent(getPrototypeText(), getPrototypeIcon(), getHorizontalTextAlignment());
            addPropertyChangeListener(this);
        }

        private Component generateRendererComponent(String text, Icon icon, int horizontalTabTextAlignmen) {
            JPanel rendererComponent = new JPanel(new GridBagLayout());
            rendererComponent.setOpaque(false);

            GridBagConstraints c = new GridBagConstraints();
            c.insets = new Insets(2, 4, 2, 4);
            c.fill = GridBagConstraints.HORIZONTAL;
            rendererComponent.add(new JLabel(icon), c);

            c.gridx = 1;
            c.weightx = 1;
            rendererComponent.add(new JLabel(text, horizontalTabTextAlignmen), c);

            return rendererComponent;
        }

        @Override
        public Component getTabRendererComponent(JTabbedPane tabbedPane, String text, Icon icon, int tabIndex) {
            Component rendererComponent = generateRendererComponent(text, icon, getHorizontalTextAlignment());
            int prototypeWidth = prototypeComponent.getPreferredSize().width;
            int prototypeHeight = prototypeComponent.getPreferredSize().height;
            rendererComponent.setPreferredSize(new Dimension(prototypeWidth, prototypeHeight));
            return rendererComponent;
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            String propertyName = evt.getPropertyName();
            if ("prototypeText".equals(propertyName) || "prototypeIcon".equals(propertyName)) {
                this.prototypeComponent = generateRendererComponent(getPrototypeText(), getPrototypeIcon(), getHorizontalTextAlignment());
            }
        }
    }
}

截图

MetalLookAndFeel:

图片描述

NimbusLookAndFeel:

图片描述

SeaglassLookAndFeel:

图片描述

WindowsLookAndFeel:

图片描述


非常好,我会尝试的,非常棒的人,谢谢你的一切 :)! - user2582318
1
非常全面和文档化;提前为我的笨拙编辑手感到抱歉。 - trashgod
相反,非常感谢您抽出时间阅读并改进我的回答 :) @trashgod - dic19

2
有一个更简单的解决方案,使用HTML格式化:
final String PRE_HTML = "<html><p style=\"text-align: left; width: 230px\">"; 
final String POST_HTML = "</p></html>"; 

tabbedpane.setTitleAt(0, PRE_HTML + "your title" + POST_HTML);
tabbedpane.setTitleAt(2, PRE_HTML + "your title 2" + POST_HTML);

这是最好的解决方案。 - Mario Jaramillo
困难的部分在于确定宽度。太小了就无法达到预期的结果。太大了,选项卡将部分隐藏在所选组件后面。我发现使用带有标题文本、图标等创建的JLabel的最大宽度效果很好(至少在金属主题和其他几个主题中)。 - guymac

2
从你的片段中无法确定对齐出了什么问题。TabComponentsDemo是一个完整示例,演示如何创建具有自定义组件的选项卡。在ButtonTabComponent中,请注意组件如何被赋予一个FlowLayout,其具有FlowLayout.LEFT对齐方式。你可以将其与你当前的方法进行比较。

+1是因为参考教程总是有用的。然而在我看来,问题更深层次,虽然在问题描述中没有很好地表述出来。当然,在教程中一切都顺利,我们都很开心,但是关于选项卡窗格和自定义选项卡组件存在设计问题。您能否请看一下我的答案?我非常感激。 - dic19

1
我所做的是添加一个组件(在这种情况下是JPanel)到选项卡中。我需要在选项卡上添加复选框,因此您可以手动添加图标而不是复选框。

以下是我的代码:

private JPanel makeTabPanel(JCheckBox checkBox) {
    String title = checkBox.getText();
    checkBox.setText("");
    JPanel pane = new JPanel();
    pane.setLayout(new BoxLayout(pane, BoxLayout.LINE_AXIS));
    pane.add(checkBox);
    pane.add(new JLabel(title, SwingUtilities.LEFT));
    pane.setMinimumSize(new Dimension(150, 25));
    pane.setPreferredSize(new Dimension(180, 25));
    pane.setMaximumSize(new Dimension(220, 25));
    return pane;
    }
     //then I call it with
    tabbed.setTabComponentAt(0, makeTabPanel(checkIncluderesume));

我知道给面板添加大小不是一个好的做法,但这是我能找到的最简单的方法。结果如下:

示例


0
tabbedPane.setTabComponentAt(idx, new JLabel(title, JLabel.LEFT));

这个不起作用是因为JLabel的大小适合它显示的字符串。请参见下面的图像。我在JLabel周围绘制了线框,以使它们的大小更加明显:

Swing窗口与选项卡标题居中对齐

我通过将所有JLabel的preferredSize设置为最长JLabel的大小来解决了这个问题。

您将需要编写自己的代码来查找最长的标题,但一旦找到它,您可以这样做:

JLabel prototypeLabel = new JLabel("The longest tab title");
        
for(int i = 0; i < tabbedPane.getTabCount(); ++i) {
    String title = tabbedPane.getTitleAt(i);
    JLabel label = new JLabel(title, JLabel.LEFT);
    label.setPreferredSize(prototypeLabel.getPreferredSize());

    tabbedPane.setTabComponentAt(i, label);
}

左对齐的带选项卡标题的Swing窗口


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接