带有鼠标侦听器的 JTabbedPane 选项卡

JTabbedPane Tab with Mouse Listener

我正在尝试创建一个选项卡窗格,用户可以在其中 double-click 在选项卡上编辑其标题。到目前为止,我已经能够创建一个选项卡组件,其中 JPanelJTextField 相互重叠,当您 double-click [=] 时切换到 JTextField 12=] 并在按 Enter:

时返回 JPanel
public class EditablePanel extends JPanel {
    private JLabel label;
    private JTextField field;

    public EditablePanel(String title) {
        super();
        setLayout(new OverlayLayout(this));
        setOpaque(false);

        add(label = new JLabel(title));
        label.setFocusable(false);

        field = new JTextField(title);
        field.setBorder(BorderFactory.createEmptyBorder());
        field.setVisible(false);
        field.addActionListener((e) -> finish(true));
        add(field);

        label.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    label.setVisible(false);
                    field.setVisible(true);
                    field.requestFocusInWindow();
                    field.selectAll();
                }
            }
        });
    }

    private void finish(boolean commit) {
        label.setText(field.getText());
        label.setVisible(true);
        field.setVisible(false);
    }
}

当我将 JTabbedPane 中的选项卡组件设置为此 EditablePanel 的实例时,如果我的鼠标是在标签上。此外,如果 LAF 在鼠标悬停在选项卡上时执行某些操作(例如像 Windows 那样更改其颜色),则在鼠标悬停在标签上时停止应用。如果我单击 JLabel 之外的区域,我可以切换标签页,但如果我在 JLabel 上单击 single-click,则不能。我希望它能工作,这样如果我 single-click 在选项卡中的任何位置,它都会切换到该选项卡,但如果我 double-click 在选项卡上,它会开始编辑选项卡标题。

我试过在 EditablePanel 中使用 getMouseListeners 将面板的鼠标事件转发到 JLabel,但它似乎忽略了它们。有没有一种方法可以将此组件用作选项卡组件,同时保留悬停和单击以更改选项卡的现有功能?如果没有,有没有办法扩展 JTabbedPane 以获得我想要的可编辑标题功能?

这是一个完整的 SCCM 演示我的问题:

import java.awt.Dimension;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.*;

public class EditablePanel extends JPanel {
    private JLabel label;
    private JTextField field;

    public EditablePanel(String title) {
        super();
        setLayout(new OverlayLayout(this));
        setOpaque(false);

        add(label = new JLabel(title));
        label.setFocusable(false);

        field = new JTextField(title);
        field.setBorder(BorderFactory.createEmptyBorder());
        field.setVisible(false);
        field.addActionListener((e) -> finish(true));
        add(field);

        label.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    label.setVisible(false);
                    field.setVisible(true);
                    field.requestFocusInWindow();
                    field.selectAll();
                }
            }
        });
    }

    private void finish(boolean commit) {
        label.setText(field.getText());
        label.setVisible(true);
        field.setVisible(false);
        field.transferFocusUpCycle();
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Editable Tab Headers");
        frame.setPreferredSize(new Dimension(400, 300));
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JTabbedPane pane = new JTabbedPane();
        pane.addTab("First Tab", new JLabel("First tab contents"));
        pane.addTab("Second Tab", new JLabel("Second tab contents"));
        pane.setTabComponentAt(0, new EditablePanel("First Tab"));
        pane.setTabComponentAt(1, new EditablePanel("Second Tab"));
        frame.add(pane);

        frame.pack();
        frame.setVisible(true);
    }
}

is there a way I could extend JTabbedPane to get the editable title functionality I want?

我会将 MouseListener 添加到选项卡式窗格。

然后在 mouseClicked(...) 事件中,您可以检查双击并在选项卡顶部显示 JTextField。当您在文本字段上按 Enter 键时,您会从选项卡式窗格中删除文本字段。

所以显示文本字段的基础是:

JTabbedPane tabbedPane = (JTabbedPane)e.getComponent();
TabbedPaneUI ui = tabbedPane.getUI();

int tab = ui.tabForCoordinate(tabbedfPane, e.getX(), e.getY());

if (tab != -1) // I believe -1 is returned if you don't click on a tab
{
    Rectangle bounds = ui.getTabBounds(tabbedPane, tab);
    JTextField textField = new JTextField();
    textField.setText(...);
    textField.setBounds( bounds );
    textField.addActionListener(...);
    tabbedPane.add( textField );
    tappedPane.repaint();
}

