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

DirectoryList 2.0

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.43/5 (12投票s)

2008年2月2日

CPOL

4分钟阅读

viewsIcon

38362

downloadIcon

735

一个自定义列表框控件,用于帮助可视化地操作数据。

new1.png

引言

内容

DirectoryList 控件是一个自定义列表框控件,可用于

  • 跟踪目录中文件和文件夹的数量(支持拖放)。
  • 将文件从一个文件夹复制到另一个文件夹。
  • 将多个文件夹中的文件合并到一个文件夹中。
  • 创建 700MB(CD-ROM 的大小)的备份文件夹。
  • 在文件夹内搜索特定类型的文件。

注意:这是 DirectoryList 类的第二版。有关此项目为何启动以及类的一些细节的更多信息,请阅读我的第一篇文章

原因

在用户需要可视化地操作或跟踪其计算机上重要数据的场景中,DirectoryList 控件最为有用。

方法

注意:请确保您已安装最新的Visual C++ 可再发行组件包

这是该类的蓝图

public ref class DirectoryList : public Control

下面列出了 DirectoryList 的公共属性和函数

  • FileCount - 返回文件数。
  • FolderCount - 返回文件夹数。
  • Items - 返回对 DirectoryList 内部 ListBox, ObjectCollection 的引用。
  • ShowProgressBar - 设置或返回一个布尔标志,用于显示或隐藏 DirectoryList 的内部 ListBox, progressPanel
  • TraverseDirectory - 布尔值,允许 DirectoryList 遍历子目录。
  • void Build(array<string^ />^ data, bool subdirsFlag) - 入口点;构建添加到 DirectoryList 的文件/文件夹。
  • void Copy(String^ destinationPath,bool overwrite, bool cdBackup, bool consolidate, bool directorCopy) - 开始文件复制。
  • void Deserialize(String^ filename) - 读取包含所有已保存文件和文件夹信息的二进制文件。
  • void Remove() - 从 DirectoryList 中删除选定的项目。
  • void SearchFor(String ^filetype, ListBox^ results) - 搜索特定类型的文件。
  • void Serialize(String^ filename) - 创建包含所有文件和文件夹信息的二进制文件。

要从 Visual Studio 中将 DirectoryList 添加到项目中,请右键单击工具箱,然后选择“选择项”。浏览 DirectoryList.dll 后,Visual Studio 应该会将 DirectoryList 添加到您的工具箱中,以便您可以像添加任何其他控件一样将其添加到您的项目中。如果您选择自己编译项目,则需要注意一些小细节。

charset.png

您需要确保项目“字符集”设置为“多字节”。您还需要确保项目设置为编译为 DLL。另外,请确保将 StatusBarProgressControl.dll 添加到项目的引用中。

Using the Code

如下构造一个 DirectoryList

DirectoryList ^myList = gcnew DirectoryList();
this->myList->AllowDrop = true;
this->myList->ContextMenuStrip = this->contextMenuStrip1;
this->myList->Location = System::Drawing::Point(14, 64);
this->myList->Name = L"myList";
this->myList->ShowProgressBar = false;
this->myList->Size = System::Drawing::Size(316, 250);
this->myList->TraverseDirectory = false;
this->myList->DragDrop += gcnew System::Windows::Forms::DragEventHandler(this, 
                                &Form1::myList_DragDrop);
this->myList->DragEnter += gcnew System::Windows::Forms::DragEventHandler(this, 
                                &Form1::myList_DragEnter);
this->Controls->Add(this->myList);

要添加一个文件夹中的所有文件以及所有子文件夹,请使用以下代码

private: System::Void btnFolder_Click(System::Object^ sender, System::EventArgs^ e) 
{
   FolderBrowserDialog^ myFolder = gcnew FolderBrowserDialog();
   if(myFolder->ShowDialog() == System::Windows::Forms::DialogResult::Cancel)
   {
       myFolder->SelectedPath = "";
   }
   else
   {
       array<string^> ^<string^ />data = gcnew array<string^ />(1);
       data[0] = myFolder->SelectedPath;
       myList->Build(data,cboxSubdirs->Checked);
       delete [] data;
   }
   delete myFolder;
}

