0

我正在尝试开发一个小型应用程序,该应用程序使用 gui 从系统上的任何位置移动文件。我有可以移动文件的代码,并且在选择和按下按钮时确实会移动,但我不知道如何刷新文件系统查看器以反映更改。我必须设置系统查看器的代码如下:

public class FileMover  {
//Start of Global Variables
private JTree tree;
private DefaultTreeModel treeModel;
private FileSystemView fileSystemView;
protected File currentFile;
protected LinkedList fileLocations;
protected JTree movedTree;
protected JPanel areaLeft;
protected JPanel areaRight;
protected JPanel areaMiddle;
protected final JFrame openFrame;
//end of global variables.

//Constructor for FileMover
public FileMover()
{
    openFrame = new JFrame("File Mover");
    createFileMover();

}
public void createFileMover(){

    Container contentPane = this.openFrame.getContentPane();
    fileLocations = new LinkedList();


    contentPane.setLayout(new BorderLayout());
    areaLeft = new JPanel();
    areaRight = new JPanel();
    areaMiddle = new JPanel();

    contentPane.add(areaLeft, BorderLayout.WEST);
    contentPane.add(areaRight, BorderLayout.EAST);
    contentPane.add(areaMiddle, BorderLayout.CENTER);


    areaLeft.add(createSystemView());
    movedTree = new JTree(fileLocations.toArray());
    JScrollPane movedPane = new JScrollPane(movedTree);
    JButton moveRightButton = new JButton("->");
    JButton moveLeftButton = new JButton("<-");
    JButton refresh = new JButton("Refresh");

    areaMiddle.setLayout(new GridLayout(1,2));
    areaMiddle.add(moveRightButton);
    areaMiddle.add(refresh);
    areaMiddle.add(moveLeftButton);

    //actualy move the file

    moveRightButton.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){

            System.out.println("Moving file: "+ currentFile.getName());
            fileLocations.add(currentFile);
            try {
                //move the file to the correct location.
                moveFile(currentFile);


            } catch (IOException ex) {
                Logger.getLogger(FileMover.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println(fileLocations.getFirst().toString());
        }
    });

    //refresh the gui
    refresh.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            refresh();
        }
    });

    //finish setting up the frame
    openFrame.setSize(1280, 768);
    openFrame.setLocationRelativeTo(null);
    openFrame.setDefaultCloseOperation(3);
    openFrame.setResizable(false);
    openFrame.pack();
    openFrame.setVisible(true);


}

/** Add the files that are contained within the directory of this node.
*/
private void showChildren(final DefaultMutableTreeNode node) {
    tree.setEnabled(false);


    SwingWorker<Void, File> worker = new SwingWorker<Void, File>() {
        @Override
        public Void doInBackground() {
            File file = (File) node.getUserObject();
            if (file.isDirectory()) {
                File[] files = fileSystemView.getFiles(file, true); //!!
                if (node.isLeaf()) {
                    for (File child : files) {

                            publish(child);

                    }
                }

            }
            return null;
        }

        @Override
        protected void process(List<File> chunks) {
            for (File child : chunks) {
                node.add(new DefaultMutableTreeNode(child));
            }
        }

        @Override
        protected void done() {

            tree.setEnabled(true);
        }
    };
    worker.execute();
}

/** Update the File details view with the details of this File. */
private void setFileDetails(File file) {
    System.out.println("Path: "+ file.getPath());
    System.out.println("Name: "+ fileSystemView.getSystemDisplayName(file));


}
private void refresh(){
    //refresh the tree here

}
private JScrollPane createSystemView(){
    //file syatem hierarchy
    fileSystemView = FileSystemView.getFileSystemView();    

    // the File tree
        DefaultMutableTreeNode root = new DefaultMutableTreeNode();
        treeModel = new DefaultTreeModel(root);

        TreeSelectionListener treeSelectionListener = new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent tse){
                DefaultMutableTreeNode node =
                    (DefaultMutableTreeNode)tse.getPath().getLastPathComponent();
                showChildren(node);
                setFileDetails((File)node.getUserObject());
                currentFile = (File)node.getUserObject();
            }
        };

        // show the file system roots.
        File[] roots = fileSystemView.getRoots();
        for (File fileSystemRoot : roots) {
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(fileSystemRoot);
            root.add( node );
            File[] files = fileSystemView.getFiles(fileSystemRoot, true);
            for (File file : files) {
                if (file.isDirectory()) {
                    node.add(new DefaultMutableTreeNode(file));
                }
            }

        }

        tree = new JTree(treeModel);
        tree.setRootVisible(false);
        tree.addTreeSelectionListener(treeSelectionListener);
        tree.setCellRenderer(new FileTreeCellRenderer());
        tree.expandRow(0);
        JScrollPane treeScroll = new JScrollPane(tree);
        tree.setVisibleRowCount(15);

        Dimension preferredSize = treeScroll.getPreferredSize();
        Dimension widePreferred = new Dimension(
            200,
            (int)preferredSize.getHeight());
        treeScroll.setPreferredSize( widePreferred );

        return treeScroll;
}

