使用PathCompactPath压缩路径时丢失完整文件名的问题求助
仅压缩路径中间、保留开头与完整文件名的C++20实现
你当前使用的PathCompactPath会在总长度/宽度不足时截断文件名,无法满足「保留开头路径和完整文件名,仅压缩中间目录」的需求,尤其当文件名本身较长时,即使设置较大的宽度阈值也会出现文件名被截断的情况。
以下是基于C++20文件系统库的两种实现方式:
1. 基于字符长度的路径压缩
这种方式按字符数控制压缩后的路径长度,逻辑简单直接:
#include <filesystem> #include <string> #include <vector> namespace fs = std::filesystem; std::wstring CompactPathMiddle(const fs::path& fullPath, size_t maxCharLength) { if (fullPath.empty()) { return L""; } // 提取完整文件名 const std::wstring fileName = fullPath.filename().wstring(); // 如果文件名本身超过最大长度,直接返回文件名 if (fileName.length() >= maxCharLength) { return fileName; } // 提取根路径(如C:\\) const std::wstring rootPath = fullPath.root_name().wstring() + fullPath.root_directory().wstring(); // 提取中间目录列表 std::vector<fs::path> middleDirs; for (const auto& dir : fullPath.parent_path()) { if (!dir.empty() && dir != fullPath.root_name() && dir != fullPath.root_directory()) { middleDirs.push_back(dir); } } size_t availableLength = maxCharLength - fileName.length() - 3; // 预留省略号(...)和路径分隔符的长度 std::wstring result = rootPath; size_t usedLength = rootPath.length(); // 先尝试保留第一个目录 if (!middleDirs.empty()) { const std::wstring firstDir = middleDirs[0].wstring() + L"\\"; if (usedLength + firstDir.length() <= availableLength) { result += firstDir; usedLength += firstDir.length(); } else { // 根路径加第一个目录都超长度,直接用根+省略号+文件名 return rootPath + L"...\\" + fileName; } } // 从后往前添加目录,直到剩余空间不足 std::wstring tailDirs; size_t tailLength = 0; for (auto it = middleDirs.rbegin() + 1; it != middleDirs.rend(); ++it) { const std::wstring dirStr = it->wstring() + L"\\"; if (usedLength + tailLength + dirStr.length() <= availableLength) { tailDirs = dirStr + tailDirs; tailLength += dirStr.length(); } else { break; } } // 拼接结果 if (!tailDirs.empty()) { result += tailDirs; } else if (middleDirs.size() > 1) { // 有多个中间目录但空间不够,添加省略号 result += L"...\\"; } result += fileName; return result; } // 使用示例 int main() { const fs::path fullPath = L"C:\\Users\\ajtru\\AppData\\Roaming\\Meeting Schedule Assistant\\Workbook-S-140-PublicTalk-WatchtowerStudy-ServiceTalk-Videoconference.xsl"; // 限制最大字符长度为60 const std::wstring compactedPath = CompactPathMiddle(fullPath, 60); // 输出示例:C:\\Users\\...\\Meeting Schedule Assistant\\Workbook-S-140-PublicTalk-WatchtowerStudy-ServiceTalk-Videoconference.xsl return 0; }
2. 基于像素宽度的路径压缩
如果需要适配窗口标题栏的实际像素宽度,可结合Windows API计算文本宽度,实现更精确的压缩:
#include <windows.h> #include <filesystem> #include <string> #include <vector> namespace fs = std::filesystem; // 计算字符串的像素宽度 int GetStringPixelWidth(const std::wstring& str, HDC hdc = nullptr) { HDC targetHdc = hdc ? hdc : GetDC(nullptr); SIZE textSize{}; GetTextExtentPoint32(targetHdc, str.c_str(), static_cast<int>(str.length()), &textSize); if (!hdc) { ReleaseDC(nullptr, targetHdc); } return textSize.cx; } std::wstring CompactPathMiddleByPixel(const fs::path& fullPath, int maxPixelWidth, HDC hdc = nullptr) { if (fullPath.empty()) { return L""; } const std::wstring fileName = fullPath.filename().wstring(); const int fileNameWidth = GetStringPixelWidth(fileName, hdc); // 文件名宽度超过上限,直接返回文件名 if (fileNameWidth >= maxPixelWidth) { return fileName; } const std::wstring rootPath = fullPath.root_name().wstring() + fullPath.root_directory().wstring(); const int rootWidth = GetStringPixelWidth(rootPath, hdc); const int ellipsisWidth = GetStringPixelWidth(L"...\\", hdc); const int availableWidth = maxPixelWidth - fileNameWidth - ellipsisWidth; // 根路径宽度超过可用空间,截断根路径后加省略号 if (rootWidth > availableWidth) { return rootPath.substr(0, rootPath.length() / 2) + L"...\\" + fileName; } std::vector<fs::path> middleDirs; for (const auto& dir : fullPath.parent_path()) { if (!dir.empty() && dir != fullPath.root_name() && dir != fullPath.root_directory()) { middleDirs.push_back(dir); } } std::wstring result = rootPath; int usedWidth = rootWidth; // 尝试保留第一个中间目录 if (!middleDirs.empty()) { const std::wstring firstDir = middleDirs[0].wstring() + L"\\"; const int firstDirWidth = GetStringPixelWidth(firstDir, hdc); if (usedWidth + firstDirWidth <= availableWidth) { result += firstDir; usedWidth += firstDirWidth; } else { return rootPath + L"...\\" + fileName; } } // 从后往前添加目录,直到剩余空间不足 std::wstring tailDirs; int tailWidth = 0; for (auto it = middleDirs.rbegin() + 1; it != middleDirs.rend(); ++it) { const std::wstring dirStr = it->wstring() + L"\\"; const int dirWidth = GetStringPixelWidth(dirStr, hdc); if (usedWidth + tailWidth + dirWidth <= availableWidth) { tailDirs = dirStr + tailDirs; tailWidth += dirWidth; } else { break; } } // 拼接最终结果 if (!tailDirs.empty()) { result += tailDirs; } else if (middleDirs.size() > 1) { result += L"...\\"; } result += fileName; return result; } // 使用示例(窗口程序中可传入窗口DC) // HWND hWnd = ...; // HDC hdc = GetDC(hWnd); // auto compacted = CompactPathMiddleByPixel(fullPath, 300, hdc); // ReleaseDC(hWnd, hdc);
核心逻辑说明
- 先拆分路径为根路径、中间目录列表、完整文件名三部分
- 优先保证文件名完整保留,再分配剩余空间给根路径和部分中间目录
- 空间不足时用
...替代未显示的中间目录,确保路径开头和结尾清晰可识别
内容的提问来源于stack exchange,提问作者Andrew Truckle
相关产品推荐
相关产品推荐

