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));
                }
            }
        }

これは、ルートに少なくとも 6 つ(コメント 1) のディレクトリがある場合にのみ機能します。ファイルを実行し、ルートからディレクトリを数えます。更新ボタンを押すと、6 番目のディレクトリが削除されます。ボタンをもう一度押すと、すでに削除されているノードを削除しようとするため、IllegalArgumentException が発生します。

treeModelでremoveNodeFromParentを呼び出す必要があります。

親からノードを削除するには、これにメッセージを送信します。これにより、nodesWereRemoved にメッセージが送信され、適切なイベントが作成されます。これは、ノードがイベントの作成を処理するため、ノードを削除するための推奨される方法です。

このも参照してください。

ビュー全体を更新する場合は、初期化のときと同じように referh 関数でモデルを再作成するか、モデルを反復して必要に応じて更新する必要がありますが、その場合はモデルの 幅優先トラバーサルをお勧めします。

編集:

「そのため、移動ごとにリフレッシュすると、ファイルが現在あるべき場所にある FS が再び表示されます」。モデルの再初期化から始めましょう。これは、単にDefaultTreeModelクラスの treeModel インスタンスを意味します。

したがって、refresh() メソッドで新しい treeModel インスタンスを作成し、createSystemView() メソッドで行ったように DefaultMutableTreeNode インスタンスで埋めます。

 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 は こちら

ただし、リフレッシュ機能でモデルを更新したい場合は、「リフレッシュ」と呼ばれるアクションのより自然な操作だと思いますが、新しい第 1 レベルのディレクトリを取得してから、このようにツリーをトラバースする必要があります (ルートのすべての子をトラバースしている場合、前述の幅優先トラバーサルになります):

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.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が発生する可能性が最も高いためです。

私が説明した 2 番目のバリアントでは、(更新ロジック 1 の部分で) ループ内からルートの子ノードを削除したくなるかもしれません。そうすると、おそらくConcurrentModificationExceptionが発生します。解決策は別のものを作ることです

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

モデルからノードを削除する代わりに、//update logic part 1 place (ループ内) で、そのリストに入れます。ループの繰り返しが完了したら、そのリストを繰り返し処理し、//更新ロジック パート 2 の場所でモデルからそれらを削除するだけです。

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

前述のように、これによりビュー (JTree) の再描画が自動的にトリガーされるため、ニーズに最適なものを確認してください。

編集^ 2:

リフレッシュ方法の 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 に答える