DirectoryList 2.0






3.43/5 (12投票s)
一个自定义列表框控件,用于帮助可视化地操作数据。
引言
内容
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
添加到您的工具箱中,以便您可以像添加任何其他控件一样将其添加到您的项目中。如果您选择自己编译项目,则需要注意一些小细节。
您需要确保项目“字符集”设置为“多字节”。您还需要确保项目设置为编译为 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::Exists
、Directory::GetFiles
和 Directory::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 的类。