You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 05:54:55