Click here to Skip to main content
15,887,596 members
Articles / Programming Languages / C#

How to copy files in C# with a customizable progress indicator and or progress bar

Rate me:
Please Sign up or sign in to vote.
4.46/5 (30 votes)
22 May 2009CPOL 213.6K   14.1K   82   50
This article shows you how to construct a class to copy files and folder trees with an optional progress indicator.

Copy Files

Introduction

After reading about lots of ways to copy files with a progress indicator in C# and finding them all to be a little... long winded, I decided to write this article to help my fellow coders. It's a simple way to copy files and folders with a customizable progress bar/indicator.

Background

The class uses the Windows Kernal32 CopyFileEx function to do the copying of files. This being said, it does use one pointer, and so needs to be compiled with the /unsafe parameter (or simply tick the 'allow unsafe code' option in the project's properties)

You can create your own way to display the progress of the copy. To do this, simply implement the ICopyFileDiag interface and pass your class to the copy method.

C#
//The interface for the Dialog the CopyFiles class uses.
public interface ICopyFilesDiag
{
    //needed to sync the CopyClass update events with the dialog thread
    System.ComponentModel.ISynchronizeInvoke SynchronizationObject { get; set; }

    //This event should fire when you want to cancel the copy
    event CopyFiles.DEL_cancelCopy EN_cancelCopy;

    //This is how the CopyClass will send your dialog information about
    //the transfer
    void update(Int32 totalFiles, Int32 copiedFiles, Int64 totalBytes, 
                Int64 copiedBytes, String currentFilename);
    void Show();
    void Hide();
}

Using the code

The guts of the code is in the CopyFiles class; however, once this is referenced in your application, it's simply a case of specifying a list of files for a directory to copy and the location to copy it to.

You then choose to do the copy asynchronously or synchronously:

C#
private void But_CopyFiles_Click(object sender, EventArgs e)
{

    //This the list of random files we want 
    //to copy into a single new directory
    List<String> TempFiles = new List<String>();
    TempFiles.Add("C:\\Copy Test Folder\\test.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Bob.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test4\\Bob.Trev.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test4\\test.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test3\\Bob.Trev.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test3\\test.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test3\\B.o.b.Trev..txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test2\\Bob.Trev.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test2\\test.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test1\\test.txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test1\\B.o.b.Trev..txt");
    TempFiles.Add("C:\\Copy Test Folder\\Test1\\Bob.Trev.txt");

    //I would recommend you put at least one large file in this folder
    //to see the progress bar in action.
    CopyFiles.CopyFiles Temp = new CopyFiles.CopyFiles(TempFiles, "C:\\Test");
    
    //Uncomment the next line to copy the file tree.
    //CopyFiles.CopyFiles Temp = 
    // new CopyFiles.CopyFiles("C:\\Copy Test Folder", "C:\\Test");

    //Create the default Copy Files Dialog window from our Copy Files assembly
    //and sync it with our main/current thread
    CopyFiles.DIA_CopyFiles TempDiag = new CopyFiles.DIA_CopyFiles();
    TempDiag.SynchronizationObject = this;

    //Copy the files anysncrinsuly
    Temp.CopyAsync(TempDiag);

    //Uncomment this line to do a synchronous copy.
    ///Temp.Copy();

}

This is an example of how the ICopyFileDiag can be implemented:

C#
public partial class DIA_CopyFiles : Form, ICopyFilesDiag
{
    // Properties
    public System.ComponentModel.ISynchronizeInvoke 
           SynchronizationObject { get; set; }
    // Constructors
    public DIA_CopyFiles()
    {
        InitializeComponent();
    }
    // Methods
    public void update(Int32 totalFiles, Int32 copiedFiles, 
           Int64 totalBytes, Int64 copiedBytes, String currentFilename)
    {
        Prog_TotalFiles.Maximum = totalFiles;
        Prog_TotalFiles.Value = copiedFiles;
        Prog_CurrentFile.Maximum = 100;
        if (totalBytes != 0)
        {
            Prog_CurrentFile.Value = Convert.ToInt32((100f / 
                  (totalBytes / 1024f)) * (copiedBytes / 1024f));
        }
        Lab_TotalFiles.Text = "Total files (" + copiedFiles + 
                  "/" + totalFiles + ")";
        Lab_CurrentFile.Text = currentFilename;
    }
    private void But_Cancel_Click(object sender, EventArgs e)
    {
        RaiseCancel();
    }
    private void DIA_CopyFiles_Closed(object sender, System.EventArgs e)
    {
        RaiseCancel();
    }
    private void RaiseCancel()
    {
        if (EN_cancelCopy != null)
        {
            EN_cancelCopy();
        }
    }
    //Events
    public event CopyFiles.DEL_cancelCopy EN_cancelCopy;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionawesome small program Pin
Member 1576897417-Feb-23 5:23
Member 1576897417-Feb-23 5:23 
GeneralGreat copying module. Copy folder works with small adaptation Pin
Aleksandar Ormanović17-Jan-20 1:26
Aleksandar Ormanović17-Jan-20 1:26 
Question2 questions Pin
gutihz145-Apr-17 5:59
gutihz145-Apr-17 5:59 
Questionshow copying progress as dialog Pin
ayman_sharkawy23-Jul-16 23:32
ayman_sharkawy23-Jul-16 23:32 
BugSome doubtful question. Pin
ss9o9o9o10-Jun-16 21:17
ss9o9o9o10-Jun-16 21:17 
QuestionHow disable form1 until async process is finished ? Pin
maxdb7114-Jan-16 10:09
maxdb7114-Jan-16 10:09 
Questionhow to do this progress thing in c# console Pin
Palash Sachan15-Nov-15 6:24
Palash Sachan15-Nov-15 6:24 
QuestionHow to copy complete directory? Pin
Member 110410572-Sep-14 0:53
Member 110410572-Sep-14 0:53 
QuestionCopy and Rename? Pin
ElSEEDY3-Jun-14 4:33
ElSEEDY3-Jun-14 4:33 
QuestionUser Control copyComplete Event Pin
Angel Blandón15-Nov-12 6:33
Angel Blandón15-Nov-12 6:33 
QuestionUpdated Code Pin
jtitley3-Oct-11 18:50
jtitley3-Oct-11 18:50 
You actually don't need to make the code unsafe. Ref will suffice. For myself the code was more than I required so I made simpler. Here is a basic version and example on the same form. It raises events on the main form to show progress.


C#
using System;
namespace Controls.Common
{

    public class FileCopiedEventArgs
    {
        public FileCopiedEventArgs(string file) { File = file; }

        public FileCopiedEventArgs(string file, Exception exception)
        {
            File = file;
            Error = exception;
        }

        public String File {get; private set;} // readonly  
        public Exception Error { get; private set; }
        public bool Cancel;
    }

    public class FileCopyCompleteEventArgs
    {
        public FileCopyCompleteEventArgs(bool cancel, Exception lastError) { Cancel = cancel; LastError = lastError;}
        public bool Cancel { get; private set; }
        public Exception LastError { get; private set; }
    }

    public class CopyFiles
    {

        // Variables
        private List<String> files = new List<String>();
        private readonly List<String> newFilenames = new List<String>();
        private Int32 _totalFiles = 0;
        private Int32 _totalFilesCopied = 0;
        private readonly String _destinationDir = "";
        private readonly String _sourceDir = "";
        private String _currentFilename;
        private Boolean _cancel = false;
        private Thread _copyThread;
        private const int ERROR_REQUEST_ABORTED = 1235;

        // Events
        public event FileCopyCompleteEventHandler CopyComplete;
        public event FileCopiedEventHandler FileCopied;

        // Delegates
        public delegate void FileCopiedEventHandler(object sender, FileCopiedEventArgs e);
        public delegate void FileCopyCompleteEventHandler(object sender, FileCopyCompleteEventArgs e);
        
        // Constructors
        public CopyFiles(String source, String destination)
        {
            //As the directory tree might be large we work out the
            //files in the threaded call Copyfiles()
            _sourceDir = source;
            _destinationDir = destination;
        }
        public CopyFiles(List<String> sourceFiles, String destination)
        {
            files = sourceFiles;
            _totalFiles = files.Count;
            _destinationDir = destination;
        }
        public CopyFiles(List<String> sourceFiles, List<String> destFiles)
        {
            if (sourceFiles.Count != destFiles.Count) throw new Exception("Array Length Mismatch");

            files = sourceFiles;
            _totalFiles = files.Count;
            newFilenames = destFiles;
        }

        public delegate void CopyProgressHandlerDelegate(Int32 totalFiles, Int32 copiedFiles, Int64 totalBytes, Int64 copiedBytes, String currentFilename);
        public event CopyProgressHandlerDelegate ProgressEvent;

        // Methods
        private List<String> GetFiles(String sourceDir)
        {

            // Variables
            String[] fileEntries;
            String[] subdirEntries;

            //Add root files in this DIR to the list
            fileEntries = System.IO.Directory.GetFiles(sourceDir);
            List<String> foundFiles = fileEntries.ToList();

            //Loop the DIR's in the current DIR
            subdirEntries = System.IO.Directory.GetDirectories(sourceDir);
            foreach (string subdir in subdirEntries)
            {

                //Dont open Folder Redirects as this can end up in an infinate loop
                if ((System.IO.File.GetAttributes(subdir) &
                     System.IO.FileAttributes.ReparsePoint) !=
                     System.IO.FileAttributes.ReparsePoint)
                {
                    //Run recursivly to follow this DIR tree 
                    //adding all the files along the way
                    foundFiles.AddRange(GetFiles(subdir));
                }

            }

            return foundFiles;
        }

        private NativeMethods.CopyProgressResult CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 streamByteTrans, UInt32 dwStreamNumber, NativeMethods.CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
        {
            if (ProgressEvent != null)
                ProgressEvent(_totalFiles, _totalFilesCopied, total, transferred, _currentFilename);

            return _cancel ? NativeMethods.CopyProgressResult.PROGRESS_CANCEL : NativeMethods.CopyProgressResult.PROGRESS_CONTINUE;
        }

        public void CancelCopy()
        {
            _cancel = true;            
        }

        private void Copyfiles()
        {
            try
            {
                Exception lastError = null;
                Int32 index = 0;

                //If we have been a sourceDIR then find all the files to copy
                if (!String.IsNullOrEmpty(_sourceDir))
                    files = GetFiles(_sourceDir);
                _totalFiles = files.Count;

                //Loop each file and copy it.
                foreach (String filename in files.ToArray())
                {
                    String tempFilepath;

                    //If we have a source directory, strip that off the filename
                    if (!String.IsNullOrEmpty(_sourceDir))
                    {
                        tempFilepath = filename;
                        tempFilepath = tempFilepath.Replace(_sourceDir, "").TrimStart(Path.DirectorySeparatorChar);
                        tempFilepath = System.IO.Path.Combine(_destinationDir, tempFilepath);
                    }
                    //otherwise strip off all the folder path
                    else
                    {
                        tempFilepath = String.IsNullOrEmpty(_destinationDir) ? newFilenames[index] : System.IO.Path.Combine(_destinationDir, newFilenames[index]);
                    }

                    //Save the new DIR path and check the DIR exsits,
                    //if it does not then create it so the files can copy
                    string tempDirPath = Path.GetDirectoryName(tempFilepath);
                    if (!System.IO.Directory.Exists(tempDirPath))
                        System.IO.Directory.CreateDirectory(tempDirPath);

                    //Have be been told to stop copying files
                    if (_cancel)
                        break;

                    //Set the file thats just about to get copied
                    _currentFilename = filename;

                    bool result = NativeMethods.CopyFileEx(filename, tempFilepath, new NativeMethods.CopyProgressDelegate(this.CopyProgressHandler), IntPtr.Zero, ref _cancel, 0);
                    _totalFilesCopied++;

                    if (!result)
                    {
                        if (Marshal.GetLastWin32Error() != ERROR_REQUEST_ABORTED)
                            lastError = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                        else
                            _cancel = true;
                        OnFileCopied(filename, lastError);
                    }
                    else
                        OnFileCopied(filename);

                    index++;
                }
                OnCopyComplete(lastError);
            }
            catch (Exception ex)
            {
                OnCopyComplete(ex);
            }
        }

        protected void OnCopyComplete(Exception error)
        {
            if (CopyComplete != null)
            {
                CopyComplete(this, new FileCopyCompleteEventArgs(_cancel, error));
            }
        }       

        protected void OnFileCopied(string fileName)
        {
            if (FileCopied != null)
            {
                FileCopiedEventArgs eventArgs = new FileCopiedEventArgs(fileName);
                FileCopied(this, eventArgs);
                _cancel = eventArgs.Cancel;
            }
        }

        protected void OnFileCopied(string fileName, Exception exception)
        {
            if (FileCopied == null) return;

            FileCopiedEventArgs eventArgs = new FileCopiedEventArgs(fileName, exception);
            eventArgs.Cancel = _cancel;
            FileCopied(this, eventArgs);
            _cancel = eventArgs.Cancel;
        }

        //Copy the files
        public void Copy()
        {        
            _copyThread = new Thread(new ThreadStart(Copyfiles)) {Name = "CopyThread"};
            _copyThread.Start();
        }       
    }
}

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Controls.Common
{

    public partial class frmCopyFiles : Form
    {

        private CopyFiles f;

        // Constructors
        public frmCopyFiles(List<string> sourceFiles, List<string> destFiles)
        {
            InitializeComponent();
            f = new CopyFiles(sourceFiles, destFiles);
            f.ProgressEvent += new CopyFiles.CopyProgressHandlerDelegate(update);
            f.CopyComplete += new CopyFiles.FileCopyCompleteEventHandler(f_CopyComplete);
            f.FileCopied += new CopyFiles.FileCopiedEventHandler(f_FileCopied);
        }

        void f_FileCopied(object sender, FileCopiedEventArgs e)
        {
            if (e.Error != null)
                e.Cancel = true;
        }

        void f_CopyComplete(object sender, FileCopyCompleteEventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new CopyFiles.FileCopyCompleteEventHandler(f_CopyComplete), new object[] {sender, e});
                return;
            }

            if (e.Cancel || e.LastError != null)
                DialogResult = System.Windows.Forms.DialogResult.Cancel;

            if (e.LastError != null)
            {
                btnCancel.Text = "OK";
                MessageBox.Show(e.LastError.Message);
            }
            else
            {
                DialogResult = System.Windows.Forms.DialogResult.OK;
                Close();
            }
        }

        public frmCopyFiles(string source, string dest)
        {
            InitializeComponent();
            f = new CopyFiles(source, dest);
            f.ProgressEvent += new CopyFiles.CopyProgressHandlerDelegate(update);
            f.CopyComplete += new CopyFiles.FileCopyCompleteEventHandler(f_CopyComplete);
            f.FileCopied += new CopyFiles.FileCopiedEventHandler(f_FileCopied);
        }

        public frmCopyFiles(List<string> sourceFiles, string destDirectory)
        {
            InitializeComponent();
            f = new CopyFiles(sourceFiles, destDirectory);
            f.ProgressEvent += new CopyFiles.CopyProgressHandlerDelegate(update);
            f.CopyComplete += new CopyFiles.FileCopyCompleteEventHandler(f_CopyComplete);
            f.FileCopied += new CopyFiles.FileCopiedEventHandler(f_FileCopied);
        }

        // Methods
        private void update(Int32 totalFiles, Int32 copiedFiles, Int64 totalBytes, Int64 copiedBytes, String currentFilename)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new CopyFiles.CopyProgressHandlerDelegate(update),new object[] {totalFiles, copiedFiles, totalBytes, copiedBytes, currentFilename});
                return;
            }

            Prog_TotalFiles.Maximum = totalFiles;
            Prog_TotalFiles.Value = copiedFiles;
            Prog_CurrentFile.Maximum = 100;
            if (totalBytes != 0)
            {
                Prog_CurrentFile.Value = Convert.ToInt32((100f / (totalBytes / 1024f)) * (copiedBytes / 1024f));
            }

            Lab_TotalFiles.Text = "Total files (" + copiedFiles + "/" + totalFiles + ")";
            Lab_CurrentFile.Text = currentFilename;
        }

        private void But_Cancel_Click(object sender, EventArgs e)
        {
            if (btnCancel.Text == "OK")
                Close();
            else
            {
                f.CancelCopy();
                btnCancel.Enabled = false;
            }
        }
        private void frmCopyFiles_Closed(object sender, System.EventArgs e)
        {
            f.CancelCopy();
        }

        private void frmCopyFiles_Shown(object sender, EventArgs e)
        {
            f.Copy();
        }
    }

}