以及从一个文件夹复制文件到另一个文件夹

private: System::Void btnCopy_Click(System::Object^  sender, System::EventArgs^  e) 
{
    if(textBox1->Text->Length > 0)
    {
        myList->Copy(textBox1->Text,cboxOverwrite->Checked,rboxCDbackup->Checked,
                     rbConsolidate->Checked,rbDcopy->Checked);
    }
}

关注点

为了更新 DirectoryList 类到 C++/CLI,我决定使用 Microsoft 的 CLR Profiler 对其进行性能分析。结果发现在我的 Build 函数中存在一些问题。具体来说,Build 函数调用私有成员函数 TraverseFiles,该函数又调用 .NET 的 Directory::ExistsDirectory::GetFilesDirectory::GetDirectories 函数。这些函数的问题在于它们分配了过多的内存,导致 Build 函数变慢。我决定编写一个 Win32 API 包装器类来封装更快的 Win32 API 函数并提高 Build 的性能。有关更多详细信息,请参阅随附的 Excel 电子表格,但从纯 .NET 解决方案转向 API 包装器将内存分配减少了 35MB。它还将重新定位的字节减少了 17MB。除了新的包装器类,我还利用了 .NET 2.0 中包含的 Microsoft 的一些新类。其中一个类是 System::Collections::Generic::LinkedList。通过从 ArrayList 迁移到 LinkedList,我将 TraverseFiles 函数的大小减少了 188 行代码。

这是加速 DirectoryList 的类

class DirectoryAPI
{
public:
    DirectoryAPI();
    ~DirectoryAPI();
    bool DirectoryExists(char *argv);
    //Make sure you delete allocated memory
    //after using FindFiles or FindDirectories
    char** FindFiles(char *argv);
    char** FindDirectories(char *argv);
    int numberOfFiles;
    int numberOfFolders;
};

例如,这是我如何实现本地 FindFiles 函数的

char** DirectoryAPI::FindFiles(char* argv)
{
    WIN32_FIND_DATA FindFileData;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    DWORD dwError;
    LPTSTR DirSpec;
    size_t length_of_arg;
    char **result;

    DirSpec = (LPTSTR) malloc (BUFSIZE);

    if( DirSpec == NULL )
    {
        //Insufficient memory available
        //MessageBox(0,"NULL","Welcome Message",1);
        goto Cleanup;
    }

    // Check that the input is not larger than allowed.
    StringCbLength(argv, BUFSIZE, &length_of_arg);

    if (length_of_arg > (BUFSIZE - 2))
    {
        //Input directory is too large.
        goto Cleanup;
    }

    // Prepare string for use with FindFile functions. First, 
    // copy the string to a buffer, then append '\*' to the 
    // directory name.
    StringCbCopyN (DirSpec, BUFSIZE, argv, length_of_arg+1);
    StringCbCatN (DirSpec, BUFSIZE, TEXT("\\*"), 2*sizeof(TCHAR));

    //Find the number of files in the directory.
    numberOfFiles = 0;

    hFind = FindFirstFile(DirSpec, &FindFileData);

    if (hFind == INVALID_HANDLE_VALUE) 
    {
        //Invalid file handle. 
    } 
    else 
    {
        // Get a Count of all the files in the directory.
        while (FindNextFile(hFind, &FindFileData) != 0) 
        {
            if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            {
                //Skip
            }
            else
            {
                numberOfFiles++;
            }
        }
    
        dwError = GetLastError();
        FindClose(hFind);
        if (dwError != ERROR_NO_MORE_FILES) 
        {
            //FindNextFile error.
            goto Cleanup;
        }
    }
    
    result = new char*[numberOfFiles];

    if(numberOfFiles > 0)
    {
        hFind = FindFirstFile(DirSpec, &FindFileData);
         
        
        if (result == NULL)
        {
            //Not enough memory
        }
        
        if (hFind == INVALID_HANDLE_VALUE) 
        {
            //Invalid file handle.
        } 
        else 
        {
            // List all the files in the directory.
            int myCount = 0;
            while (FindNextFile(hFind, &FindFileData) != 0) 
            {
                if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                {
                    //Skip
                }
                else
                {
                    int fileNameLength = strlen(FindFileData.cFileName);
                    int pathLength = strlen(argv);
                    int totalLength = fileNameLength + pathLength;

                    result[myCount] = new char[totalLength + 2]; 
                    
                    //Copy file path
                    result[myCount][pathLength] = '\\';
                    for(int j = pathLength; j--;)
                    {
                        result[myCount][j] = argv[j];    
                    }

                    //Concat file name to file path
                    int p = totalLength;
                    result[myCount][totalLength+1] = '\0';

                    for(int k = fileNameLength; k--;)
                    {
                        result[myCount][p] = FindFileData.cFileName[k];
                        p--;
                    }
                    myCount++;
                }    
            }

            dwError = GetLastError();
            FindClose(hFind);
            if (dwError != ERROR_NO_MORE_FILES) 
            {
                //FindNextFile error.
                goto Cleanup;
            }
        }
    }
    Cleanup:
    free(DirSpec);
    return result;
}

