SHFileOperation を利用してファイルをコピー 移動する - C#

C#でSHFileOperation を利用してファイルのコピーや移動をするコードを紹介します。

ファイルのコピー

UI

下図のUIを作成します。フォームにボタンを一つ配置します。

コード

下記のコードを作成します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace SHFileOperationDemo
{
  public partial class FormMain : Form
  {
    public enum FileFuncFlags : uint
    {
      FO_MOVE = 0x1,
      FO_COPY = 0x2,
      FO_DELETE = 0x3,
      FO_RENAME = 0x4
    }

    [Flags]
    public enum FILEOP_FLAGS : ushort
    {
      FOF_MULTIDESTFILES = 0x1,
      FOF_CONFIRMMOUSE = 0x2,
      /// <summary>
      /// Don't create progress/report
      /// </summary>
      FOF_SILENT = 0x4,
      FOF_RENAMEONCOLLISION = 0x8,
      /// <summary>
      /// Don't prompt the user.
      /// </summary>
      FOF_NOCONFIRMATION = 0x10,
      /// <summary>
      /// Fill in SHFILEOPSTRUCT.hNameMappings.
      /// Must be freed using SHFreeNameMappings
      /// </summary>
      FOF_WANTMAPPINGHANDLE = 0x20,
      FOF_ALLOWUNDO = 0x40,
      /// <summary>
      /// On *.*, do only files
      /// </summary>
      FOF_FILESONLY = 0x80,
      /// <summary>
      /// Don't show names of files
      /// </summary>
      FOF_SIMPLEPROGRESS = 0x100,
      /// <summary>
      /// Don't confirm making any needed dirs
      /// </summary>
      FOF_NOCONFIRMMKDIR = 0x200,
      /// <summary>
      /// Don't put up error UI
      /// </summary>
      FOF_NOERRORUI = 0x400,
      /// <summary>
      /// Dont copy NT file Security Attributes
      /// </summary>
      FOF_NOCOPYSECURITYATTRIBS = 0x800,
      /// <summary>
      /// Don't recurse into directories.
      /// </summary>
      FOF_NORECURSION = 0x1000,
      /// <summary>
      /// Don't operate on connected elements.
      /// </summary>
      FOF_NO_CONNECTED_ELEMENTS = 0x2000,
      /// <summary>
      /// During delete operation, 
      /// warn if nuking instead of recycling (partially overrides FOF_NOCONFIRMATION)
      /// </summary>
      FOF_WANTNUKEWARNING = 0x4000,
      /// <summary>
      /// Treat reparse points as objects, not containers
      /// </summary>
      FOF_NORECURSEREPARSE = 0x8000
    }

    //[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    //If you use the above you may encounter an invalid memory access exception (when using ANSI
    //or see nothing (when using unicode) when you use FOF_SIMPLEPROGRESS flag.
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct SHFILEOPSTRUCT
    {
      public IntPtr hwnd;
      public FileFuncFlags wFunc;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string pFrom;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string pTo;
      public FILEOP_FLAGS fFlags;
      [MarshalAs(UnmanagedType.Bool)]
      public bool fAnyOperationsAborted;
      public IntPtr hNameMappings;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string lpszProgressTitle;
    }

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);


    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      SHFILEOPSTRUCT shfos;
      shfos.hwnd = this.Handle;
      shfos.wFunc = FileFuncFlags.FO_COPY;
      shfos.pFrom = "D:\\photo\\100OLYMP\\*.ORF\0\0";
      shfos.pTo = "c:\\data2\0\0";
      shfos.fFlags = FILEOP_FLAGS.FOF_ALLOWUNDO;
      shfos.fAnyOperationsAborted = true;
      shfos.hNameMappings = IntPtr.Zero;
      shfos.lpszProgressTitle = null;

      SHFileOperation(ref shfos);
    }
  }
}

解説

コードの前半部分は、SHFileOperation で利用される、FileFuncFlags, FILEOP_FLAGS, SHFILEOPSTRUCT の定義になります。

  [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
  static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);
上記のコードがSHFileOperationのインポートのコードです。

    private void button1_Click(object sender, EventArgs e)
    {
      SHFILEOPSTRUCT shfos;
      shfos.hwnd = this.Handle;
      shfos.wFunc = FileFuncFlags.FO_COPY;
      shfos.pFrom = "D:\\photo\\100OLYMP\\*.ORF\0\0";
      shfos.pTo = "c:\\data2\0\0";
      shfos.fFlags = FILEOP_FLAGS.FOF_ALLOWUNDO;
      shfos.fAnyOperationsAborted = true;
      shfos.hNameMappings = IntPtr.Zero;
      shfos.lpszProgressTitle = null;

      SHFileOperation(ref shfos);
    }
ボタンがクリックされたときは、上記のClickイベントハンドラのコードが実行されます。
SHFILEOPSTRUCT 構造体を準備し、ファイルのコピーに関する情報を設定します。hWndにはウィンドウのハンドル、wFuncにはファイル操作の種類、pFromはコピー元ファイル名、pToはコピー先のファイル名、fFlagsはコピーのオプション、fAnyOperationsAborted は操作が中止できるかどうか、hNameMappings はファイル名のマッピング情報、lpszProgressTitleには進行状況ダイアログのタイトル名を設定します。
pFromには、ワイルドカードを指定できます。また、pFrom, pToの末尾は"\0"(ヌルゼロ)を2つ入れます。また、複数のファイルを指定する場合は、ファイル名を"\0"で区切ります。

準備したSHFILEOPSTRUCT 構造体をSHFileOperation()に与えて関数を呼び出すことで、ファイル操作が開始されます。

実行結果

プログラムを実行します。

下図のフォームが表示されます。


