可关闭的JTabbedPane – 关闭按钮的对齐方式

我已经实现了我自己的可关闭JTabbedPane (基本上遵循这里的建议 – 通过扩展JTabbedPane并重写一些方法并调用setTabComponentAt(…))。 除了一件事情之外它完美地工作 – 当有太多标签适合一行时(当有2行或更多行标签时),十字按钮/图标没有对齐到标签的右边但是它仍然在旁边标题标题,看起来很难看。 我已经尝试过Java教程中的演示,它遇到了同样的问题。

我想要的是十字按钮/图标始终与最右边对齐,但文本始终与中心对齐。 这可以通过一些布局技巧来实现吗? 注意:我不想实现自定义TabbedPaneUI因为这会导致其他问题。

更新我被迫使用Java 6

完整的代码如下,只需运行它并添加5个或更多选项卡。

 import java.awt.BorderLayout; import java.awt.Component; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.net.URL; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTabbedPane; /** * CloseableTabbedPane is a tabbed pane with a close icon on the right side of all tabs making it possible to close a tab. * You can pass an instance of TabClosingListener to one of the constructors to react to tab closing. * * @author WiR */ public class CloseableTabbedPane extends JTabbedPane { public static interface TabClosingListener { /** * @param aTabIndex the index of the tab that is about to be closed * @return true if the tab can be really closed */ public boolean tabClosing(int aTabIndex); /** * @param aTabIndex the index of the tab that is about to be closed * @return true if the tab should be selected before closing */ public boolean selectTabBeforeClosing(int aTabIndex); } private TabClosingListener tabClosingListener; private String iconFileName = "images/cross.gif"; private String selectedIconFileName = "images/cross_selected.gif"; private static Icon CLOSING_ICON; private static Icon CLOSING_ICON_SELECTED; private class PaintedCrossIcon implements Icon { int size = 10; @Override public void paintIcon(Component c, Graphics g, int x, int y) { g.drawLine(x, y, x + size, y + size); g.drawLine(x + size, y, x, y + size); } @Override public int getIconWidth() { return size; } @Override public int getIconHeight() { return size; } } public CloseableTabbedPane() { super(); } public CloseableTabbedPane(TabClosingListener aTabClosingListener) { super(); tabClosingListener = aTabClosingListener; } /** * Sets the file name of the closing icon along with the optional variant of the icon when the mouse is over the icon. */ public void setClosingIconFileName(String aIconFileName, String aSelectedIconFileName) { iconFileName = aIconFileName; selectedIconFileName = aSelectedIconFileName; } /** * Makes the close button at the specified indes visible or invisible */ public void setCloseButtonVisibleAt(int aIndex, boolean aVisible) { CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(aIndex); cbt.closingLabel.setVisible(aVisible); } @Override public void insertTab(String title, Icon icon, Component component, String tip, int index) { super.insertTab(title, icon, component, tip, index); setTabComponentAt(index, new CloseButtonTab(component, title, icon)); } @Override public void setTitleAt(int index, String title) { super.setTitleAt(index, title); CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(index); cbt.label.setText(title); } @Override public void setIconAt(int index, Icon icon) { super.setIconAt(index, icon); CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(index); cbt.label.setIcon(icon); } @Override public void setComponentAt(int index, Component component) { CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(index); super.setComponentAt(index, component); cbt.tab = component; } //note: setToolTipTextAt(int) must NOT be overridden ! private Icon getImageIcon(String aImageName) { URL imageUrl = CloseableTabbedPane.class.getClassLoader().getResource(aImageName); if (imageUrl == null) { return new PaintedCrossIcon(); } ImageIcon result = new ImageIcon(imageUrl); if (result.getIconWidth() != -1) { return result; } else { return null; } } private class CloseButtonTab extends JPanel { private Component tab; private JLabel label; private JLabel closingLabel; public CloseButtonTab(Component aTab, String aTitle, Icon aIcon) { tab = aTab; setOpaque(false); setLayout(new GridBagLayout()); setVisible(true); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(0, 0, 0, 5); label = new JLabel(aTitle); label.setIcon(aIcon); add(label, gbc); if (CLOSING_ICON == null) { CLOSING_ICON = getImageIcon(iconFileName); CLOSING_ICON_SELECTED = getImageIcon(selectedIconFileName); } closingLabel = new JLabel(CLOSING_ICON); closingLabel.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { JTabbedPane tabbedPane = (JTabbedPane) getParent().getParent(); int tabIndex = indexOfComponent(tab); if (tabClosingListener != null) { if (tabClosingListener.selectTabBeforeClosing(tabIndex)) { tabbedPane.setSelectedIndex(tabIndex); } if (tabClosingListener.tabClosing(tabIndex)) { tabbedPane.removeTabAt(tabIndex); } } else { tabbedPane.removeTabAt(tabIndex); } } @Override public void mouseEntered(MouseEvent e) { if (CLOSING_ICON_SELECTED != null) { closingLabel.setIcon(CLOSING_ICON_SELECTED); } } @Override public void mouseExited(MouseEvent e) { if (CLOSING_ICON_SELECTED != null) { closingLabel.setIcon(CLOSING_ICON); } } }); gbc.insets = new Insets(0, 0, 0, 0); add(closingLabel, gbc); } } static int count = 0; /** * For testing purposes. * */ public static void main(String[] args) { final JTabbedPane tabbedPane = new CloseableTabbedPane(); tabbedPane.addTab("test" + count, new JPanel()); count++; JPanel mainPanel = new JPanel(new BorderLayout()); mainPanel.add(tabbedPane, BorderLayout.CENTER); JButton addButton = new JButton("Add tab"); addButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tabbedPane.addTab("test" + count, new JPanel()); count++; } }); mainPanel.add(addButton, BorderLayout.SOUTH); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(700, 400); frame.getContentPane().add(mainPanel); frame.setVisible(true); } } 

以下是使用JLayer一种可能的实现:

在此处输入图像描述

 import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.plaf.*; public class CloseableTabbedPaneTest { public JComponent makeUI() { UIManager.put("TabbedPane.tabInsets", new Insets(2, 2, 2, 50)); final JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("aaaaaaaaaaaaaaaa", new JPanel()); tabbedPane.addTab("bbbbbbbb", new JPanel()); tabbedPane.addTab("ccc", new JPanel()); JPanel p = new JPanel(new BorderLayout()); p.add(new JLayer(tabbedPane, new CloseableTabbedPaneLayerUI())); p.add(new JButton(new AbstractAction("add tab") { @Override public void actionPerformed(ActionEvent e) { tabbedPane.addTab("test", new JPanel()); } }), BorderLayout.SOUTH); return p; } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { createAndShowGUI(); } }); } public static void createAndShowGUI() { JFrame f = new JFrame(); f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); f.getContentPane().add(new CloseableTabbedPaneTest().makeUI()); f.setSize(320, 240); f.setLocationRelativeTo(null); f.setVisible(true); } } class CloseableTabbedPaneLayerUI extends LayerUI { private final JPanel p = new JPanel(); private final Point pt = new Point(-100, -100); private final JButton button = new JButton("x") { @Override public Dimension getPreferredSize() { return new Dimension(16, 16); } }; public CloseableTabbedPaneLayerUI() { super(); button.setBorder(BorderFactory.createEmptyBorder()); button.setFocusPainted(false); button.setBorderPainted(false); button.setContentAreaFilled(false); button.setRolloverEnabled(false); } @Override public void paint(Graphics g, JComponent c) { super.paint(g, c); if (c instanceof JLayer) { JLayer jlayer = (JLayer) c; JTabbedPane tabPane = (JTabbedPane) jlayer.getView(); for (int i = 0; i < tabPane.getTabCount(); i++) { Rectangle rect = tabPane.getBoundsAt(i); Dimension d = button.getPreferredSize(); int x = rect.x + rect.width - d.width - 2; int y = rect.y + (rect.height - d.height) / 2; Rectangle r = new Rectangle(x, y, d.width, d.height); button.setForeground(r.contains(pt) ? Color.RED : Color.BLACK); SwingUtilities.paintComponent(g, button, p, r); } } } @Override public void installUI(JComponent c) { super.installUI(c); ((JLayer)c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); } @Override public void uninstallUI(JComponent c) { ((JLayer)c).setLayerEventMask(0); super.uninstallUI(c); } @Override protected void processMouseEvent(MouseEvent e, JLayer l) { if (e.getID() == MouseEvent.MOUSE_CLICKED) { pt.setLocation(e.getPoint()); JTabbedPane tabbedPane = (JTabbedPane) l.getView(); int index = tabbedPane.indexAtLocation(pt.x, pt.y); if (index >= 0) { Rectangle rect = tabbedPane.getBoundsAt(index); Dimension d = button.getPreferredSize(); int x = rect.x + rect.width - d.width - 2; int y = rect.y + (rect.height - d.height) / 2; Rectangle r = new Rectangle(x, y, d.width, d.height); if (r.contains(pt)) { tabbedPane.removeTabAt(index); } } l.getView().repaint(); } } @Override protected void processMouseMotionEvent(MouseEvent e, JLayer l) { pt.setLocation(e.getPoint()); JTabbedPane tabbedPane = (JTabbedPane) l.getView(); int index = tabbedPane.indexAtLocation(pt.x, pt.y); if (index >= 0) { tabbedPane.repaint(tabbedPane.getBoundsAt(index)); } else { tabbedPane.repaint(); } } } 

