我有一个MFC项目,在给定初始根路径的情况下,遍历每个文件,文件夹和子文件夹,然后在列表控件中向用户显示每个文件。由于这很容易成为一个相当冗长的操作,我偶尔会控制操作系统(通过处理单个消息泵队列),允许显示到目前为止发现的每个元素。现在这里有一个棘手的部分......
我想保持元素按其最后已知的修改时间戳排序,我相信这需要某种插入排序技术。由于某些元素可能包含重复的时间戳,但文件路径不同,我们将按时间戳排序(以std::string
格式存储为MM:DD:YY hh:mm
),简单的std::vector
不会似乎做了这个工作。此外,在开始排序元素之前,我宁愿不让用户等待整个操作完成,因为等待的时间是未知的,就像我上面所说的那样,很容易变得足够长以使任何人不耐烦。
最后,我需要一些方法来保持插入到List Control中的元素同等地映射到容器上的排序操作,这样用户就可以在实际中看到最近修改过的根路径内容(和子内容)时间。
为实现这个目的,使用什么样的容器和算法是什么?
这基本上就是我现在所做的事情:
void CFileSearchDlg::UpdateDirectoryList(std::string strRootPath)
{
CFilesystem fs; // Helper class which uses C++11 <filesystem> to iterate through file path entries
DWORD time = GetTickCount(); // Determines if we should yield control to the OS after a certain period of time
m_listView.DeleteAllItems(); // Clears all current elements from the list view control
/*
// CFilesystem::Search() takes in a root path and a lambda expression, and executes the expression for each
// element found within the root path, passing a basic_directory_entry<path> as a parameter to the lambda
// expression, and will continue to parse until no entries are left (or until we return false)...
*/
fs.Search(strRootPath, [&](CFilesystem::path_entry pe)
{
// This is primarily a Unicode project, so we need to convert the search results to an std::wstring
std::string path = pe.path().string();
std::wstring wpath;
wpath.assign(path.begin(), path.end());
// Get a Win32 handle to the file/folder, or display an error & exit
auto hFile = CreateFileA(path.c_str(), GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBoxA(NULL, "Failed to open file", path.c_str(), MB_OK | MB_ICONERROR);
return false; // Stop parsing
}
// Get the date & time attributes of the file/folder, or display an error & exit
TCHAR fileTime[MAX_PATH];
ZeroMemory(&fileTime, sizeof(TCHAR)* MAX_PATH);
auto ret = GetLastWriteTime(hFile, fileTime, MAX_PATH);
CloseHandle(hFile);
if (!ret) {
MessageBoxA(NULL, "Failed to get date & time attributes", path.c_str(), MB_OK | MB_ICONERROR);
return false; // Stop parsing
}
std::wstring strTime(fileTime);
/**************************************************
// THIS IS WHERE THE MAGIC IS SUPPOSED TO HAPPEN //
/*************************************************/
InsertPathItem(m_listView, wpath, strTime); // ... But how?
// Check if we should yield control to the operating system
auto tick = GetTickCount();
if (tick - time > 100) {
YieldControl();
time = tick;
}
// Continue to parse directory contents
return true;
}
);
}
修改: 完整的答案似乎是galinette(关于正确的STL容器)和foraidt(关于将视图与数据同步)的组合。
答案 0 :(得分:3)
只需使用std::multimap
,密钥类型可以是整数时间戳(最快的方式),也可以使用时间字符串,如果默认字符串排序保持时间戳顺序(这样更慢)
std::multimap<time_t, std::wstring>
或
std::multimap<std::string, std::wstring>
插入:
myFileMap.insert(std::pair<time_t, std::wstring>(time,wPath));
答案 1 :(得分:2)
std::set
根据其比较器保持其元素排序。
编辑:你需要提供一个严格小于,确定性的比较器(忘记数学术语)。如果可能存在多个相同的时间戳,则需要使用其他属性(例如,id)消除歧义。
答案 2 :(得分:1)
要同步使用内部数据结构对MFC 控件进行排序,您可以尝试虚拟列表:关键是使用LVS_OWNERDATA
风格。然后,列表将不会自行存储项目数据,而是回调您的代码以获取显示项目所需的信息。您可以在此处跳转到自定义排序容器并检索信息。
文章'Using virtual lists' on codeproject似乎非常全面。
接下来,排序本身。您可以尝试包含要显示的文本的自定义类型以及以数字格式排序的标准,例如:
struct item {
std::string label;
time_t mtime;
};
出于性能原因,将多个项插入向量中然后在将控制权交还给OS之前对其进行排序可能很有用。您必须测试这是否确实比直接插入已排序的容器更好。
无论如何,为了在容器类型之间轻松切换,你可以指定一个可以用作排序谓词的排序函数(可能用C ++ 11 lambdas做得更优雅):
struct item_less {
bool operator()(const item & a, const item & b) {
return a.mtime < b.mtime;
}
};
像这样使用:
std::set<item, item_less> a; // Automatic sorting
std::vector<item> b;
std::sort(a.begin(), a.end(), item_less()); // Manual sorting