Change the location of the accessory component in

2019-01-26 01:24发布

问题:

I have a problem in changing the location of the accessory component in a filechooser.

I customized a save file dialog by putting a check box in the accessory component of the file chooser. But the position of the check box is not good, it is really ugly.

Can I move the accessory component to be underneath the file pane? How to do it?

Or if you have other solution to do the same thing, also welcome.

Thank you guys.

I using following code to add the check box:

JFileChooser fc = new JFileChooser(file)
JPanel accessory = new JPanel();
JCheckBox isOpenBox = new JCheckBox("Open file after saving");
accessory.setLayout(new BorderLayout());
accessory.add(isOpenBox, BorderLayout.SOUTH);
fc.setAccessory(accessory);

In this screenshot, the position of the check box is not good.

This screenshot is the exactly effect I want.

回答1:

The "right" way would be to build a new UI/Look and Feel delegate which meet your requirements. The problem is, the details you need to do this (in an OO way) are hidden, either private with no public/protected accessor or defined locally...this is a major problem with most of the L&F delegates...thanks guys...

So, you'd end up copying and pasting the entire class, just so you can add this one feature...and you'd need to do this for EVERY platform you wanted to support...

The "less than optimal" way, is to ferret around within the JFileChooser and pull out those elements you need to "retrofit" your requirements. This is messy, it's error prone, it makes assumptions and will break very, very easily.

Personally, if I was to go down this particular track, I would create a utility class which provides a simple public, static method which would allow me to pass an instance of JFileChooser and have it, based on the current platform (and/or the current look and feel), make the changes for me...or a factory class that could auto generate a JFileChooser modified to meet those requirements...but this is just an idea...

Let me re-iterate this, this is a very bad idea to a very poor design problem and is meant to demonstrate: 1- The problems with trying to modify a look and feel; 2- The problems and issues you will face in attempting to make this work the way want it to...

import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFileChooser fc = new JFileChooser();
                List<JTextField> fields = findAll(JTextField.class, fc);
                if (fields.size() == 1) {
                    JTextField fieldNameField = fields.get(0);
                    Container parent = fieldNameField.getParent();
                    JCheckBox cb = new JCheckBox("Open file after saving");

                    JComboBox fileTypes = findAll(JComboBox.class, parent).get(0);

                    parent.setLayout(new GridBagLayout());
                    parent.removeAll();
                    GridBagConstraints gbc = new GridBagConstraints();
                    gbc.gridx = 0;
                    gbc.gridy = 0;
                    gbc.fill = GridBagConstraints.HORIZONTAL;
                    gbc.weightx = 1;
                    gbc.insets = new Insets(2, 2, 4, 2);

                    parent.add(fieldNameField, gbc); // file name field...

                    gbc.gridx++;
                    gbc.weightx = 0;
                    parent.add(cb, gbc); // Check box

                    gbc.gridx = 0;
                    gbc.gridy++;
                    gbc.gridwidth = GridBagConstraints.REMAINDER;
                    parent.add(fileTypes, gbc); // File types

                } else {
                    System.out.println("Found to many results?!");
                }

                fc.showOpenDialog(null);
            }
        });
    }

    public <T extends Component> List<T> findAll(Class<? extends T> aClass, Container parent) {
        List<T> matches = new ArrayList<>();

        for (Component child : parent.getComponents()) {
            if (aClass.isInstance(child)) {
                matches.add((T)child);
            }
            if (child instanceof Container) {
                matches.addAll(findAll(aClass, (Container)child));
            }
        }

        return matches;
    }

}

And no, I'm not proud...need to go wash my eyes and brain out with bleach...icky...

Updated with Mac Support

And for those who would like a Mac version...

This just further highlights the "flakiness" of this approach, it won't take much to break it...

import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFileChooser fc = new JFileChooser();
                List<JLabel> labels = findAll(JLabel.class, fc);

                JCheckBox cb = new JCheckBox("Open file after saving");
                JLabel fileFormatLabel = null;
                for (JLabel label : labels) {
                    if ("File Format:".equals(label.getText())) {
                        fileFormatLabel = label;
                    }
                }
                System.out.println(fileFormatLabel);
                if (fileFormatLabel != null) {
                    Container parent = fileFormatLabel.getParent();
                    parent.add(cb);
                }

                fc.showOpenDialog(null);
            }
        });
    }

    public <T extends Component> List<T> findAll(Class<? extends T> aClass, Container parent) {
        List<T> matches = new ArrayList<>();

        for (Component child : parent.getComponents()) {
            if (aClass.isInstance(child)) {
                matches.add((T) child);
            }
            if (child instanceof Container) {
                matches.addAll(findAll(aClass, (Container) child));
            }
        }

        return matches;
    }

}

Updated with Locale based searching

This uses the FileChooser.filesOfTypeLabelText UIManager.getString to look up the text of the File Format: key, which in theory, should make it a (slightly) better cross platform solution...at least it makes it work better on the mac...

This also demonstrates the mess of having to start to support multiple OS...Since I only have two...

