在使用IM软件将文件接收完成,或者使用下载软件将文件下载完成时,都会有打开文件和打开文件坐在文件夹的入口,如下:
打开文件是直接将文件打开,打开所在文件夹则是打开文件所在的目录并选中该文件。下面我们就来看看实现打开文件及打开所在文件夹这两个功能的实现细节。
对于打开文件,我们只需要调用ShellExecute,传入“open”参数和要打开的文件的完整路径即可,执行这句代码后,系统会启动适当的程序去打开这个文件。此处有个细节需要考虑一下,要判断ShellExecute API函数的返回值,如果文件打开失败(系统找不到合适的程序来打开该文件),则需要将如下的系统窗口弹出来,让用户选择使用哪个程序打开(下面的代码中会有涉及):(以win10系统的截图为例)
对于打开文件所在文件夹,除了要打开文件所在的目录,还要选中该文件。调用ShellExecute是实现不了这样的效果的,可以调用系统的一个专用的API函数SHOpenFolderAndSelectItems即可。此处也需要考虑一个细节,SHOpenFolderAndSelectItems在个别情况下可能会调用失败,如果调用失败,则再去调用ShellExecute粗略地实现。这些细节在下面展示的代码会有充分的说明。
上述两个功能点,细节上还要考虑的更全面一点,都要考虑文件不存在的情况(可能文件被删除了),要做相应的处理。我们在开发软件时,要尽量考虑的全面一些,将各种可能的场景和问题都考虑进去,这一点显得尤为重要!
相关的代码如下所示:(以richedit中显示“打开文件”和“打开所在文件夹”的超链接为例)
void CRichEditUI::OnURLClick( ENLINK *pLink )
{
ASSERT(pLink);
if ( WM_LBUTTONDOWN == pLink->msg )
{
m_bDownUrl = TRUE;
}
if ( WM_LBUTTONUP == pLink->msg )
{
if ( !m_bDownUrl )
{
return;
}
m_bDownUrl = FALSE;
SetSel( pLink->chrg );
CStdString strSelText;
strSelText = GetSelText();
CStdString strLeft = strSelText.Left( 2 );
TCHAR achFilePath[MAX_PATH] = { 0 };
CStdString strFilePath;
CStdString strFilePathParam;
// 对于文件传输,传输结束后在界面中会显示“打开文件”和“打开所在文件夹”的链接
if ( 0 == _tcscmp( strSelText, _T("打开文件") ) )
{
// 1、打开文件(链接或按钮)
// 获取目标文件的完整路径
strFilePath = GetFilePath( pLink->chrg.cpMin );
if ( _taccess( strFilePath, 0 ) != 0 )
{
// 如果文件不存在,则要给出文件不存在的提示
::MessageBox( NULL, _T("文件不存在,文件打开失败!"), _T("提示"), MB_OK);
return;
}
// 加上双引号以防路径中有空格导致ShellExecute参数解析错误
strFilePathParam.Format( _T("\"%s\""), strFilePath );
int nRet = (int)ShellExecute( NULL, _T("open"), strFilePathParam, NULL, NULL, SW_SHOWNORMAL );
if ( SE_ERR_NOASSOC == nRet )
{
// 没有相关的程序能够打开该文件,需要自动弹出系统的“打开方式->选择程序...”对话框
CStdString strCmd;
strCmd.Format( _T("shell32, OpenAs_RunDLL %s"), strFilePath.GetData() );
ShellExecute( NULL, _T("open"), _T("rundll32.exe"), strCmd, NULL, SW_SHOWNORMAL );
}
}
else if ( 0 == _tcscmp( strSelText, STRING_OPEN_THE_FOLDER ) )
{
// 2、“打开所在文件夹”链接
strFilePath = GetFilePath( pLink->chrg.cpMin );
if ( _taccess( strFilePath, 0 ) != 0 )
{
// 当前文件不存在,可能已经被删除
CStdString strPathName = strFilePath.Left( strFilePath.ReverseFind('\\') );
if ( 0 == _taccess( strPathName, 0 ) )
{
// 文件夹存在,可以打开文件夹
// 如果文件夹已经打开,此时再用ShellExecute执行打开所在文件夹的操作,会将文件夹最前显示,
ShellExecute( NULL, _T("open"), _T("explorer.exe"), strPathName, NULL, SW_SHOWNORMAL );
}
else
{
// 如果所在文件夹不存在,则要给出文件夹不存在的提示
::MessageBox( NULL, _T("所在文件夹不存在,打开失败!"), _T("提示"), MB_OK);
}
return;
}
// 当前文件存在,则打开所在文件夹并选中文件
bool bOpenRet = OpenFolderAndSelFile( strFilePath );
if ( !bOpenRet )
{
// 有时会出现SHOpenFolderAndSelectItems调用失败的情况,比如标准用户下提权到
// 管理员权限时会返回操作失败,迅雷也有类似的问题。此时再借助ShellExecute来
// 打开,ShellExecute有点小问题,就是如果所在文件夹已经打开,不能选中目标文
// 件,不过在SHOpenFolderAndSelectItems调用失败时,也可以使用
CStdString strParameter;
strParameter.Format( _T("/select, \"%s\""), strFilePath );
ShellExecute( NULL, _T("open"), _T("explorer.exe"), strParameter, NULL, SW_SHOWNORMAL );
}
}
其中OpenFolderAndSelFile函数的实现如下:
bool CRichEditUI::OpenFolderAndSelFile( CStdString strFilePath )
{
LPITEMIDLIST pidl = NULL;
LPCITEMIDLIST cpidl = NULL;
LPSHELLFOLDER pDesktopFolder = NULL;
WCHAR wfilePath[MAX_PATH+1] = { 0 };
// 主工程已经初始化了COM库,此处不用在初始化了
//::CoInitialize( NULL );
if ( SUCCEEDED( SHGetDesktopFolder( &pDesktopFolder ) ) )
{
// IShellFolder::ParseDisplayName要传入宽字节
LPWSTR lpWStr = NULL;
#ifdef _UNICODE
_tcscpy( wfilePath, strFilePath );
lpWStr = wfilePath;
#else
MultiByteToWideChar( CP_ACP, 0, strFilePath.GetData(), -1, wfilePath, MAX_PATH );
lpWStr = wfilePath;
#endif
HRESULT hr = pDesktopFolder->ParseDisplayName( NULL, 0, lpWStr, NULL, &pidl, NULL );
if ( FAILED( hr ) )
{
pDesktopFolder->Release();
//::CoUninitialize();
return false;
}
cpidl = pidl;
hr = SHOpenFolderAndSelectItems( cpidl, 0, NULL, 0 ); // 第二个参数cidl置为0,表示是选中文件
if ( FAILED( hr ) )
{
pDesktopFolder->Release();
//::CoUninitialize();
return false;
}
pDesktopFolder->Release();
//::CoUninitialize();
return true;
}