4

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.

enter image description here

This screenshot is the exactly effect I want.

enter image description here

mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • You will need to create your UI delegate and rebuild the UI from scratch. You will then need to supply a delegate for each platform you want to support, to be sure that it didn't completely screw up on different platforms – MadProgrammer Aug 27 '14 at 23:27
  • Digging around the `BasicFileChooserUI`, it's apparent that it makes no core choices about the UI setup. Digging around `WindowsFileChooserUI`, all the fields/containers you might need are actually `private` or created locally – MadProgrammer Aug 27 '14 at 23:52
  • @MadProgrammer thanks, but it sounds very complicate. I have no idea how to build the UI delegate, so is there any possible simpler way to do that? thanks again :) – Anping Wang Aug 27 '14 at 23:53
  • @AnpingWang Short answer is no, and not one that would be very portable – MadProgrammer Aug 27 '14 at 23:56
  • @MadProgrammer OK, Thanks. If we must use the delegate stuff, how can I do it? can you provide some sample codes? or some external links also be great. Thanks – Anping Wang Aug 28 '14 at 00:24

1 Answers1

10

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...

File Chooser

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...

Mac File Chooser

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!

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • 2
    You REALLY need to understand the perils of this approach, it is a dirty, dirty hack which is made up assumptions about the current design of the interface...be very, very careful – MadProgrammer Aug 28 '14 at 00:28
  • Ya, I see it. Thank you very much. It gives me a good idea how to solve this problem. I will do more research. Thank you very much. – Anping Wang Aug 28 '14 at 00:45
  • 1
    Throws `IndexOutOfBoundsException` on `com.apple.laf.AquaLookAndFeel` when trying to find the `JComboBox` parent; fragility index high. :-) – trashgod Aug 28 '14 at 02:11
  • @trashgod I said "hack", never said "work" ;) - I also said "I didn't like it" ;) – MadProgrammer Aug 28 '14 at 02:13
  • @trashgod I think it only works in the Windows Look and Feel. I think we could Hack for other Look and Feel as well. :) – Anping Wang Aug 28 '14 at 03:54
  • @trashgod It's not the fact that it faild that weirds me out, it's where it failed...wheres the flippen `JTextField`?? Okay, admittedly I was look at an open dialog, so it might be hidden, but it still freaked me out... – MadProgrammer Aug 28 '14 at 03:59
  • +1 [for dirty hack](http://stackoverflow.com/a/6758985/714968), to use its standard, BoxLayout – mKorbel Aug 28 '14 at 05:49
  • @MadProgrammer: The text field is [there](http://hg.openjdk.java.net/macosx-port/macosx-port/jdk/file/b8ebd37e2bd0/src/macosx/classes/com/apple/laf/AquaFileChooserUI.java); it's container, `fTextfieldPanel`, is just not visible! I don't know how `filterComboBox` evades `findAll()`. – trashgod Aug 28 '14 at 10:16
  • 1
    @trashgod It's probably not on the same container – MadProgrammer Aug 28 '14 at 11:05
  • @trashgod The textfield seems to appear at the top of the dialog, where as the type appears at the bottom, they are in different containers. See update for "another" "hack" to get it to work... – MadProgrammer Aug 28 '14 at 11:23
  • @trashgod What, there's others? In that case, you can use the `UIManager.getString` with `FileChooser.filesOfTypeLabelText` to look up the name of the label... – MadProgrammer Aug 28 '14 at 22:05
  • 1
    @trashgod And just because I'm already going to (or are already in) programmer hell, I did a version using reflection... – MadProgrammer Aug 29 '14 at 01:02
  • Masterful! This looked like fun; hell is trying to explain why it's a bad idea. :-) – trashgod Aug 29 '14 at 01:07
  • @trashgod Circumvention of the basic object privileges, how could that possibly be a bad idea ;) – MadProgrammer Aug 29 '14 at 01:11