フォームのボタンをクリックすると、ファイルのコピーが始まります。ファイル数やファイルのサイズが小さい場合は進行状況ダイアログは表示されません。ファイル数やファイルサイズが大きい場合は、下図の進行状況ダイアログが表示されます。


ダイアログはWindowsのエクスプローラのファイルコピーで表示されるものと同じダイアログです。


ファイルがコピーされることが確認できました。


ファイルの移動

UI

下図のUIを作成します。先ほどのUIにボタンを一つ追加します。

コード

下記のコードを記述します。(先のコードのbutton1のClickイベントハンドラのコードは省略しています。)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace SHFileOperationDemo
{
  public partial class FormMain : Form
  {
    public enum FileFuncFlags : uint
    {
      FO_MOVE = 0x1,
      FO_COPY = 0x2,
      FO_DELETE = 0x3,
      FO_RENAME = 0x4
    }

    [Flags]
    public enum FILEOP_FLAGS : ushort
    {
      FOF_MULTIDESTFILES = 0x1,
      FOF_CONFIRMMOUSE = 0x2,
      /// <summary>
      /// Don't create progress/report
      /// </summary>
      FOF_SILENT = 0x4,
      FOF_RENAMEONCOLLISION = 0x8,
      /// <summary>
      /// Don't prompt the user.
      /// </summary>
      FOF_NOCONFIRMATION = 0x10,
      /// <summary>
      /// Fill in SHFILEOPSTRUCT.hNameMappings.
      /// Must be freed using SHFreeNameMappings
      /// </summary>
      FOF_WANTMAPPINGHANDLE = 0x20,
      FOF_ALLOWUNDO = 0x40,
      /// <summary>
      /// On *.*, do only files
      /// </summary>
      FOF_FILESONLY = 0x80,
      /// <summary>
      /// Don't show names of files
      /// </summary>
      FOF_SIMPLEPROGRESS = 0x100,
      /// <summary>
      /// Don't confirm making any needed dirs
      /// </summary>
      FOF_NOCONFIRMMKDIR = 0x200,
      /// <summary>
      /// Don't put up error UI
      /// </summary>
      FOF_NOERRORUI = 0x400,
      /// <summary>
      /// Dont copy NT file Security Attributes
      /// </summary>
      FOF_NOCOPYSECURITYATTRIBS = 0x800,
      /// <summary>
      /// Don't recurse into directories.
      /// </summary>
      FOF_NORECURSION = 0x1000,
      /// <summary>
      /// Don't operate on connected elements.
      /// </summary>
      FOF_NO_CONNECTED_ELEMENTS = 0x2000,
      /// <summary>
      /// During delete operation, 
      /// warn if nuking instead of recycling (partially overrides FOF_NOCONFIRMATION)
      /// </summary>
      FOF_WANTNUKEWARNING = 0x4000,
      /// <summary>
      /// Treat reparse points as objects, not containers
      /// </summary>
      FOF_NORECURSEREPARSE = 0x8000
    }

    //[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    //If you use the above you may encounter an invalid memory access exception (when using ANSI
    //or see nothing (when using unicode) when you use FOF_SIMPLEPROGRESS flag.
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct SHFILEOPSTRUCT
    {
      public IntPtr hwnd;
      public FileFuncFlags wFunc;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string pFrom;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string pTo;
      public FILEOP_FLAGS fFlags;
      [MarshalAs(UnmanagedType.Bool)]
      public bool fAnyOperationsAborted;
      public IntPtr hNameMappings;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string lpszProgressTitle;
    }

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);


    public FormMain()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      SHFILEOPSTRUCT shfos;
      shfos.hwnd = this.Handle;
      shfos.wFunc = FileFuncFlags.FO_MOVE;
      shfos.pFrom = "D:\\data-1\\*.ORF\0\0";
      shfos.pTo = "D:\\data-2\0\0";
      shfos.fFlags = FILEOP_FLAGS.FOF_ALLOWUNDO;
      shfos.fAnyOperationsAborted = true;
      shfos.hNameMappings = IntPtr.Zero;
      shfos.lpszProgressTitle = null;

      SHFileOperation(ref shfos);
    }
  }
}

解説

コードの前半部分は、SHFileOperation で利用される、FileFuncFlags, FILEOP_FLAGS, SHFILEOPSTRUCT の定義になります。

  [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
  static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);
上記のコードがSHFileOperationのインポートのコードです。

    private void button1_Click(object sender, EventArgs e)
    {
      SHFILEOPSTRUCT shfos;
      shfos.hwnd = this.Handle;
      shfos.wFunc = FileFuncFlags.FO_MOVE;
      shfos.pFrom = "D:\\data-1\\*.ORF\0\0";
      shfos.pTo = "D:\\data-2\0\0";
      shfos.fFlags = FILEOP_FLAGS.FOF_ALLOWUNDO;
      shfos.fAnyOperationsAborted = true;
      shfos.hNameMappings = IntPtr.Zero;
      shfos.lpszProgressTitle = null;

      SHFileOperation(ref shfos);
    }
ボタンがクリックされたときは、上記のClickイベントハンドラのコードが実行されます。
ファイルのコピーと同じく、SHFileOperation関数に、SHFILEOPSTRUCTを与えて呼び出します。ファイル移動の時は、SHFILEOPSTRUCT構造体のwFuncに FileFuncFlags.FO_MOVE を設定します。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。[button2]をクリックします。~


[Button2]をクリックするとファイルが移動します。同じドライブ内でのファイルの移動の場合はすぐに処理が終了するため、進行状況ダイアログボックスは表示されないことが多いです。



別のディスクドライブにファイルを移動する場合は、ファイルの個数やファイルサイズが大きい場合は、下図の進行状況ダイアログが表示される場合があります。



著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2015-06-17
iPentec all rights reserverd.