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

Posted

技术标签:

【中文标题】JTabbedPane:选项卡位置设置为 LEFT,但图标未对齐【英文标题】:JTabbedPane: tab placement set to LEFT but icons are not aligned 【发布时间】:2014-12-06 04:19:49 【问题描述】:

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

考虑这张图片:

如您所见,“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: icon on left side of tabs。

【问题讨论】:

1) 为了尽快获得更好的帮助,请发布MCVE(最小完整可验证示例)。 2) 例如,获取图像的一种方法是热链接到在this Q&A 中看到的图像。 这段代码怎么不是MCVE?不止于此,您还想看到 JFrame frame = new JFrame(); 它是 M,但不是 C、V 或 E。点击链接,阅读 MCVE。 不确定我是否理解这个问题。这似乎是默认行为。有关更多信息和示例,请参阅 How to Use TabbedPanes 上的 Swing 教程部分。 我的意思是左边是完整的,不像这样取决于 JLabel 文本大小,看看 Distribuir 协议下的“Editar Protocolo”图标如何不准确? 【参考方案1】:

我想要什么:图标全部在左边,而不是基于文本大小 [...]

标签的内容以典型实现为中心,这是有道理的,因为在标签被有效呈现之前,适合此内容所需的区域是不可预测的。由于区域取决于内容,并且不同的选项卡可能具有不同的标题长度,因此必须制定有关如何呈现这些选项卡的策略。标准是使选项卡内容居中并使选项卡区域适合此内容。当我们有一个默认的选项卡式窗格并在顶部放置选项卡时,我们不太关心图标/文本对齐:

唯一的问题可能是标签的长度不同,但谁在乎呢?毕竟,图标和文本是可见的,选项卡式窗格看起来已经足够好了。但是,当您将选项卡位置设置为 LEFTRIGHT 时,情况会有所不同,并且看起来没有吸引力:

显然这种默认行为是一个长期存在的问题,并且有一个非常有趣的讨论here。一些 SO 成员参与其中:@camickr、@kleopatra、@splunebob。正如在那篇文章中所讨论的,一个简单的解决方案是不可能的,并且提出了几种解决方法:基本上是自定义 UI 实现或使用面板作为渲染器并根据文本长度使用首选宽度/高度。这两种选择都涉及大量工作。

为了避免处理 UI 委托并利用 setTabComponentAt(...) 方法,我前段时间开始了一个选项卡式窗格扩展,我想在这里分享。该方法基于 Swing 的渲染器概念:必须生成一个组件才能渲染另一个组件的部分的类,目标是提供一种灵活的机制来添加自定义选项卡组件。

我在下面使用我的自定义选项卡式窗格提供了一个示例,这里概述了提供上述机制所需的所有接口/类。

ITabRenderer 接口

第一步是定义一个 iterface 来提供一个契约来渲染一个标签组件。

AbstractTabRenderer 类

提供基本方法以帮助实现getTabRendererComponent(...) 方法的抽象类。这个抽象类具有三个主要属性:

prototypeText:用于定义原型文本以生成默认渲染器组件。 prototypeIcon:用于定义原型图标生成默认渲染器。 horizontalTextAlignment:标签的文本水平对齐。

注意这个类是抽象的,因为它没有实现getTabRendererComponent(...) 方法。

DefaultTabRenderer 类

通过扩展AbstractTabRenderer 类的具体实现。请注意,如果您想像教程演示中所示包含一个关闭按钮,那么在这个类中做一些工作就足够了。事实上,我已经这样做了,但我不会包含该部分以不扩展这篇(已经很大)的帖子。

JXTabbedPane

最后是选项卡式窗格的扩展,其中包括选项卡渲染器支持并覆盖 addTab(...) 方法。

示例

我使用这些 PLAF 运行了这个示例并获得了积极的结果:

WindowsLookAndFeel WindowsClassicLookAndFeel NimbusLookAndFeel 金属外观和感觉 Seaglass LookAndFeel

另外,如果您将标签位置从 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());
            
        
    

截图

金属外观和感觉

NimbusLookAndFeel

SeaglassLookAndFeel

WindowsLookAndFeel

【讨论】:

非常好,我会试一试,非常好的人,谢谢大家:)! +1 非常详尽且有据可查;提前为我含糊的编辑手道歉。 相反,非常感谢您花时间阅读并改进我的答案:) @trashgod【参考方案2】:

从您的片段中不清楚对齐出错的地方。 TabComponentsDemo 是一个 complete example,说明了如何使用自定义组件创建 选项卡。在ButtonTabComponent 中,请注意如何为组件提供FlowLayoutFlowLayout.LEFT 对齐。您可以将此与您当前的方法进行比较。

【讨论】:

+1 因为参考教程总是有用的。但是恕我直言,尽管问题中没有很好地描述,但问题更深。当然,在教程中一切顺利,我们很高兴,但是关于选项卡式窗格和自定义选项卡组件存在设计问题。你能看看我的回答吗?我真的很感激。【参考方案3】:

使用 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);

【讨论】:

这是最好的解决方案 困难的部分是确定宽度。太小,它不会达到预期的结果。太大,选项卡将部分隐藏在所选组件后面。我发现使用由标题文本、图标等创建的最大宽度的 JLabel 效果很好(至少在金属主题和其他几个主题中)。【参考方案4】:

我所做的是为选项卡添加一个组件(在本例中为 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));

我知道给面板增加尺寸不是一个好习惯,但这是我能找到的最简单的方法。结果是:

sample

【讨论】:

以上是关于JTabbedPane:选项卡位置设置为 LEFT,但图标未对齐的主要内容,如果未能解决你的问题,请参考以下文章

如何更改 JTabbedPane 选项卡的形状?

选项卡面板(JTabbedPane)

Java Swing:从动态生成的 jTabbedPane 中获取值 [关闭]

选项卡关闭按钮位置

JAVA可折叠垂直布局

Java - 一个接一个地加载 JTabbedPane