C#
internal static class NativeMethods
   {
       public const int FSCTL_SET_COMPRESSION = 0x9C040;
       public const short COMPRESSION_FORMAT_DEFAULT = 1;
       public const int HWND_BROADCAST = 0xFFFF;

       // Enums
       // These Enums are used for the windows CopyFileEx function
       [Flags]
       public enum CopyFileFlags : uint
       {
           COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
           COPY_FILE_RESTARTABLE = 0x00000002,
           COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
           COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
       }
       public enum CopyProgressResult : uint
       {
           PROGRESS_CONTINUE = 0,
           PROGRESS_CANCEL = 1,
           PROGRESS_STOP = 2,
           PROGRESS_QUIET = 3
       }
       public enum CopyProgressCallbackReason : uint
       {
           CALLBACK_CHUNK_FINISHED = 0x00000000,
           CALLBACK_STREAM_SWITCH = 0x00000001
       }

       public delegate CopyProgressResult CopyProgressDelegate(Int64 TotalFileSize, Int64 TotalBytesTransferred, Int64 StreamSize, Int64 StreamBytesTransferred, UInt32 dwStreamNumber, CopyProgressCallbackReason dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

       [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
       [return: MarshalAs(UnmanagedType.Bool)]
       public static extern bool CopyFileEx(string existingFileName, string newFileName, CopyProgressDelegate lpProgressRoutine, IntPtr lpData, ref bool pbCancel, CopyFileFlags dwCopyFlags);
}

QuestionRe: Updated Code Pin
scooter_seh7-Apr-12 6:47
scooter_seh7-Apr-12 6:47 
AnswerRe: Updated Code Pin
zt08194913-Nov-12 16:40
zt08194913-Nov-12 16:40 
GeneralRe: Updated Code Pin
scooter_seh14-Nov-12 1:28
scooter_seh14-Nov-12 1:28 
GeneralRe: Updated Code Pin
zt08194914-Nov-12 2:52
zt08194914-Nov-12 2:52 
QuestionCopy also empty folders Pin
lozirion10-Feb-11 0:04
lozirion10-Feb-11 0:04 
AnswerRe: Copy also empty folders Pin
MacSpudster7-Jul-11 8:20
professionalMacSpudster7-Jul-11 8:20 
Generalfolder structure Pin
pavelnt26-Nov-10 0:24
pavelnt26-Nov-10 0:24 
GeneralMy vote of 5 Pin
B.V.Papadopoulos6-Sep-10 1:06
professionalB.V.Papadopoulos6-Sep-10 1:06 
GeneralError Handling Pin
mason.c2-Jun-10 10:45
mason.c2-Jun-10 10:45 
QuestionCopyfolder for me does not work with large files Pin
Nadeem Rasool15-Apr-10 5:15
Nadeem Rasool15-Apr-10 5:15 
AnswerRe: Copyfolder for me does not work with large files Pin
Neil Cadman4-May-10 9:49
Neil Cadman4-May-10 9:49 
GeneralCool article! Pin
Abhinav S27-Mar-10 21:01
Abhinav S27-Mar-10 21:01 
QuestionVery helpful but I would like to copy an entire tree of files and folder. Pin
kakaomocha26-Mar-10 9:05
kakaomocha26-Mar-10 9:05 
AnswerRe: Very helpful but I would like to copy an entire tree of files and folder. Pin
Neil Cadman4-May-10 9:52
Neil Cadman4-May-10 9:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.