CMFCEditBrowseCtrl文件名显示截断问题排查与求助
我在使用CMFCEditBrowseCtrl选择图片路径(比如D:\WhatsApp Image 2020-04-02 at 13.03.48.jpeg)时遇到了一个棘手的问题:路径正常显示在控件中,但再次点击浏览按钮时,完整路径会被截断成at 13.03.48.jpeg;点击编辑框有时能恢复显示完整路径,但不是每次都成功,不过点击确定按钮时路径是完整正确的,这给用户造成了不小的困惑。
排查过程记录
Update 1:临时恢复方法
发现点击文件名输入框后按HOME键,可以强制显示完整文件名,这说明控件本身是存储了完整路径的,只是显示层面出现了异常。
Update 2:查看MFC源码定位逻辑
我查阅了CMFCEditBrowseCtrl中文件浏览模式的源码,核心逻辑如下:
case BrowseMode_File: { CString strFile; GetWindowText(strFile); if (!strFile.IsEmpty()) { TCHAR fname [_MAX_FNAME]; _tsplitpath_s(strFile, NULL, 0, NULL, 0, fname, _MAX_FNAME, NULL, 0); CString strFileName = fname; strFileName.TrimLeft(); strFileName.TrimRight(); if (strFileName.IsEmpty()) { strFile.Empty(); } const CString strInvalidChars = _T("*?<>|"); if (strFile.FindOneOf(strInvalidChars) >= 0) { if (!OnIllegalFileName(strFile)) { SetFocus(); return; } } } CFileDialog dlg(TRUE, !m_strDefFileExt.IsEmpty() ? (LPCTSTR)m_strDefFileExt : (LPCTSTR)NULL, strFile, m_dwFileDialogFlags, !m_strFileFilter.IsEmpty() ? (LPCTSTR)m_strFileFilter : (LPCTSTR)NULL, NULL); if (dlg.DoModal() == IDOK && strFile != dlg.GetPathName()) { SetWindowText(dlg.GetPathName()); SetModify(TRUE); OnAfterUpdate(); } if (GetParent() != NULL) { GetParent()->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); } } break;
这段代码里会拆分路径获取文件名,但看起来逻辑本身没有明显问题,于是尝试自定义控件重写浏览逻辑。
Update 3:自定义控件重写OnBrowse仍未解决
我创建了CMyMFCEditBrowseFileCtrl类重写OnBrowse方法,优化了文件过滤器索引和扩展名设置逻辑,代码如下:
#include "stdafx.h" #include "MyMFCEditBrowseFileCtrl.h" IMPLEMENT_DYNAMIC(CMyMFCEditBrowseFileCtrl, CMFCEditBrowseCtrl) BEGIN_MESSAGE_MAP(CMyMFCEditBrowseFileCtrl, CMFCEditBrowseCtrl) END_MESSAGE_MAP() void CMyMFCEditBrowseFileCtrl::OnBrowse() { CString strFile, strFileExtension; GetWindowText(strFile); if (!strFile.IsEmpty()) { TCHAR fname[_MAX_FNAME]; TCHAR ext[_MAX_EXT]; _tsplitpath_s(strFile, NULL, 0, NULL, 0, fname, _MAX_FNAME, ext, _MAX_EXT); CString strFileName = fname; strFileName.TrimLeft(); strFileName.TrimRight(); if (strFileName.IsEmpty()) { strFile.Empty(); } strFileExtension = ext; strFileExtension.Trim(); strFileExtension.MakeLower(); const CString strInvalidChars = _T("*?<>|"); if (strFile.FindOneOf(strInvalidChars) >= 0) { if (!OnIllegalFileName(strFile)) { SetFocus(); return; } } } int iFilterIndex = 2; // jpg - fallback m_strDefFileExt = _T("jpg"); if (strFileExtension == _T(".gif")) { iFilterIndex = 1; m_strDefFileExt = _T("gif"); } else if (strFileExtension == _T(".jpeg") || strFileExtension == _T(".jpg")) { iFilterIndex = 2; m_strDefFileExt = _T("jpg"); } else if (strFileExtension == _T(".png")) { iFilterIndex = 3; m_strDefFileExt = _T("png"); } else if (strFileExtension == _T(".tif") || strFileExtension == _T(".tiff")) { iFilterIndex = 4; m_strDefFileExt = _T("tif"); } else if (strFileExtension == _T(".bmp")) { iFilterIndex = 5; m_strDefFileExt = _T("bmp"); } CFileDialog dlg(TRUE, (LPCTSTR)m_strDefFileExt, strFile, m_dwFileDialogFlags, !m_strFileFilter.IsEmpty() ? (LPCTSTR)m_strFileFilter : (LPCTSTR)NULL, NULL); dlg.m_pOFN->nFilterIndex = iFilterIndex; if (dlg.DoModal() == IDOK && strFile != dlg.GetPathName()) { SetWindowText(dlg.GetPathName()); SetModify(TRUE); OnAfterUpdate(); } if (GetParent() != NULL) { GetParent()->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); } }
但修改后,路径截断的问题依然存在。
Update 4:定位到CFileDialog的问题
我单独用CFileDialog做了测试,发现当传入长文件名(比如123456789abcdefghijklmnopqrstuvwxyz.mwb)时,对话框中选中的内容仅显示rstuvwxyz.mwb,这说明问题出在CFileDialog层面,而非CMFCEditBrowseCtrl的逻辑。
Update 5:根源是Shell COM对象的IFileDialog
进一步排查后发现,这并非MFC的问题,根源在于MFC底层依赖的Shell COM对象(实现IFileDialog接口)。即使用纯COM代码不通过MFC,也能复现该问题:
#include <Windows.h> #include <ShlObj.h> int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevious, LPWSTR szCommandline, INT nShow) { HRESULT hr = CoInitialize(nullptr); if (SUCCEEDED(hr)) { IFileDialog *pfd = nullptr; hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd)); if (SUCCEEDED(hr)) { COMDLG_FILTERSPEC rgFileSpec[] = { {L"MWB Files (*.mwb)", L"*.mwb"}, {L"All Files (*.*)", L"*.*"} }; hr = pfd->SetFileTypes(ARRAYSIZE(rgFileSpec), rgFileSpec); hr = pfd->SetFileName(L"123456789abcdefghijklmnopqrstuvwxyz.mwb"); hr = pfd->Show(NULL); pfd->Release(); } } CoUninitialize(); return 0; }
结论
这个路径截断显示的问题,本质是Windows Shell的IFileDialog组件在处理较长文件名(或包含空格等特殊结构的文件名)时的显示异常,而非MFC控件本身的逻辑错误。控件内部始终存储着完整的正确路径,只是在触发浏览对话框时,对话框的文件名输入框会出现截断显示的情况,给用户造成误解。
内容的提问来源于stack exchange,提问作者Andrew Truckle

