سؤال

I'm embedding a JFileChooser in my program in my own frame with other custom components in the frame. Here's a design of my app as it may help visualize my issues:

How I'm using JFileChooser

If you can't tell, the lists directly under the JFrame titles are JFileChoosers. The way this is supposed to work is you assign shortcuts to destinations and then when you press those shortcut keys, the selected file moves to the destination.

My strategy for doing this is to assign the shortcut to a javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW scope of the InputMap of the entire frame.

But what's annoying is that something (I assume the JFileChooser) keeps on responding/absorbing key presses I don't want it to. For example, if I press Ctrl+C my shortcut action doesn't get run. I've tried this with the native Look and Feel (I'm using windows 7) and the default L&F and both situations have the same problem. I think it might be trying to do a copy action of the selected file in the JFileChooser because if I click on one of the buttons to force it to lose focus, all the sudden my Ctrl+C command does my action.

But, I'm not really sure how the JFileChooser is doing this. When I call getKeyListeners() on it, it returns an empty array. I've also tried clearing its input map for this key combination at all three scopes, and it still seems to be absorbing the keypress.

Can anyone give me some sample code that makes the JFileChooser ignore Ctrl+C? Also, it'd be helpful if someone could tell me how to debug problems like this in the future.


Here is some code of what I've tried so far. You can also use this to try to test this on your own, since this code compiles and runs, as-is:

package com.sandbox;

import javax.swing.*;
import java.awt.event.ActionEvent;

public class Sandbox {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control C"), "println");
        panel.getActionMap().put("println", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("The JPanel action was performed!");
            }
        });

        panel.add(buildFileChooser());  //if you comment out this line, Ctrl+C does a println, otherwise my action is ignored.

        frame.setContentPane(panel);

        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    private static JFileChooser buildFileChooser() {
        JFileChooser fileChooser = new JFileChooser();        
        fileChooser.getActionMap().clear(); //I've tried lots of ideas like this, but the JFileChooser still responds to Ctrl+C
        return fileChooser;
    }
}

UPDATE: I've gone as far as to recursively clear the inputMaps and remove the keyListeners of the JFileChooser and all of its child components and the JFileChooser still swallows my Ctrl+C command. Here's the code I've used to do this (I passed my JFileChooser into this):

private static void removeKeyboardReactors(JComponent root) {
    System.out.println("I'm going to clear the inputMap of: " + root);
    root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).clear();
    root.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).clear();
    root.getInputMap(JComponent.WHEN_FOCUSED).clear();
    root.getActionMap().clear();

    if (root.getRootPane() != null) {
        removeKeyboardReactors(root.getRootPane());
    }

    for (KeyListener keyListener : root.getKeyListeners()) {
        root.removeKeyListener(keyListener);
    }

    for (Component component : root.getComponents()) {
        if (component instanceof JComponent) {
            removeKeyboardReactors((JComponent) component);
        } else if (component instanceof Container) {
            Container container = (Container) component;
            for (Component containerComponent : container.getComponents()) {
                if (containerComponent instanceof JComponent) {
                    removeKeyboardReactors((JComponent) containerComponent);
                } else {
                    System.out.println("This Container Component was not a JComponent: " + containerComponent);
                }
            }
        } else {
            System.out.println("This was not a JComponent: " + component);
        }
    }
}
هل كانت مفيدة؟

المحلول

the details view will still have a populated inputmap

I suspect the difference between the details view and the list view is that one uses a JTable the other a JList. So I would guess you only need to remove the bindings from the JTable of the details view.

This can be done without creating the details panel:

InputMap im = (InputMap)UIManager.get("Table.ancestorInputMap");
KeyStroke ctrlC = KeyStroke.getKeyStroke("control C");
//im.put(ctrlC, "none");
im.remove(ctrlC);

Again, it should be noted that this solution (along with the solution you currently have) will remove the default Ctrl+C functionality for all components, not just the ones instantiated for the JFileChooser.

Edit:

Shouldn't it only be removing it from the ones I remove it from?

Your code uses the getParent() method to get the InputMap that contains the binding. This InputMap is shared by all instances of a component. A component will only have unique bindings when you use:

component.getInputMap(...).put(...);