向左移动按钮和向右区域尚未完成,但我需要的是当我在树中选择一个节点并单击向右箭头按钮时,节点反映的文件/文件夹由我的 moveFile 代码在内部移动,并且有效。但是该更改并未反映在树中,因此如何在树中显示此更改,即刷新树以显示文件系统的当前状态?

我试过 treeModel.reload(); 但这似乎不起作用并引发空指针异常。

我试过了 :

areaLeft.removeAll();

areaLeft.add(createSystemView());

认为它可以通过重新创建系统视图来刷新它,但这似乎没有任何作用。

在这里的帮助将不胜感激!

编辑:以下是文件树渲染器的请求代码:

/** A TreeCellRenderer for a File. */
class FileTreeCellRenderer extends DefaultTreeCellRenderer {

private static final long serialVersionUID = -7799441088157759804L;

private FileSystemView fileSystemView;

private JLabel label;

FileTreeCellRenderer() {
    label = new JLabel();
    label.setOpaque(true);
    fileSystemView = FileSystemView.getFileSystemView();
}

@Override
public Component getTreeCellRendererComponent(
    JTree tree,
    Object value,
    boolean selected,
    boolean expanded,
    boolean leaf,
    int row,
    boolean hasFocus) {

    DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
    File file = (File)node.getUserObject();
    label.setIcon(fileSystemView.getSystemIcon(file));
    label.setText(fileSystemView.getSystemDisplayName(file));
    label.setToolTipText(file.getPath());

    if (selected) {
        label.setBackground(backgroundSelectionColor);
        label.setForeground(textSelectionColor);
    } else {
        label.setBackground(backgroundNonSelectionColor);
        label.setForeground(textNonSelectionColor);
    }

    return label;
}
}
4

1 回答 1

1

因为从您的代码看起来您​​知道自己在做什么,所以我将仅展示仅在您第一次按下刷新按钮时才有效的基本示例:

private DefaultMutableTreeNode someNode;
private void refresh(){
    System.out.println(someNode);
    treeModel.removeNodeFromParent(someNode);
}

并像这样重写 createSystemView() 的一部分:

        int  cnt = 0;
        for (File file : files) {
            if (file.isDirectory()) {                    
                if ((cnt++) == 5) {                        //1
                    System.out.println(file.getPath());
                    node.add(someNode = new DefaultMutableTreeNode(file));
                }
                else {
                    node.add(new DefaultMutableTreeNode(file));
                }
            }
        }

这仅在您的根目录下至少有六个(注释 1)目录时才有效。运行该文件,从根目录计算目录 - 当您按下刷新按钮时,它将删除第六个目录。如果再次按下按钮,它将尝试删除已删除的节点,因此您将收到 IllegalArgumentException。

您需要在treeModel上调用removeNodeFromParent

消息此以从其父节点中删除节点。这将消息 nodesWereRemoved 以创建适当的事件。这是删除节点的首选方式,因为它会为您处理事件创建。

另请参阅此示例

如果您想刷新整个视图,您应该像在初始化时那样在 referh 函数中重新创建模型,或者只是迭代模型并根据需要进行更新——但我建议 在这种情况下对模型进行广度优先遍历

编辑:

“因此,每次移动刷新后,FS 都会再次显示它们现在应该位于的文件”。让我们从重新初始化模型开始,我只指DefaultTreeModel类的 treeModel 实例。

