|
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.
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;}
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
{
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;
public event FileCopyCompleteEventHandler CopyComplete;
public event FileCopiedEventHandler FileCopied;
public delegate void FileCopiedEventHandler(object sender, FileCopiedEventArgs e);
public delegate void FileCopyCompleteEventHandler(object sender, FileCopyCompleteEventArgs e);
public CopyFiles(String source, String destination)
{
_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;
private List<String> GetFiles(String sourceDir)
{
String[] fileEntries;
String[] subdirEntries;
fileEntries = System.IO.Directory.GetFiles(sourceDir);
List<String> foundFiles = fileEntries.ToList();
subdirEntries = System.IO.Directory.GetDirectories(sourceDir);
foreach (string subdir in subdirEntries)
{
if ((System.IO.File.GetAttributes(subdir) &
System.IO.FileAttributes.ReparsePoint) !=
System.IO.FileAttributes.ReparsePoint)
{
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 (!String.IsNullOrEmpty(_sourceDir))
files = GetFiles(_sourceDir);
_totalFiles = files.Count;
foreach (String filename in files.ToArray())
{
String tempFilepath;
if (!String.IsNullOrEmpty(_sourceDir))
{
tempFilepath = filename;
tempFilepath = tempFilepath.Replace(_sourceDir, "").TrimStart(Path.DirectorySeparatorChar);
tempFilepath = System.IO.Path.Combine(_destinationDir, tempFilepath);
}
else
{
tempFilepath = String.IsNullOrEmpty(_destinationDir) ? newFilenames[index] : System.IO.Path.Combine(_destinationDir, newFilenames[index]);
}
string tempDirPath = Path.GetDirectoryName(tempFilepath);
if (!System.IO.Directory.Exists(tempDirPath))
System.IO.Directory.CreateDirectory(tempDirPath);
if (_cancel)
break;
_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;
}
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;
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);
}
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();
}
}
}
internal static class NativeMethods
{
public const int FSCTL_SET_COMPRESSION = 0x9C040;
public const short COMPRESSION_FORMAT_DEFAULT = 1;
public const int HWND_BROADCAST = 0xFFFF;
[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);
}
|
|
|
|
|
How could I change this code to make it copy or move depending on a Boolean variable argument.
|
|
|
|
|
hello
I now copy file, open source file, the path in the textbox inside, open the target file, path in another textbox inside, with the progress bar shows, percentage, copy completed many display, now code to write bad, consult, hope comprehensive point! thank you
|
|
|
|
|
|
Folder copy to folder, display duplicate progress bar and copy percentage, can achieve at ordinary times copy show the effect of the same
|
|
|
|
|
Hello!
Thank you very much for this great article. I have a little question: I have to copy a folder tree containing some empty folders, running your code it doesn't copy empty folders, I need that folders to be created anyway, how can I do this?
Thanks
|
|
|
|
|
Change the code to automatically create the directory on the target.
Then, regardless if there are files in the source directory or not, the target directory has been created.
That is: TargetDirectoryCreated + NoFilesInSourceDirectory = EmpyTargetDirectory;
ASPX ~ Apple Simply Performs eXcellently
|
|
|
|
|
Excellent article.
Is it possible to implement such a task?
There is a text file txt with a list of file paths of the form:
C: \ Dir \ file1.exe
C: \ Dir \ My Folder \ file2.exe
C: \ Book \ file3.doc
You must copy these files to the specified directory paths in preserving the folder structure, for example, must be copied into a directory on drive D: \ copy of the list so that you are:
D: \ copy of the list \ C \ Dir \ file1.exe
D: \ copy of the list \ C \ Dir \ My Folder \ file2.exe
D: \ copy of the list \ C \ Book \ file3.doc
Thanks in advance for your help!
|
|
|
|
|
|
I am a bit new to this so bear with me. When I use a normal file copy method and a file is locked, I get an IO exception. However, when I run this code with files that I know are locked, no errors are thrown. So I am a bit baffled as to why since I don't see any error handling for that scenario.
I would really like to be able to pop up an error or write to a log if a IO lock or authentication error occured.
Thanks.
|
|
|
|
|
Hi there
I have seen the new fix for the copy folder, but when it reaches the end, I get the following error
System.ArgumentOutOfRangeException was unhandled by user code
Message="Value of '984' is not valid for 'Value'. 'Value' should be between 'minimum' and 'maximum'.\r\nParameter name: Value"
Source="System.Windows.Forms"
ParamName="Value"
StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at CopyFiles.CopyFiles.CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData) in C:\Users\nrasool\Desktop\Nadeem\Project\DatixWebInstaller\DatixWebInstaller\CopyFiles.cs:line 165
at CopyFiles.CopyFiles.CopyFileEx(String lpExistingFileName, String lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, Boolean* pbCancel, CopyFileFlags dwCopyFlags)
at CopyFiles.CopyFiles.Copyfiles() in C:\Users\nrasool\Desktop\Nadeem\Project\DatixWebInstaller\DatixWebInstaller\CopyFiles.cs:line 298
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(IMessage msg, IMessageSink replySink)
InnerException:
It seems to have a problem in CopyFile.cs with the following line:
digWindow.SynchronizationObject.Invoke(new CopyProgressRoutine(CopyProgressHandler),
new Object[] { total, transferred, streamSize, StreamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData });
Please could someone help on this
Many thanks
Kind Regards
|
|
|
|
|
Hi,
That is to do with the progress bar by the looks of things, to be hones I didnt really test is on any files bigger than about 400 meg. put a brake point over the line in the copy files dialog which is being called by the update event, you should see the code where it works out the max value of the progress bar. that should help you resolve that issue.
|
|
|
|
|
Nice.
A little more detail (to add to the source code) would be more helpful though.
Me, I'm dishonest. And a dishonest man you can always trust to be dishonest. Honestly. It's the honest ones you want to watch out for...
|
|
|
|
|
|
Hello, First of all Thank you very much for writing this great article.
I run the sample and it works like a charm!
However, I would like to know one more thing that is to copy an entire tree of the foler that's specified, meaning copying test1, test2, test3, test4 folders and put files that belong to those subfolder when being copied.
I've looked at CopyFiles.cs file and tried to see I could modify to achieve this. I gave up after looking at it for 2 mins.
If you could give me a clue as to how, I would appreciate it so much!
|
|
|
|
|
Hiya,
the code should already copy subfolders in the folder you specified to stat copying, its only if you supply it with a list of files it will only copy the ones you tell it to. Give me an example of how you are using the class if giving it jsut a folder path does not solve your problem?
|
|
|
|
|
As per this part
|
|
|
|
|
you can use "\" character to the end of the path
new CopyFiles.CopyFiles("C:\\Copy Test Folder", "C:\\Test");
new CopyFiles.CopyFiles("C:\\Copy Test Folder\\", "C:\\Test\\");
|
|
|
|
|
those double backslashes drive me nuts!
You can also (and should!) use:
new CopyFiles.CopyFiles(@"C:\Copy Test Folder", @"C:\Test");
new CopyFiles.CopyFiles(@"C:\Copy Test Folder\", @"C:\Test\");
ASPX ~ Apple Simply Performs eXcellently
|
|
|
|
|
Dear Neil!
That was a very helpful small program that you published! Thank you very much!
Greetings,
Tamas
|
|
|
|
|
|
Hi, I'm running into trouble when running this code. I'm using the events EV_copyCanceled and EV_copyComplete to inform the user when the file copy has been either completed or cancelled.
This is the code I'm using:
<br />
private void But_CopyFiles_Click(object sender, EventArgs e)<br />
{<br />
List<String> TempFiles = new List<String>();<br />
TempFiles.Add("a.exe");<br />
TempFiles.Add("b.msi");<br />
TempFiles.Add("c.exe");<br />
TempFiles.Add("d.exe");<br />
TempFiles.Add("e.exe");<br />
TempFiles.Add("f.msi");<br />
TempFiles.Add("g.exe");<br />
TempFiles.Add("h.msi");<br />
TempFiles.Add("i.exe");<br />
TempFiles.Add("j.msi");<br />
TempFiles.Add("k.msi");<br />
TempFiles.Add("l.exe");<br />
TempFiles.Add("m.msi");<br />
TempFiles.Add("n.iso");<br />
TempFiles.Add("o.zip");<br />
TempFiles.Add("p.msu");<br />
TempFiles.Add("q.zip");<br />
TempFiles.Add("r.exe");<br />
<br />
CopyFiles.CopyFiles Temp = new CopyFiles.CopyFiles(TempFiles, "Destination Folder");<br />
CopyFiles.DIA_CopyFiles TempDiag = new CopyFiles.DIA_CopyFiles();<br />
TempDiag.SynchronizationObject = this;<br />
Temp.EV_copyCanceled += new CopyFiles.CopyFiles.DEL_copyCanceled(Temp_EV_copyCanceled);<br />
Temp.EV_copyComplete += new CopyFiles.CopyFiles.DEL_copyComplete(Temp_EV_copyComplete);<br />
Temp.CopyAsync(TempDiag);<br />
}<br />
<br />
void Temp_EV_copyComplete()<br />
{<br />
MessageBox.Show("Copy completed");<br />
}<br />
<br />
void Temp_EV_copyCanceled(List<CopyFiles.CopyFiles.ST_CopyFileDetails> filescopied)<br />
{<br />
MessageBox.Show("Copy Canceled");<br />
}<br />
But when I click the cancel button on the progress dialog, I get the "Copy Canceled" messagebox followed by the "Copy completed" dialog. This last one shouldn't show up. I've tried to modify the code on the CopyFiles class to correct this behavior but I haven't found any way to do it. Any help would be appreciated. Thanks in advance.
|
|
|
|
|
Hi,
I don’t have the code with me however I think the best solution would be to change the copyCanceled event to return a class which holds both the list which is currently being returned and also a Boolean (notifyComplete). This way you can set the notifyComplete == false, then back in the copy class you could set check if it’s been set to false once the copyCanceled event has been fired, if it has then you could set another private Boolean in the copy class to equal it, then it’s a case of checking that Boolean when the OnCopyCompete method gets ran, so
if (_ notifyComplete)
if (copyCompete != null)
copyCompete();
Does that make sense?
|
|
|
|
|
Hello, I tried adding the two events and didn't have any problems with the MessageBox, but I do have problems when I try to modify another button on the Form.
void Temp_EV_copyComplete()
{
MessageBox.Show("Copy completed");
button1.Text = "Hello";
button1.Enabled = true;
}
The button1 is just another button placed on Form1 below the original Copy button.
When the event occurs I get the following error
{"Cross-thread operation not valid: Control 'button1' accessed from a thread other than the thread it was created on."}
Any ideas?
Thanks in advance.
|
|
|
|
|
Hello I figured it out.
(Mind you these delegate confuse me
private delegate void UpdateButtonData();
void CopyEngine_EV_copyComplete()
{
if (this.CancelSyncButton.InvokeRequired)
{
UpdateButtonData ButtonText = new UpdateButtonData(this.CopyEngine_EV_copyComplete);
this.CancelSyncButton.Invoke(ButtonText);
}
else
{
CancelSyncButton.Text = "Close";
CancelSyncButton.Enabled = true;
}
success = true;
}
|
|
|
|
|
Hello and thanks a lot for your code !
I got a little problem, hope somebody can help me.
How I can copy a list of Folders (like a list of files)
Source:
C:\temp\
C:\temp\test
C:\temp\test2
(Now I do it with for each.... but it make for each folder a own thread...)
Can anyone help me ?
Thanks
|
|
|
|
|