C#のWPFでファイルマネージャを作る:コピー・切り取り・貼り付け対応

コンピュータ

WPFでファイルマネージャを作成しています。
今回はファイルのコピー・切り取り・貼り付け機能を追加します。

GitHubリポジトリ(最新)
https://github.com/kareteruhito/MyFileManager

ソースコード

追加変更部分

ファイル名:MainWindow.xaml
変更部分のみ抜粋

                    <!-- コンテキストメニュー ここから -->
                    <ListView.ContextMenu>
                        <ContextMenu x:Name="FileContextMenu"
                                     Opened="FileContextMenu_Opening">
                            <MenuItem Header="コピー"
                                Click="Copy_Click"/>
                            <MenuItem Header="切り取り"
                                Click="Cut_Click"/>
                            <Separator/>
                            <MenuItem Header="貼り付け"
                                Click="Paste_Click"/>
                            <Separator/>
                            <MenuItem Header="開く"
                                Click="Menu_Open_Click" />
                            <MenuItem Header="エクスプローラーで開く"
                                Click="Menu_OpenInExplorer_Click" />
                            <Separator/>
                            <MenuItem Header="削除"
                                Click="Menu_Delete_Click" />
                        </ContextMenu>
                    </ListView.ContextMenu>
                    <!-- コンテキストメニュー ここまで -->

ファイル名:MainWindow.FileListView.cs
追加部分

    /*
    * コピー・カット&ペースト処理(アプリ内クリップボード)
    */

    // クリップボードの内容
    enum FileOperation
    {
        None,
        Copy,
        Cut
    }

    // ファイルクリップボード
    class FileClipboard
    {
        public FileOperation Operation { get; set; } = FileOperation.None;
        public List<string> Paths { get; } = new();
    }
    
    // 選択中のファイルを取得
    List<string> GetSelectedPaths()
    {
        return FileListView.SelectedItems
            .Cast<FileItem>()
            .Select(x => x.FullPath)
            .ToList();
    }
    private FileClipboard _clipboard = new FileClipboard();
    // 「コピー」メニュー項目クリック
    void Copy_Click(object sender, RoutedEventArgs e)
    {
        SetClipboard(FileOperation.Copy);
    }
    // 「切り取り」メニュー項目クリック
    void Cut_Click(object sender, RoutedEventArgs e)
    {
        SetClipboard(FileOperation.Cut);
    }
    // クリップボードに選択中のファイルをセット
    void SetClipboard(FileOperation op)
    {
        var paths = GetSelectedPaths();
        if (paths.Count == 0) return;

        _clipboard.Paths.Clear();
        _clipboard.Paths.AddRange(paths);
        _clipboard.Operation = op;
    }
    // 「貼り付け」メニュー項目クリック
    void Paste_Click(object sender, RoutedEventArgs e)
    {
        if (_clipboard.Operation == FileOperation.None) return;

        string destDir = _currentDirectory; // カレントディレクトリ

        foreach (var src in _clipboard.Paths)
        {
            string name = Path.GetFileName(src);
            string dest = Path.Combine(destDir, name);

            if (File.Exists(dest) || Directory.Exists(dest))
            {
                // 同名が存在する場合はスキップ
                continue;
            }            

            if (_clipboard.Operation == FileOperation.Copy)
            {
                if (Directory.Exists(src))
                {
                    // ディレクトリ
                    // 未実装
                    continue;
                }
                else if (File.Exists(src))
                {
                    // ファイル
                    File.Copy(src, dest, overwrite: false);
                }                
            }
            else if (_clipboard.Operation == FileOperation.Cut)
            {
                if (Directory.Exists(src))
                {
                    // ディレクトリ
                    Directory.Move(src, dest);
                }
                else if (File.Exists(src))
                {
                    // ファイル
                    File.Move(src, dest);
                }                
            }
        }

        _clipboard.Paths.Clear();
        _clipboard.Operation = FileOperation.None;

        // 既存の一覧更新処理
        SetFileListViewDirectory(_currentDirectory);
    }
    // コンテキストメニュー表示前処理
    void FileContextMenu_Opening(object sender, RoutedEventArgs e)
    {
        var menu = (ContextMenu)sender;

        var copy = (MenuItem)menu.Items[0];
        var cut  = (MenuItem)menu.Items[1];
        var paste = (MenuItem)menu.Items[3];

        // メニュー項目の有効/無効設定

        // ディレクトリはコピー不可とする

        var paths = FileListView.SelectedItems
            .Cast<FileItem>()
            .Select(x => x.FullPath)
            .ToList();

        bool hasFile = false;
        bool hasDirectory = false;

        foreach (var path in paths)
        {
            if (File.Exists(path))
                hasFile = true;
            else if (Directory.Exists(path))
                hasDirectory = true;
        }

        copy.IsEnabled =
            paths.Count > 0 &&
            hasFile &&
            !hasDirectory;

        cut.IsEnabled =
            paths.Count > 0;

        paste.IsEnabled =
            _clipboard.Operation != FileOperation.None &&
            paths.Count == 0;
    }
    // 右クリックで項目を選択状態にする
    void FileListView_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
    {
        DependencyObject? current = e.OriginalSource as DependencyObject;

        while (current != null && current is not ListViewItem)
        {
            current = VisualTreeHelper.GetParent(current);
        }

        if (current is ListViewItem item)
        {
            item.IsSelected = true;
        }
    }

説明

xamlではFileListView上のコンテキストメニューにコピー・切り取り・貼り付けの項目追加をしています。

C#側で、メニューから呼び出される機能を実装します。

便宜上クリップボードという名称を使っていますが、本物クリップボードではないので、

他のアプリケーションに貼り付けは出来ません。

また、実際試してみて、

File.Copy()やFile.Move()で同名ファイルの場合の処理を書いていませんでした。

例外が発生していたので、

とりあえず、同名が存在する場合はスキップする回避をしています。

このような想定外な操作が、まだまだ沢山存在していると思われます。

操作はエクスプローラ準拠としたいところですが、開発者の力量的に難しいので、

操作してみて問題が発生したら都度対応になります。

コメント