简体   繁体   中英

JTabbedPane Tab with Mouse Listener

I am trying to create a tab pane where a user can double-click on the tab to edit its title. So far I have been able to create a tab component where a JPanel and JTextField are overlaid on top of each other which switches to the JTextField when you double-click the JPanel and back to the JPanel when you press Enter :

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);
    }
}

When I set the component of a tab in a JTabbedPane to an instance of this EditablePanel , I can no longer click on the tab to select it if my mouse is over the label. Additionally, if the LAF does something when the mouse hovers over the tab (like change its color as Windows does), that stops applying while the mouse is over the label. I can switch tabs if I click an area outside the JLabel , but not if I single-click on the JLabel . I would like it work so that if I single-click anywhere in the tab, it switches to that tab, but if I double-click on a tab, it begins editing the tab title.

I've tried using getMouseListeners in the EditablePanel to forward the panel's mouse events to the JLabel , but it seems to ignore them. Is there a way I can use this component as a tab component while also keeping the existing functionality of hovering and clicking to change tabs? If not, is there a way I could extend JTabbedPane to get the editable title functionality I want?

Here is a full SCCM demonstrating my problem:

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);
    }
}

As an alternative to what camickr proposed, you could use a MouseListener that dispatches all events to the JTabbedPane . This should achieve the desired effect of not affecting the other behavior, and still handle the special case of a double-click.

Here is an MCVE, where I also fixed the editing behavior in that I added a focus listener that basically cancels the editing when a new tab is selected while editing (ie when the edit is not confirmed by pressing Enter ).

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);
    }
}

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

I would add the MouseListener to the tabbed pane.

Then in the mouseClicked(...) event you can check for a double click and display a JTextField over top of the tab. When you press enter on the text field you remove the text field from the tabbed pane.

So the basics of displaying the text field would be:

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();
}

Then in the ActionListener you would get the text and update the tab title and then remove the text field from the tabbed pane.

Now the tab should behave as normal since the text field is only displayed temporarily while you edit the text title.

Note, this is basically how the editor of a JTable works. When you double click a cell, a text field is added to the table over top of the cell and then removed when editing is finished.

For example if you want to add popupmenu to your i-th tab you can use equality of selected tab and i-th tab. in my example I want to add popupmenu to my first tab so I should check equality of selectedtab and first tab(index=0)

You can use this way :

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());
            }
        }
    });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM