保存JFileChooser的状态

3
一个用户要求我在应用程序重新启动时保留JFileChooser的状态。具体而言,他要求我保留详细信息/列表视图类型选择的状态。两个相关问题: 如何在详细视图中启动JFileChooser? 按日期排序的文件启动JFileChooser 这两个都显示了使用特定默认行为启动JFileChooser的方法。缺失的部分是一种确定用户何时处于活动状态(视图类型,排序顺序)的方法,当JFileChooser窗口关闭时,以便稍后保存和恢复。有什么想法吗?

1
它是可序列化的。您可以根据需要对其进行序列化和反序列化。(虽然我从未尝试过。) - Gábor Bakos
谢谢。我刚刚根据你的建议尝试了序列化/反序列化。我感兴趣的信息并没有包含在序列化数据中,所以这并不能作为解决方案。 - danBhentschel
2个回答

3
你可以使用Properties API或Preferences API来保存/恢复用户数据。
  1. 在启动时,您将读取用户数据并设置文件选择器属性。
  2. 要监听用户对视图类型的更改,可以向文件选择器添加PropertyChangeListener并侦听viewType事件。然后,您将使用新值更新用户数据。
您可以向RowSorter添加RowSorterListener以侦听排序顺序的更改。然后,您需要保存排序顺序。我不知道存储排序数据的最佳方法。

谢谢,但似乎不起作用。如果我为“viewType”向JFileChooser添加一个PropertyChangeListener,当在列表和详细信息之间切换时它从来不会触发。我尝试添加一个没有指定要监听的属性的PropertyChangeListener,但在创建/显示窗口时会触发几个事件,但是在打开窗口时,当更改视图类型时根本不会触发任何事件。我的做法有问题吗? - danBhentschel
好的。所以我刚刚发现我需要将监听器添加到FilePane而不是JFileChooser。我认为这样会起作用。 - danBhentschel
@danBhentschel - "我刚弄明白需要将监听器添加到FilePane而不是其他地方" - 知道了很好。那么你如何访问文件选择器的FilePane - camickr
我根据您的建议和其他信息添加了我的实现作为答案。感谢您的帮助。请随时指出我所做的任何愚蠢之处。FilePane是“受限制”的非API Swing代码的一部分。通常情况下,我会感到不舒服使用这个代码,但在这种情况下,JVM与我的应用程序捆绑在一起,因此我相对于实现变化是相对受保护的。 - danBhentschel

1
基于反馈,我创建了以下类。我相信这提供了我正在寻找的所有功能。此类依赖于this SwingUtils类
您还需要更新访问限制规则(至少在Eclipse中),以允许访问sun/swing/FilePane,如下所述: 访问限制:由于对所需库..\jre\lib\rt.jar的限制而无法访问 编辑:清理资源泄漏
长假期后回到工作岗位后,我意识到我最初提供的代码可能会出现泄漏JFileChooser实例的情况,因此我改进了它,提供了一个可在try-with-resources语句中使用的AutoCloseable实体。对于这种变动造成的不便,我深感抱歉。
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;

import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.swing.FilePane;
import darrylbu.util.SwingUtils;

@SuppressWarnings("restriction")
public class JFileChooserPersisterFactory {
    private static final String VIEW_TYPE_LIST = "viewTypeList"; //$NON-NLS-1$
    private static final String VIEW_TYPE_DETAILS = "viewTypeDetails"; //$NON-NLS-1$
    private static final String CHOOSER_CLOSING_PROPERTY = "JFileChooserDialogIsClosingProperty"; //$NON-NLS-1$
    private static final String VIEW_TYPE_PROPERTY = "viewType"; //$NON-NLS-1$
    private static final String IS_DETAILS = "isDetails"; //$NON-NLS-1$
    private static final String SORT_ORDER = "sortOrder"; //$NON-NLS-1$

    private JFileChooserPersisterFactory() {
    }

    public static JFileChooserPersister createJFileChooserPersister() {
        JFileChooserPersisterImpl persister = new JFileChooserPersisterImpl();
        persister.init();
        return persister;
    }

    public interface JFileChooserPersister extends AutoCloseable {
        JFileChooser getJFileChooser();

        @Override
        void close();
    }

    private static class JFileChooserPersisterImpl implements JFileChooserPersister {
        private final Logger logger = LoggerFactory.getLogger(getClass());
        private final Preferences persistentPrefs = Preferences.userNodeForPackage(getClass());

        private final JFileChooser chooser;
        private boolean isDetails;
        private OnChooserClosing chooserClosingListener;
        private FilePane filePane;
        private OnViewTypeChanged viewTypeChangedListener;

        public JFileChooserPersisterImpl() {
            chooser = new JFileChooser();
        }

        public void init() {
            restoreSettings();
            registerForViewTypeChangeEvents();
            chooserClosingListener = new OnChooserClosing();
            chooser.addPropertyChangeListener(CHOOSER_CLOSING_PROPERTY, chooserClosingListener);
        }

