65.9K
CodeProject 正在变化。 阅读更多。
Home

CopyFileGeneric

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (14投票s)

2010年4月22日

CPOL

3分钟阅读

viewsIcon

80721

downloadIcon

7286

复制文件并增加其他功能

引言

Windows 复制只是复制文件,而它唯一提供的功能是取消复制。然而,我经常需要一些额外的功能;其中包括暂停复制过程,将未复制的文件列在最后而不是烦人的中间。

CopyFile1.png

CopyFile2.png

此应用程序包含以下功能

  • 暂停/恢复复制过程
  • 跳过单个文件
  • 在复制时抑制警告和错误
  • 在最后显示未复制的文件列表,并可以选择重试复制它们
  • 如果文件存在,则可以选择禁止覆盖警告。

我将代码分为两个不同的部分

  • 复制:处理复制文件(项目 Backup2
  • 用户界面:处理用户界面以及与复制的同步(项目 CopyFilesGeneric

Using the Code

项目 Backup2

这是一个库项目。我使用了 Shell 函数 CopyFileEx() 来复制文件。此函数的定义是

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern unsafe bool CopyFileEx(string ExistingFileName, string NewFileName, 
CopyProgressRoutine ProgressRoutine, IntPtr Data, ref Boolean Cancel, 
CopyFileFlags CopyFlags);   

另一个函数 HResultToString() HRESULT 值转换为相应的消息。为此,我使用了一个文件 XPSP2CodesEn.csv,它存储了 HRESULT 及其对应的字符串消息。

public static string HResultToString(uint result)
{
	System.IO.StreamReader str = 
		new StreamReader(Properties.Resources.ErrorCodeSource);
	LumenWorks.Framework.IO.Csv.CsvReader csv = new CsvReader(str, true);

         csv.SkipEmptyLines = true;
         csv.SupportsMultiline = true;

         foreach (var item in csv)
         {
         	if (uint.Parse(item[0], 
		System.Globalization.CultureInfo.InvariantCulture) == (uint)result)
                 {
                 	return item[2];
                 }
         }

         return "INVALID HRESULT";
} 

为了读取 CSV 文件,我使用了 Sebastien Lorion 的 CsvReader https://codeproject.org.cn/KB/database/CsvReader.aspx)。

主函数 CopyFiles() 执行复制操作。需要注意的是,在未对文件进行计数之前,开始复制是无用的。这是因为在计数文件时,CountFiles() 函数会标记要复制的文件(通过将其添加到文件中)。

private void CopyFiles()
{
      if (this.CurrentStatus == CopyStatus.Counting)
      { 
      	    restEvent.Reset();    
      }

      resetEvent.WaitOne();

      this.CurrentStatus = CopyStatus.Copying;
      RaiseCopyStartedEvent();
      RaiseStatusChangedEvent();
            
      foreach (KeyValuePair<string, double> item in files)
      {
      Retry:
      	    bool success = false;
            Boolean b = false;
            previousCopiedSize = 0;
            skip = false;

	    string destinationDirectory = Destination + 
		item.Key.Substring(0, item.Key.LastIndexOf(
                	Path.DirectorySeparatorChar)).Substring(sourceDirectory.Length - 1);
            if (Directory.Exists(item.Key))
            {                    
                  if ((Directory.CreateDirectory(Destination + 
			item.Key.Substring(sourceDirectory.Length - 1)) == null)) 
                        	unCopiedFiles.Add(new UncopiedFilesStructure(item.Key,
			item.Value, UnsafeNativeMethods.GetHResult(
                          	(uint)Marshal.GetLastWin32Error()).ToString()));
                 
                  continue;
            }                

            if (cancelCopy)
            {
                    RaiseCopyCanceledEvent();
                    copyThread.Abort();
            }


            if (destinationDirectory[destinationDirectory.Length - 1] != 
			Path.DirectorySeparatorChar)
               		destinationDirectory += Path.DirectorySeparatorChar;

               this.CurrentFile = item.Key;
               RaiseFileCopyStartEvent(item.Key, Path.Combine
		(destinationDirectory, Path.GetFileName(item.Key)), item.Value);

               unsafe
               {
                     success = UnsafeNativeMethods.CopyFileEx(item.Key,
                        destinationDirectory + Path.GetFileName(item.Key),
                        new CopyProgressRoutine(CopyProgressRoutineHandler), IntPtr.Zero,
                        ref b, this.CopyFlags);
               }
               if (!success)
               {
                   string hres = UnsafeNativeMethods.HResultToString
		(UnsafeNativeMethods.GetHResult((uint)Marshal.GetLastWin32Error()));

                   if (skip) { unCopiedFiles.Add(new UncopiedFilesStructure
			(item.Key, item.Value, "Skipped: " + hres)); continue; }

                   switch (RaiseFileCopyUnsuccessfulEvent
			(item.Key, destinationDirectory + Path.GetFileName(item.Key),
			 item.Value, hres))
                   {
                        case FileHandleProcedure.Skip:
                        case FileHandleProcedure.Cancel:
                            unCopiedFiles.Add(new UncopiedFilesStructure
				(item.Key, item.Value, hres)); break;
                        case FileHandleProcedure.Retry: goto Retry;
                        case FileHandleProcedure.CancelAll: this.cancelCopy = true; break;
                        default: break;
                   }
               }
               else
               {
                    RaiseFileCopyCompletedEvent(item.Key, 
			destinationDirectory + Path.GetFileName(item.Key), item.Value);
               }
        }

        this.CurrentStatus = CopyStatus.CopyCompleted;
        RaiseStatusChangedEvent();
        RaiseCopyCompletedEvent();
} 

此外,必须通知用户复制失败并进行处理。此任务由事件 FileCopyUnsuccessfull 执行。

项目 CopyFile

该项目引用了 Backup2 项目,并处理用户界面和同步过程。

最值得注意的需要稍作解释的函数是 UpdateUI()。此函数的定义是

/// <summary>
/// Synchronize UI with the copying status
/// </summary>
/// <param name="sync">Synchronize Operation To perform</param>
/// <param name="objs">Objects and their values. It must have the following structure:
/// index representation
/// 0. Text; Control whose caption(text) is to be changed
/// 1. the text for control at index 0
/// 2. Status; Text for CopyStatusLabel
/// 3. Status; Text for CurrentFileLabel
/// 4. Progress; the progressbar whose value is to be updated
/// 5. the value of the progressbar specified at index 4
/// 6. UpdateList; Listbox that has to be updated
/// 7. Operation that has to be applied on Listbox at index 6; i.e., ADD, DELETE or CLEAR
/// 8+. objects at index 8 or more are added or deleted from 
/// the listbox at index 6, depending on the operation specified at index 7
/// </param>
private void UpdateUI(SyncronizationOperations sync, object[] objs)
{
    if ((sync & SyncronizationOperations.CheckBox) == SyncronizationOperations.CheckBox)
    {
        CheckBoxStatus();
    }
    if ((sync & SyncronizationOperations.SetText) == SyncronizationOperations.SetText)
    {
        if (objs.Length > 1 && objs[0] != null && objs[1] != null)
        {
            Control content = objs[0] as Control;
            content.Text = objs[1] as string;
        }
    }
    if ((sync & SyncronizationOperations.SetStatus) == SyncronizationOperations.SetStatus)
    {
        if (objs.Length > 2 && objs[2] != null)
        {
            CopyStatusLabel.Text = objs[2] as string;
            this.Text = CopyStatusLabel.Text;
        }
        if (objs.Length > 3 && objs[3] != null)
        {
            CurrentFileLabel.Text = objs[3] as string;
        }
    }
    if ((sync & SyncronizationOperations.SetProgress) == 
			SyncronizationOperations.SetProgress)
    {
        if (objs.Length > 5 && objs[4] != null && objs[5] != null)
        {
            ProgressBar p = objs[4] as ProgressBar;
            p.Value = Convert.ToInt32(objs[5], CultureInfo.InvariantCulture);
            
        }
    }
    if ((sync & SyncronizationOperations.UpdateList) == 
			SyncronizationOperations.UpdateList)
    {
        if (objs.Length > 7 && objs[6] != null && objs[7] != null)
        {
            ListView list = objs[6] as ListView;                    
            switch ((ListboxOperations)objs[7])
            {
                case ListboxOperations.ADD:
                    for (int i = 8; i < objs.Length; i++)
                    {                                
                        list.Items.Add(new ListViewItem
				((objs[i] as string).Split('\t')));
                    }
                    break;
                case ListboxOperations.DELETE:
                    for (int i = 8; i < objs.Length; i++)
                    {
                        list.Items.Remove(new ListViewItem
				((objs[i] as string).Split('\t')));
                    }
                    break;
                case ListboxOperations.CLEAR:
                    list.Items.Clear();
                    break;
                default:
                    break;
            }
        }
    }
}

这段代码真的很令人困惑,需要编辑。问题是我对线程的概念很陌生。这似乎是我能想到的最好的方法。在这个方法中,有 2 个参数:第一个是 SyncronizationOperations ,它定义了要执行的操作;第二个是对象数组。索引 0 处的对象具有一个控件,其文本(标题)需要更改;索引 1 处的对象具有需要分配给索引 0 处对象的 string 。在索引 2 处,我们有用于更新拷贝器整体进度的 string 。类似地,索引 3 处的 string 用于更新当前文件的名称。索引 4 处的对象具有一个 progressbar 对象,而索引 5 处的对象具有索引 4 处 progressbar 的新值。在索引 6 处,我们有一个需要更新的 listview listview 的更新操作由对象 (ListboxOperations) 提供,后续元素是要操作的 listview 的值。

历史

  • 2010 年 4 月 23 日 - 初始上传
  • 2010 年 4 月 30 日 - 修订
    • 添加了支持 UI 以选择要复制的文件
    • 其他一些小的改动
© . All rights reserved.