That is, the binding is added to the components InputMap, not its parents InputMap.

How did you know you could do this and this is the right thing to do

See UIManager Defaults. This list the defaults for the given LAF. I don't know if this is the right thing to do. As far as I know the effect is the same as the code you use now. This is just another way or removing a binding from an InputMap without needing an actual component to access the parents InputMap.

Second edit:

Some simple code to show the InputMaps are the same:

public static void main(String[] args)
{
    JButton first = new JButton("button");
    System.out.println(first.getInputMap().getParent());

    InputMap im = (InputMap) UIManager.get("Button.focusInputMap");
    System.out.println(im);
}

نصائح أخرى

Apparently InputMaps can have parents. So your purging of all built-in key "reactors" is not entirely complete. As you've probably guessed, Swing registers certain default keyboard bindings for itself on certain components. On Windows this often includes Ctrl+C, since that is the OS-standard hotkey for copying data to the clipboard.

This modified removeKeyboardReactors gets the System.out.println appearing for me:

private static void removeKeyboardReactors(JComponent root) {
    System.out.println("I'm going to clear the inputMap of: " + root);
    clearInputMap(root.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));
    clearInputMap(root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW));
    clearInputMap(root.getInputMap(JComponent.WHEN_FOCUSED));

    for (KeyListener keyListener : root.getKeyListeners()) {
        root.removeKeyListener(keyListener);
    }

    for (Component component : root.getComponents()) {
        if (component instanceof JComponent) {
            removeKeyboardReactors((JComponent) component);
        } else if (component instanceof Container) {
            Container container = (Container) component;
            for (Component containerComponent : container.getComponents()) {
                if (containerComponent instanceof JComponent) {
                    removeKeyboardReactors((JComponent) containerComponent);
                } else {
                    System.out.println("This Container Component was not a JComponent: " + containerComponent);
                }
            }
        } else {
            System.out.println("This was not a JComponent: " + component);
        }
    }
}

private static void clearInputMap(InputMap inputMap) {
    inputMap.clear();
    while ((inputMap = inputMap.getParent()) != null) {
        inputMap.clear();
    }
}

I had to remove this code from removeKeyboardReactors because it was causing a stack overflow:

if (root.getRootPane() != null) {
    removeKeyboardReactors(root.getRootPane());
}

The entire modified Sandbox class follows below. Hopefully this is enough to get you on your way. If you want the key-binding removal to be more key-specific, have a look at InputMap#remove(KeyStroke).

public class Sandbox
{

    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control C"), "println");
        panel.getActionMap().put("println", new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                System.out.println("The JPanel action was performed!");
            }
        });

        JFileChooser fileChooser = buildFileChooser();
        panel.add(fileChooser); //if you comment out this line, Ctrl+C does a println, otherwise my action is ignored.

        frame.setContentPane(panel);

        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();

        removeKeyboardReactors(fileChooser);

        frame.setVisible(true);
    }

    private static JFileChooser buildFileChooser()
    {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.getActionMap().clear(); //I've tried lots of ideas like this, but the JFileChooser still responds to Ctrl+C
        return fileChooser;
    }

    private static void clearInputMap(InputMap inputMap)
    {
        inputMap.clear();
        while ((inputMap = inputMap.getParent()) != null)
        {
            inputMap.clear();
        }
    }

    private static void removeKeyboardReactors(JComponent root) {
        System.out.println("I'm going to clear the inputMap of: " + root);
        clearInputMap(root.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));
        clearInputMap(root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW));
        clearInputMap(root.getInputMap(JComponent.WHEN_FOCUSED));

        for (KeyListener keyListener : root.getKeyListeners()) {
            root.removeKeyListener(keyListener);
        }

        for (Component component : root.getComponents()) {
            if (component instanceof JComponent) {
                removeKeyboardReactors((JComponent) component);
            } else if (component instanceof Container) {
                Container container = (Container) component;
                for (Component containerComponent : container.getComponents()) {
                    if (containerComponent instanceof JComponent) {
                        removeKeyboardReactors((JComponent) containerComponent);
                    } else {
                        System.out.println("This Container Component was not a JComponent: " + containerComponent);
                    }
                }
            } else {
                System.out.println("This was not a JComponent: " + component);
            }
        }
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top