编辑:

以下是使用GlassPane的示例(注意:这根本未经过测试):

 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class CloseableTabbedPaneTest2 { public JComponent makeUI() { UIManager.put("TabbedPane.tabInsets", new Insets(2, 2, 2, 50)); final JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("aaaaaaaaaaaaaaaa", new JPanel()); tabbedPane.addTab("bbbbbbbb", new JPanel()); tabbedPane.addTab("ccc", new JPanel()); JPanel p = new JPanel(new BorderLayout()); //p.setBorder(BorderFactory.createLineBorder(Color.RED, 10)); p.add(tabbedPane); p.add(new JButton(new AbstractAction("add tab") { @Override public void actionPerformed(ActionEvent e) { tabbedPane.addTab("test", new JScrollPane(new JTree())); } }), BorderLayout.SOUTH); EventQueue.invokeLater(new Runnable() { @Override public void run() { JPanel gp = new CloseableTabbedPaneGlassPane(tabbedPane); tabbedPane.getRootPane().setGlassPane(gp); gp.setOpaque(false); gp.setVisible(true); } }); return p; } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { createAndShowGUI(); } }); } public static void createAndShowGUI() { JFrame f = new JFrame(); f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); f.getContentPane().add(new CloseableTabbedPaneTest2().makeUI()); f.setSize(320, 240); f.setLocationRelativeTo(null); f.setVisible(true); } } class CloseableTabbedPaneGlassPane extends JPanel { private final Point pt = new Point(-100, -100); private final JButton button = new JButton("x") { @Override public Dimension getPreferredSize() { return new Dimension(16, 16); } }; private final JTabbedPane tabbedPane; private final Rectangle buttonRect = new Rectangle(button.getPreferredSize()); public CloseableTabbedPaneGlassPane(JTabbedPane tabbedPane) { super(); this.tabbedPane = tabbedPane; MouseAdapter h = new Handler(); tabbedPane.addMouseListener(h); tabbedPane.addMouseMotionListener(h); button.setBorder(BorderFactory.createEmptyBorder()); button.setFocusPainted(false); button.setBorderPainted(false); button.setContentAreaFilled(false); button.setRolloverEnabled(false); } @Override public void paintComponent(Graphics g) { Point glassPt = SwingUtilities.convertPoint(tabbedPane, 0, 0, this); for (int i = 0; i < tabbedPane.getTabCount(); i++) { Rectangle tabRect = tabbedPane.getBoundsAt(i); int x = tabRect.x + tabRect.width - buttonRect.width - 2; int y = tabRect.y + (tabRect.height - buttonRect.height) / 2; buttonRect.setLocation(x, y); button.setForeground(buttonRect.contains(pt) ? Color.RED : Color.BLACK); buttonRect.translate(glassPt.x, glassPt.y); SwingUtilities.paintComponent(g, button, this, buttonRect); } } class Handler extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { pt.setLocation(e.getPoint()); int index = tabbedPane.indexAtLocation(pt.x, pt.y); if (index >= 0) { Rectangle tabRect = tabbedPane.getBoundsAt(index); int x = tabRect.x + tabRect.width - buttonRect.width - 2; int y = tabRect.y + (tabRect.height - buttonRect.height) / 2; buttonRect.setLocation(x, y); if (buttonRect.contains(pt)) { tabbedPane.removeTabAt(index); } } tabbedPane.repaint(); } @Override public void mouseMoved(MouseEvent e) { pt.setLocation(e.getPoint()); int index = tabbedPane.indexAtLocation(pt.x, pt.y); if (index >= 0) { tabbedPane.repaint(tabbedPane.getBoundsAt(index)); } else { tabbedPane.repaint(); } } } } 

我正在使用这个: http : //docs.oracle.com/javase/tutorial/uiswing/examples/components/TabComponentsDemoProject/src/components/ButtonTabComponent.java

关闭按钮由它自己绘制,因此可以放在任何地方。