Java Swing: Caret-listener stops firing after JPopupMenu is shown

六眼飞鱼酱① 提交于 2019-12-11 03:47:06

问题


I'm making a text-editor application, and I've run into an issue with my CaretListener not being fired when text is typed or pasted after a JPopupMenu is shown.

I have added the CaretListener to a JTextArea with

textArea.addCaretListener(new CaretListener() {
        public void caretUpdate(CaretEvent e) {
            runThisMethod();
        }
    });

This works fine, and calls "runThisMethod()" whenever the caret moves (key press, text selection, etc...). In my application I have a JMenuBar, and also a JPopupMenu added using textArea.setComponentPopupMenu(popupMenu);.

My issue is whenever the popup is closed (ether by selecting a menuItem, or cancelling it by clicking anywhere but the JTextArea), the CaretListener stops firing for any key input (including pasting). Clicking anywhere in the JTextArea will get it working again, and it will again be called for key input. Using the JMenuBar does not trigger this issue.

Here is a code sample that demonstrates the issue (Sorry for the length):

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.DefaultEditorKit;

public class GUI {

    private JFrame mainFrame;
    private JTextArea textArea;
    private JLabel posLabel;

    public GUI()
    {
        mainFrame = new JFrame("Untitled");
        mainFrame.setSize(800, 400);
        mainFrame.setLocationRelativeTo(null);
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        initPanel((JPanel)mainFrame.getContentPane());

        mainFrame.setVisible(true);
    }

    private void initPanel(JPanel panel)
    {
        textArea = new JTextArea();
        initMenu();

        panel.setLayout(new BorderLayout());
        JPanel textPanel = new JPanel();
        textPanel.setLayout(new GridLayout(1,1));
        //Set some more stuff...

        //KeyListener works, and seams to show that the JTextArea is focused.
        textArea.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e){
                System.out.println("Key Update!");
                System.out.println(mainFrame.getFocusOwner().toString());
            }
            @Override
            public void keyPressed(KeyEvent e){}
            @Override
            public void keyReleased(KeyEvent e){}
            });

        //
        //CaretListener:
        //
        textArea.addCaretListener(new CaretListener() {

            @Override
            public void caretUpdate(CaretEvent e) {

                SwingUtilities.invokeLater(new Runnable(){      //Not sure if this is needed? Have tried with and without.

                    @Override
                    public void run() {

                        System.out.println("Caret Update!");
                        System.out.println(mainFrame.getFocusOwner().toString());
                        UpdatePosLabel();

                        //Do more stuff
                    }
                });
            }
        });

        textArea.addFocusListener(new FocusListener(){      //Updates Position once when popup is closed.

            SwingUtilities.invokeLater(new Runnable(){

                @Override
                public void run() {

                    System.out.println("Focus Update!");
                    System.out.println(mainFrame.getFocusOwner().toString());
                    UpdatePosLabel();
                }
            });
            }
            @Override
            public void focusLost(FocusEvent e) {}
        });

        //Did have DocumentListener, but as I recall it broke something (Can't remember what :( ), I'll experiment with adding it again.

        JScrollPane textScrollPane = new JScrollPane(textArea,
                                        JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                                            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        textPanel.add(textScrollPane);


        posLabel = new JLabel(" ");
        UpdatePosLabel();

        panel.add(textPanel, BorderLayout.CENTER);
        panel.add(posLabel, BorderLayout.PAGE_END);


    }

    private void initMenu()
    {
        //MenuBar
        JMenuBar jmb = new JMenuBar();
        mainFrame.setJMenuBar(jmb);

        JMenu menuEdit = new JMenu("Edit");

        Action Paste = textArea.getActionMap().get(DefaultEditorKit.pasteAction);

        JMenuItem itemPaste = new JMenuItem(Paste);
        itemPaste.setText("Paste");
        menuEdit.add(itemPaste);

        JMenuItem itemSelectAll = new JMenuItem("Select All");
        itemSelectAll.addActionListener(new ActionListener()  //Could maybe be done better...
                {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        textArea.selectAll();
                    }
                });
        menuEdit.add(itemSelectAll);

        jmb.add(menuEdit);


        //PopupMenu
        JPopupMenu popupMenu = new JPopupMenu();

        JMenuItem itemPastePopup = new JMenuItem(Paste);
        itemPastePopup.setText("Paste");
        popupMenu.add(itemPastePopup);


        JMenuItem itemSelectAllPopup = new JMenuItem("Select All");
        itemSelectAllPopup.addActionListener(new ActionListener()  //Could maybe be done better...
                {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        textArea.selectAll();
                    }
                });
        popupMenu.add(itemSelectAllPopup);

        textArea.setComponentPopupMenu(popupMenu);

    }

    //Just updates the label.
    private void UpdatePosLabel()
    {
        int lineNum = 1;
        int columnNum = 0;
        try {

            int caretpos = textArea.getCaretPosition();
            lineNum = textArea.getLineOfOffset(caretpos);
            columnNum = caretpos - textArea.getLineStartOffset(lineNum);
            lineNum += 1;
        }
        catch(Exception ex){
            posLabel.setText("Ln " + "?" + ", Col " + "?");
        }
        posLabel.setText("Ln " + lineNum + ", Col " + columnNum);
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable(){
            @Override
            public void run() {
                try
                {
                    new GUI();
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }

}

To replicate: Type or paste as normal, note the position updating at the bottom. Right click to bring up the menu, then select Paste or Select All. Try typing again, the position doesn't update (CaretListener not getting run).

Note: This does not seam to be a focus issue (though I could very well be wrong), as mainFrame.getFocusOwner().toString() seams to show the JTextArea when called, and popupMenu.setFocusable(false);doesn't help.

I've been stuck on this for awhile now, so if you could help explain what I'm doing wrong, and how I might go about getting the CaretListener to fire, I would really appreciate it! :)

Thanks, and Happy Halloween to those who celebrate it!

Update: This also happens with JTextFields (unsurprising, but thought i'd test it anyway), and removing the JScrollPane doesn't seam to have any effect. Calling setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE) on the JTextArea's Caret likewise makes no difference.

Update 2: I think I found a solution (see my answer)... though I'm still not sure why this problem was happening.


回答1:


Ok, I think I found a solution... I'll post it here in case anyone else experiences this. What I ended up doing is setting a ChangeListener directly on the JTextArea's Caret:

textArea.getCaret().addChangeListener(new ChangeListener(){     //Seams to work!

            @Override
            public void stateChanged(ChangeEvent e) {

                SwingUtilities.invokeLater(new Runnable(){

                    @Override
                    public void run() {

                        //Do stuff

                    }
                });
            }
        });

For some reason, this seams to get fired even when the CaretListener on the JTextArea doesn't. I'm not sure why this would happen though, so I might post another question regarding that.

Hope this helps anyone who might have the same issue.



来源:https://stackoverflow.com/questions/40339741/java-swing-caret-listener-stops-firing-after-jpopupmenu-is-shown

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!