        @Override
        public JFileChooser getJFileChooser() {
            return chooser;
        }

        private void persistSettings() {
            persistentPrefs.putBoolean(IS_DETAILS, isDetails);
            if (isDetails) persistSortOrder();
        }

        private void persistSortOrder() {
            byte[] serializedSortOrder = serializeSortOrder();
            if (serializedSortOrder != null)
                persistentPrefs.putByteArray(SORT_ORDER, serializedSortOrder);
        }

        private byte[] serializeSortOrder() {
            List<? extends SortKey> keys = getRowSorter().getSortKeys();
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            try (ObjectOutputStream out = new ObjectOutputStream(byteStream)) {
                out.writeObject(new SortOrderInfo(keys));
                return byteStream.toByteArray();
            } catch (IOException e) {
                logger.error("Could not serialize JFileChooser row sort order.", e); //$NON-NLS-1$
            }
            return null;
        }

        private void restoreSettings() {
            isDetails = persistentPrefs.getBoolean(IS_DETAILS, false);
            if (isDetails) {
                setToDetailsView();
                applyInitialSortOrder();
            } else {
                setToListView();
            }
        }

        private void setToDetailsView() {
            Action details = chooser.getActionMap().get(VIEW_TYPE_DETAILS);
            details.actionPerformed(null);
        }

        private void setToListView() {
            Action details = chooser.getActionMap().get(VIEW_TYPE_LIST);
            details.actionPerformed(null);
        }

        private void applyInitialSortOrder() {
            byte[] serializedSortOrder = persistentPrefs.getByteArray(SORT_ORDER, null);
            if (serializedSortOrder == null) return;
            ByteArrayInputStream byteStream = new ByteArrayInputStream(serializedSortOrder);
            try (ObjectInputStream in = new ObjectInputStream(byteStream)) {
                setSortInfo((SortOrderInfo) in.readObject());

            } catch (IOException | ClassNotFoundException e) {
                logger.error("Could not deserialize JFileChooser row sort order.", e); //$NON-NLS-1$
            }
        }

        private void setSortInfo(SortOrderInfo info) {
            info.setSortOrder(getRowSorter());
        }

        private RowSorter<?> getRowSorter() {
            JTable table = SwingUtils.getDescendantsOfType(JTable.class, chooser).get(0);
            RowSorter<?> rowSorter = table.getRowSorter();
            return rowSorter;
        }

        private void registerForViewTypeChangeEvents() {
            filePane = SwingUtils.getDescendantsOfType(FilePane.class, chooser).get(0);
            viewTypeChangedListener = new OnViewTypeChanged();
            filePane.addPropertyChangeListener(VIEW_TYPE_PROPERTY, viewTypeChangedListener);
        }

        private final class OnChooserClosing implements PropertyChangeListener {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                persistSettings();
            }
        }

        private class OnViewTypeChanged implements PropertyChangeListener {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                isDetails = ((int) evt.getNewValue()) == FilePane.VIEWTYPE_DETAILS;
            }
        }

        public static class SortOrderInfo implements Serializable {
            private static final long serialVersionUID = -5393878644049680645L;
            private final List<ColumnSortInfo> keyInfo = new ArrayList<>();

            public SortOrderInfo(List<? extends SortKey> keys) {
                for (SortKey sortKey : keys) {
                    keyInfo.add(new ColumnSortInfo(sortKey));
                }
            }

            public void setSortOrder(RowSorter<?> rowSorter) {
                rowSorter.setSortKeys(makeSortKeys());
            }

            private List<SortKey> makeSortKeys() {
                List<SortKey> keys = new ArrayList<>();
                for (ColumnSortInfo info : keyInfo) {
                    keys.add(info.makeSortKey());
                }
                return keys;
            }

            public static class ColumnSortInfo implements Serializable {
                private static final long serialVersionUID = 5406885180955729893L;
                private final SortOrder sortOrder;
                private final int column;

                public ColumnSortInfo(SortKey sortKey) {
                    column = sortKey.getColumn();
                    sortOrder = sortKey.getSortOrder();
                }

                public SortKey makeSortKey() {
                    return new SortKey(column, sortOrder);
                }
            }
        }

        @Override
        public void close() {
            chooser.removePropertyChangeListener(CHOOSER_CLOSING_PROPERTY, chooserClosingListener);
            filePane.removePropertyChangeListener(VIEW_TYPE_PROPERTY, viewTypeChangedListener);
        }
    }
}

1+ SwingUtils可能会派上用场。我仍然感到惊讶的是,FilePane类生成的属性更改事件对JFileChooser类不可用。我们不应该使用SwingUtils这样的类来访问这些事件。看起来像是一个文件选择器设计上的漏洞。 - camickr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接