然后在 ActionListener 中您将获取文本并更新选项卡标题,然后从选项卡式窗格中删除文本字段。

现在选项卡应该正常运行,因为文本字段仅在您编辑文本标题时临时显示。

注意,这基本上就是 JTable 编辑器的工作方式。当您双击一个单元格时,一个文本字段会添加到单元格顶部的 table,然后在编辑完成后删除。

作为 camickr 提议的替代方案,您可以使用 MouseListener 将所有事件分派给 JTabbedPane。这应该达到不影响其他行为的预期效果,并且仍然处理双击的特殊情况。

这是一个 MCVE,我还修复了编辑行为,因为我添加了一个焦点侦听器,当在编辑时选择新选项卡时(即当编辑 notEnter).

确认
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.OverlayLayout;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class EditablePanel extends JPanel
{
    private JLabel label;
    private JTextField field;

    public EditablePanel(String title)
    {
        super();
        setLayout(new OverlayLayout(this));
        setOpaque(false);

        add(label = new JLabel(title));
        label.setFocusable(false);

        field = new JTextField(title);
        field.setBorder(BorderFactory.createEmptyBorder());
        field.setVisible(false);
        field.addActionListener((e) -> finish(true));
        field.addFocusListener(new FocusListener()
        {
            @Override
            public void focusLost(FocusEvent e)
            {
                finish(false);
            }

            @Override
            public void focusGained(FocusEvent e)
            {
                // Nothing to do here
            }
        });
        add(field);

        TabMouseAdapter mouseAdapter = new TabMouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                super.mouseClicked(e);
                if (e.getClickCount() == 2)
                {
                    label.setVisible(false);
                    field.setVisible(true);
                    field.requestFocusInWindow();
                    field.selectAll();
                }
            }
            @Override
            public void mousePressed(MouseEvent e)
            {
                super.mousePressed(e);
                finish(false);
            }

        };
        label.addMouseListener(mouseAdapter);
    }

    static class TabMouseAdapter implements MouseListener
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            redispatch(e);
        }

        @Override
        public void mousePressed(MouseEvent e)
        {
            redispatch(e);
        }

        @Override
        public void mouseReleased(MouseEvent e)
        {
            redispatch(e);
        }

        @Override
        public void mouseEntered(MouseEvent e)
        {
            redispatch(e);
        }

        @Override
        public void mouseExited(MouseEvent e)
        {
            redispatch(e);
        }

        private void redispatch(MouseEvent e)
        {
            Component source = e.getComponent();
            Component target = source.getParent();
            while (true)
            {
                if (target == null)
                {
                    break;
                }
                if (target instanceof JTabbedPane)
                {
                    break;
                }
                target = target.getParent();
            }
            if (target != null)
            {
                MouseEvent targetEvent =
                    SwingUtilities.convertMouseEvent(source, e, target);
                target.dispatchEvent(targetEvent);
            }
        }
    }

    private void finish(boolean commit)
    {
        if (commit)
        {
            label.setText(field.getText());
        }
        label.setVisible(true);
        field.setVisible(false);
        field.transferFocusUpCycle();
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("Editable Tab Headers");
        frame.setPreferredSize(new Dimension(400, 300));
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JTabbedPane pane = new JTabbedPane();
        pane.addTab("First Tab", new JLabel("First tab contents"));
        pane.addTab("Second Tab", new JLabel("Second tab contents"));
        pane.setTabComponentAt(0, new EditablePanel("First Tab"));
        pane.setTabComponentAt(1, new EditablePanel("Second Tab"));
        frame.add(pane);

        frame.pack();
        frame.setVisible(true);
    }
}

例如,如果您想将弹出菜单添加到第 i 个选项卡 您可以使用所选选项卡和第 i 个选项卡的相等性。 在我的示例中,我想将 popupmenu 添加到我的第一个选项卡中 我应该检查 selectedtab 和第一个 tab(index=0)

是否相等

你可以这样使用:

JTabbedPane tabs=new JTabbedPane();
JPopupMenu popupMenu=new JPopupMenu("Edit");
popupMenu.add(new JMenuItem("Cut"));
popupMenu.add(new JMenuItem("Copy"));
JPanel bodyPanel=new JPanel();
tabs.add("Body",bodyPanel);
tabs.add("DummyTab",new JPanel());

tabs.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            if(tabs.getSelectedComponent().equals(tabs.getComponentAt(0))){
                popupMenu.show(bodyPanel,e.getX(),e.getY());
            }
        }
    });