因此,在 refresh() 方法中创建一个新的 treeModel 实例,并用 DefaultMutableTreeNode 实例填充它,就像在 createSystemView() 方法中所做的那样:

 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
 treeModel = new DefaultTreeModel(root);
 File[] roots = fileSystemView.getRoots();
    for (File fileSystemRoot : roots) {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(fileSystemRoot);
        root.add( node );
        File[] files = fileSystemView.getFiles(fileSystemRoot, true);
        for (File file : files) {
            if (file.isDirectory()) {
                node.add(new DefaultMutableTreeNode(file));
            }
        }
    }

我们已经准备好了模型,现在我们需要将它设置到树上,我相信仅仅是:

tree.setModel(treeModel);

应该足够了,请注意您不需要添加侦听器,因为 setModel 方法将侦听器从旧模型重新附加到新模型,并且还通知 JTree 视图相应地重绘自身 - 我检查了源代码。如果它不会重绘(我对此表示怀疑,但我没有对此进行测试)强制它在下一行中,如下所示:

treeModel.reload();

此处为 reload() 方法的 APIdoc

但是,如果您想在刷新函数中更新模型,我认为这对于称为“刷新”的操作会更自然,您需要获取新的第一级目录,然后像这样遍历树(因为我们'正在遍历所有根的孩子,这将是提到的广度优先遍历):

int firstLevelCount = treeModel.getChildCount(root);
DefaultMutableTreeNode child;
for (int i=0; i < firstLevelCount; i++) {
   child = treeModel.getChild(root, index); 
   // update logic part 1
}
// update logic part 2
treeModel.reload();

您将需要DefaultMutableTreeNode.getUserObject()方法,该方法将返回该树节点所代表的文件。

显然,您需要从模型中删除不在新文件数组中的所有节点,并且对于模型中没有对应节点的所有文件,您需要将它们添加到模型中.

我建议不要使用 files 数组而是 list (这样你就可以从List.contains()方法中受益),例如

List<File> files = new ArrayList<File>(Arrays.asList(FileSystemView.getFileSystemView().getRoots()));

另外,像这样重写渲染器的一部分:

if (file != null) {
    label.setIcon(fileSystemView.getSystemIcon(file));
    label.setText(fileSystemView.getSystemDisplayName(file));
    label.setToolTipText(file.getPath());
}

因为您视图中的根节点没有关联文件,所以您很可能会在某个时候更新模型时获得NPE 。

在我描述的第二个变体中,您可能会想从循环内部(在更新逻辑 1 部分)删除根子节点 - 如果这样做,您很可能会得到一个ConcurrentModificationException。解决方案是制作另一个

List<DefaultMutableTreeNode> toBeRemoved = new ArrayList<DefaultMutableTreeNode>();

并在 //update logic part 1 位置(在循环中)而不是从模型中删除节点,而是将其放入该列表中。当您完成循环迭代后,您只需迭代该列表并将它们从模型中删除它们 //update logic part 2 地方,例如

for (DefaultMutableTreeNode node : toBeRemoved) {
    treeModel.removeNodeFromParent(node);
}

如前所述,这将自动触发视图(JTree)重绘,因此请查看最适合您需要的内容。

编辑^2:

关于刷新方法的第二个变体,您已经有了

private DefaultTreeModel treeModel;

作为全局变量,您可以像这样获得树的根(getRoot()

DefaultMutableTreeNode root = (DefaultMutableTreeNode)treeModel.getRoot();

正如你已经做的那样,你获得了文件系统的当前状态,如下所示:

FileSystemView.getFileSystemView().getRoots();

这些是您编写所描述的 refresh() 方法所需的所有变量。

关于移动(带箭头的按钮 (<- , ->))操作,从您的 treeSelectionListener 内部将此变量设为全局:

DefaultMutableTreeNode node = (DefaultMutableTreeNode)tse.getPath().getLastPathComponent();

使用这个变量(我们称之为 selectedNode),您可以使用 DefaultTreeModel 方法:

请注意,这两种方法都会触发 JTree 重绘。这应该足以实现您描述的操作。此外,您可以像这样重写 moveButton actionListener 方法:

moveRightButton.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e){
        if (selectedNode != null) {
             //all the move-code-refresh-tre-view-here
             selectedNode = null;
        }
    }
});

selectedNode 将仅由 treeSelectionListener 设置,因此通过这种方式,您可以确保文件移动操作仅在树视图中实际选择文件时才会发生。

于 2012-10-13T22:23:32.283 回答