import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test1 {

    public static void main(String[] args) {
        new Test1();
    }

    public Test1() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFileChooser fc = new JFileChooser();        
                JCheckBox cb = new JCheckBox("Open file after saving");

                if (System.getProperty("os.name").startsWith("Windows 7")) {
                    List<JTextField> fields = findAll(JTextField.class, fc);
                    if (fields.size() == 1) {
                        JTextField fieldNameField = fields.get(0);
                        Container parent = fieldNameField.getParent();
                        JComboBox fileTypes = findAll(JComboBox.class, parent).get(0);

                        parent.setLayout(new GridBagLayout());
                        parent.removeAll();
                        GridBagConstraints gbc = new GridBagConstraints();
                        gbc.gridx = 0;
                        gbc.gridy = 0;
                        gbc.fill = GridBagConstraints.HORIZONTAL;
                        gbc.weightx = 1;
                        gbc.insets = new Insets(2, 2, 4, 2);

                        parent.add(fieldNameField, gbc); // file name field...

                        gbc.gridx++;
                        gbc.weightx = 0;
                        parent.add(cb, gbc); // Check box

                        gbc.gridx = 0;
                        gbc.gridy++;
                        gbc.gridwidth = GridBagConstraints.REMAINDER;
                        parent.add(fileTypes, gbc); // File types                        

                    }

                } else if (System.getProperty("os.name").startsWith("Mac OS X")) {

                    Locale l = fc.getLocale();
                    JLabel fileFormatLabel = findLabelByText(fc, UIManager.getString("FileChooser.filesOfTypeLabelText", l), "Format:");

                    if (fileFormatLabel != null) {
                        Container parent = fileFormatLabel.getParent();
                        System.out.println("");

                        parent.add(cb);
                    }

                }

                fc.showOpenDialog(null);
            }
        });
    }

    public JLabel findLabelByText(Container parent, String... texts) {
        JLabel find = null;
        List<JLabel> labels = findAll(JLabel.class, parent);
        for (JLabel label : labels) {
            for (String text : texts) {
                if (text.equals(label.getText())) {
                    find = label;
                    break;
                }
            }
        }
        return find;
    }

    public <T extends Component> List<T> findAll(Class<? extends T> aClass, Container parent) {
        List<T> matches = new ArrayList<>();

        for (Component child : parent.getComponents()) {
            if (aClass.isInstance(child)) {
                matches.add((T) child);
            }
            if (child instanceof Container) {
                matches.addAll(findAll(aClass, (Container) child));
            }
        }

        return matches;
    }

}

Updated with reflection...

Since I'm already going to programmer hell for the previous "hacks", I might as well as throw in an example of using reflection to find the fields.

Now, there are a number of ways you might do this, you could list all the fields by type, for example and inspect various properties to determine what you want. This would be used when you don't know the actual field name OR, if you have access to the source code, you can look up the field names directly, as is done in this example

import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.FileChooserUI;

public class Test1 {

    public static void main(String[] args) {
        new Test1();
    }

    public Test1() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFileChooser fc = new JFileChooser();
                JCheckBox cb = new JCheckBox("Open file after saving");

                if (System.getProperty("os.name").startsWith("Windows 7")) {

                    try {
                        JTextField filenameTextField = (JTextField) getField("filenameTextField", fc.getUI());
                        JComboBox filterComboBox = (JComboBox) getField("filterComboBox", fc.getUI());

                        System.out.println(filenameTextField);
                        System.out.println(filterComboBox);

                        Container parent = filenameTextField.getParent();

                        parent.setLayout(new GridBagLayout());
                        parent.removeAll();
                        GridBagConstraints gbc = new GridBagConstraints();
                        gbc.gridx = 0;
                        gbc.gridy = 0;
                        gbc.fill = GridBagConstraints.HORIZONTAL;
                        gbc.weightx = 1;
                        gbc.insets = new Insets(2, 2, 4, 2);

                        parent.add(filenameTextField, gbc); // file name field...

                        gbc.gridx++;
                        gbc.weightx = 0;
                        parent.add(cb, gbc); // Check box

                        gbc.gridx = 0;
                        gbc.gridy++;
                        gbc.gridwidth = GridBagConstraints.REMAINDER;
                        parent.add(filterComboBox, gbc); // File types                        
                    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
                        ex.printStackTrace();
                    }
                } else if (System.getProperty("os.name").startsWith("Mac OS X")) {

                    try {
                        JComboBox filterComboBox = (JComboBox) getField("filterComboBox", fc.getUI());
                        Container parent = filterComboBox.getParent();
                        parent.add(cb);
                    } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
                        ex.printStackTrace();
                    }
                }

                fc.showOpenDialog(null);
            }

        });
    }

    private Object getField(String name, Object parent) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Class aClass = parent.getClass();
        Field field = aClass.getDeclaredField(name);
        field.setAccessible(true);
        return field.get(parent);
    }

}

Remember: Just because you can do something, doesn't mean you should!