这是将所有内容粘合在一起的包装器类

ref class DirectoryAPIWrapper
{
public:
    DirectoryAPIWrapper();
    ~DirectoryAPIWrapper();
    array<string^ />^ GetFiles(String^ currentDirectory);
    array<string^ />^ GetDirectories(String^ currentDirectory);
    bool DirectoryExists(String^ currentDirectory);
private:
    DirectoryAPI* api;
};

这是我如何实现 GetFiles 函数的,该函数又调用本地 FindFiles 函数

array<String^>^ DirectoryAPIWrapper::GetFiles(System::String ^currentDirectory)
{    
    array<String^>^ answer;
    
    try
    {
        char *nativeArg = 
          (char*)(Marshal::StringToHGlobalAnsi(currentDirectory).ToPointer());
        char **result = api->FindFiles(nativeArg);

        answer = gcnew array<string^ />(api->numberOfFiles);

        if(api->numberOfFiles > 0)
        {
            for(int i = api->numberOfFiles; i--;)
            {
                answer[i] = gcnew String(result[i]);
                delete [] result[i];
                result[i] = nullptr;
            }    
        }

        if(result != nullptr)
        {
            delete [] result;
            result = nullptr;
        }
        Marshal::FreeHGlobal(IntPtr(nativeArg));
    }
    catch(System::Exception ^ex)
    {
        MessageBox::Show(ex->Message,"GetFiles Error");
    }
    return answer;
}

为了完整起见,这是我最终更新的 TraverseFiles 函数

void DirectoryList::TraverseFiles(void)
{
    DirectoryAPIWrapper^ myWrapper = gcnew DirectoryAPIWrapper();
    beginFileCount = fileCount;
    
    try
    {
        for(int i=0; i < directoryData->Length; i++)
        {
            if(myWrapper->DirectoryExists(directoryData[i]))
            {
                llfolders->AddLast(directoryData[i]);
                folderCount++;

                Generic::LinkedListNode<string^ /> ^currentNode = llfolders->Last;
                    
                while(currentNode != nullptr)
                {
                    array<String^>^ files = myWrapper->GetFiles(currentNode->Value);

                    if(files->Length > 0)
                    {
                        //Get interior pointer to array
                        interior_ptr<string^ /> p = &files[0]; 
                        
                        while(p != &files[0] + files->Length)
                        {
                            llfiles->AddLast(*p++);
                            fileCount++;
                        }
                    }

                    if(traverseDirectory)
                    {
                        array<String^>^ subDirs = 
                          myWrapper->GetDirectories(currentNode->Value);

                        if(subDirs->Length > 0)
                        {
                            //Get interior pointer to array
                            interior_ptr<string^ /> p = &subDirs[0]; 
                        
                            while(p != &subDirs[0] + subDirs->Length)
                            {
                                llfolders->AddLast(*p++);
                                folderCount++;
                            }
                        }
                    }
                    currentNode = currentNode->Next;
                    ShowProgress();
                }
            }
            else
            {
                llfiles->AddLast(directoryData[i]);
                fileCount++;
            }
        }
    }
    catch(System::Exception^ ex)
    {
        MessageBox::Show(ex->Message);
    }

    delete myWrapper;

    //Return results to listbox in GUI
    Display();
}

