C#, WPF – Copy files with progress bar by CopyFileEx API

When we copy a lot of files to a folder we would like our program showing the copying progress with useful information, for example how does the copying run or how many file left to be copied. The managed function File.Copy does not allow us to monitor its process by providing any callback function. There are already in internet many articles show us how to copy files with progress bar in Windows Form. Therefor in this example below, I would like to make a demo with WPF. The core function of our example is the CopyFileEx API.
CopyFileEx Function copies an existing file to a new file, notifying the application of its progress through a callback function.
From the beginning, I thought it’s pretty simple but then I was stuck in a mess which takes me many hours to solve. The signature of CopyFileEx API in C# looks like following.

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

I took the signature from pinvoke.net but changed the “ref int pbCancel” to “ref bool pbCancel” so that the argument get more meaningful. Code to call to this API

bool bSuccess = CopyFileEx(lstSource[nIndex], m_strDestFile, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref m_bCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
if (!bSuccess)
{
	int error = Marshal.GetLastWin32Error();
	throw new Win32Exception(error);
}

Whereas the variable lstSource contains paths to all of source files, m_strDestFile is the folder where we want to copy to, m_bCancel is variable to cancel the copy progress when it is set to be true. In the callback function we have possibility to get how much data was already copied and other info.

private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long StreamByteTrans, uint dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
{
	switch (reason)
	{
		case CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED:
			m_dtElapsedTime = DateTime.Now - m_dtStartTime;
			EventCopyHandler(this, new CopyEventArgs(((double)transferred / total) * 100, (transferred / 1024) / m_dtElapsedTime.TotalSeconds));
			return m_bCancel ? CopyProgressResult.PROGRESS_CANCEL : CopyProgressResult.PROGRESS_CONTINUE;                    
		default:                    
			return CopyProgressResult.PROGRESS_CONTINUE;
	}
}

You can see that the data was sent in chunk. As I remembered (if not wrong ^^) in my computer, the chunk size is about 0x10000 bytes. In this callback function we will update our progress bar and cancel if receiving cancel-event from user. That’s all about main ideas. I think it’s not difficult to understand. I would like now to talk more about 2 big problems which I met during programming.
1. WPF Control, exactly progress bar, does not update itself when copying
When I test to copy a big file, the complete GUI just completely freezes. The progress bar doesn’t run smoothly. It just jumps from 0 to 100. Thanks to the post of this blog http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh–update-wpf-controls.aspx , I solved the problem as following

private void UpdateProgress(int nPercent)
{
	pbBar.Value = nPercent;
	pbBar.Refresh();
	btnCopy.RefreshInput();
}

void cfewEngine_EventCopyHandler(CopyFileExWrapper sender, CopyEventArgs e)
{
	this.Dispatcher.Invoke(new UpdateProgressDelegate(UpdateProgress), new object[] { Convert.ToInt32(e.Percent) });            
}

public static class ExtensionMethods
{
	private static Action EmptyDelegate = delegate() { };
	public static void Refresh(this UIElement uiElement)
	{
		uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
	}

	public static void RefreshInput(this UIElement uiElement)
	{
		uiElement.Dispatcher.Invoke(DispatcherPriority.Input, EmptyDelegate);
	}
}

2. m_bCancel lost its value
When the user click to cancel copying, I set the m_bCancel to “true”. But in the callback function it has value “false” again. Until now I still do not understand why it losts its value. I solve the problem by adding “static” keyword to m_bCancel variable and it works. But I can not explain myself, why it turns to be false although I set it to true.

The complete source code you can download here “CopyFileEx Wrapper”

UPDATE 12.04.2012
– Fix code so that m_bCancel doesn’t have to be static.

5 thoughts on “C#, WPF – Copy files with progress bar by CopyFileEx API”

  1. Great code — thanks so much! By the way, the reason that your m_bCancel variable is “getting lost” is because you’re instantiating a new CopyFileExWrapper in your button click handler. So when you click the cancel button, it’s creating a second instance of your wrapper class and setting m_bCancel=true on the second instance, not the first instance that is copying the file. If you move this line:

    CopyFileExWrapper cfewEngine = new CopyFileExWrapper();

    into your declarations section, you can take the static modifier off of m_bCancel and it will work the way you intended.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>