jjzjj

java - Closeable JTabbedPane - 关闭按钮的对齐方式

coder 2024-04-01 原文

我已经实现了自己的可关闭 JTabbedPane(基本上遵循了 here 的建议 - 通过扩展 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<JTabbedPane>(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<JTabbedPane> {
  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<? extends JTabbedPane> 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<? extends JTabbedPane> 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();
      }
    }
  }
}

关于java - Closeable JTabbedPane - 关闭按钮的对齐方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24634047/

有关java - Closeable JTabbedPane - 关闭按钮的对齐方式的更多相关文章

  1. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  4. ruby-on-rails - 正确的 Rails 2.1 做事方式 - 2

    question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参

  5. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  6. ruby - 如何关闭 ruby​​ gem "Spreadsheet?"中的文件 - 2

    下面的代码在我第一次运行它时就可以正常工作:require'rubygems'require'spreadsheet'book=Spreadsheet.open'/Users/me/myruby/Mywks.xls'sheet=book.worksheet0row=sheet.row(1)putsrow[1]book.write'/Users/me/myruby/Mywks.xls'当我再次运行它时,我会收到更多消息,例如:/Library/Ruby/Gems/1.8/gems/spreadsheet-0.6.5.9/lib/spreadsheet/excel/reader.rb:11

  7. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  10. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

随机推荐