我想要的是:将所有图标都放置在左侧,不基于文本大小 [...]
在典型的实现中,选项卡的内容居中对齐 很有意义,因为需要适应该内容的区域直到选项卡被有效呈现。由于该区域取决于内容,而不同的选项卡可能具有不同的标题长度,因此必须制定如何呈现这些选项卡的策略。标准是使选项卡内容居中,并将选项卡区域适应此内容。当我们使用默认选项卡面板并将选项卡放置在顶部时,我们不太关心图标/文本对齐方式:唯一需要考虑的是选项卡具有不同的长度,但谁关心呢?毕竟,图标和文本是可见的,选项卡面板看起来足够好。但是,当您将选项卡放置在
LEFT或
RIGHT时,情况就不同了,它看起来不吸引人。显然,这种默认行为是长期存在的问题,有一个非常有趣的讨论
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:
![图片描述](https://istack.dev59.com/Idu0s.webp)
NimbusLookAndFeel:
![图片描述](https://istack.dev59.com/fiyLW.webp)
SeaglassLookAndFeel:
![图片描述](https://istack.dev59.com/HYA8u.webp)
WindowsLookAndFeel:
![图片描述](https://istack.dev59.com/k9ida.webp)