感谢 Dandy Cheung 和一些研究,我增加了在 DirectoryList 中搜索文件类型的能力。(可以尝试更新的演示应用程序。)这是 SearchFor 调用以查找文件的函数

void DirectoryList::GetResults(Object ^stateInfo)
{
    try
    {
        SearchInfo ^mySearch = dynamic_cast<searchinfo^ />(stateInfo);

        if(fileCount > 0)
        {
            Generic::LinkedListNodearray<String^>^ currentNode = llfolders->First;
            String ^filetypeCopy = mySearch->filetype;
            
            while(currentNode != nullptr)
            {
                //"C:\\WINDOWS\\*.*";
                mySearch->filetype = currentNode->Value + filetypeCopy;
                mySearch->currentSearchNode = currentNode;

                //Parameter Array
                array<object^>^ myArray = gcnew array<object^ />(1);
                myArray[0] = mySearch;

                mySearch->results->Invoke(gcnew 
                  ReturnResultsDelegate(this,&DirectoryList::ReturnResults),myArray);
                currentNode = currentNode->Next;
            }
        }
    }
    catch(System::Exception^ ex)
    {
        MessageBox::Show(ex->Message);
    }
}

这是返回结果的代码

void DirectoryList::ReturnResults(Object^stateInfo)
{
    SearchInfo ^mySearch = dynamic_cast<SearchInfo^>(stateInfo);
    
    char *nativeArg = 
      (char*)(Marshal::StringToHGlobalAnsi(mySearch->filetype).ToPointer());
        
    //To solve your problem you have to link your project with the User32.lib file.
    //If you're using the IDE to compile go to the project
    //properties->linker->input->additional dependencies
    //and add User32.lib
    ::SendMessage((HWND)mySearch->results->Handle.ToPointer(), 
                   LB_DIR,DDL_DIRECTORY,(LPARAM)nativeArg);

    if(mySearch->currentSearchNode->Next == nullptr)
    {
        mySearch->results->Cursor = Cursors::Default;
        MessageBox::Show(::SendMessage((HWND)mySearch->results->Handle.ToPointer(),
             LB_GETCOUNT,0,0).ToString() + " files found!", 
             "Results",MessageBoxButtons::OK,MessageBoxIcon::Information);
        mySearch->results->EndUpdate();
    }
}

历史

  • 2008 年 8 月 18 日:文章中的一些小错别字和说明。重命名了一些私有成员函数以提高清晰度。修复了演示中的一个 bug,该 bug 导致如果拖放一个 .lst 文件,它不会构建子目录。进度条现在显示 DirectoryList 正在构建其文件/文件夹。这还有改进的空间,但总比没有好。
  • 2008 年 5 月 20 日GetResults() 现在异步运行,并且使用 MethodInvokers 简化了回调代码。
  • 2008 年 4 月 2 日:小的 bug 修复,重新设计了演示应用程序以支持文件类型搜索。
  • 版本 2.0:添加了 Win32 API 包装器,并移除了排序和验证函数以简化类。重新构建了 C++/CLI 的类。
© . All rights reserved.