如若需要获取某个选定文件的完整路径,小弟经常机械性地先复制Shell窗口中的路径,紧接着复制文件名并拼在路径之后.有时候复制文件路径是为了程序使用,这就必须将路径中的所有"\"换成"\\",总之一切都是很无聊的操作.还好我是个程序员,完全可以定制一些程序来方便自己.在此分享给大家.
程序实现的功能很明确:在Shell的上下文菜单中加入一菜单项目"获取文件路径并保存到剪贴板",点击此项可以将选中的一个或多个文件的完整路径保存到剪贴板中.多个文件路径之间以换行"\r\n"间隔.若需要获取的路径是程序格式("\"换成"\\"),则可在Ctrl键按下的状态下单击该菜单项.
实现:定制Shell的菜单项,需实现IContextMenu接口,同时也需要实现IShellExtInit接口来完成初始化的工作.
首先定义一些需要使用的宏:
//
菜单ID
#define
ID_COPY_PATH 0
//
用于剪贴板格式
#ifdef _UNICODE
#define
CF_TEXT_FORMAT CF_UNICODETEXT
#else
#define
CF_TEXT_FORMAT CF_TEXT
#endif
定义一数组保存选中的文件(夹)列表,定义如下:
CAtlArray
<
CString
>
m_arrFilePath;
IShellExtInit接口就一个Initialize方法,在这里用于显示上下文菜单之前的初始化工作.我在实现中将当前选中的文件(夹)列表保存到m_arrFilePath数组,代码如下:
HRESULT STDMETHODCALLTYPE Initialize( LPCITEMIDLIST pidlFolder, IDataObject
*
pdtobj, HKEY hkeyProgID)
{
m_arrFilePath.RemoveAll();
//
文件列表
if
( pdtobj
!=
NULL )
{
STGMEDIUM medium
=
{
0
};
FORMATETC fe
=
{ CF_HDROP, NULL, DVASPECT_CONTENT,
-
1
, TYMED_HGLOBAL };
if
( SUCCEEDED( pdtobj
->
GetData(
&
fe,
&
medium) ) )
{
HDROP hDrop
=
(HDROP) ::GlobalLock(medium.hGlobal);
UINT uCount
=
::DragQueryFile( hDrop, (UINT)
-
1
, NULL,
0
);
for
( UINT uIndex
=
0
; uIndex
<
uCount; uIndex
++
)
{
TCHAR szFileName[MAX_PATH]
=
{
0
};
::DragQueryFile(hDrop, uIndex, szFileName, (
sizeof
(szFileName)
/
sizeof
(TCHAR))
-
1
);
//
szFileName为文件(夹)名
m_arrFilePath.Add( szFileName );
}
::GlobalUnlock(medium.hGlobal);
::ReleaseStgMedium(
&
medium);
}
}
return
S_OK;
}
IContextMenu接口则是实现上下文菜单的主体,其有三个成员方法要实现:
QueryContextMenu方法:可以用来添加自己的菜单项,实现如下:
HRESULT STDMETHODCALLTYPE QueryContextMenu( HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
CString strMenuText;
strMenuText.Format( _T(
"
获取文件路径并保存到剪贴板
"
) );
::InsertMenu(hmenu, indexMenu
++
, MF_STRING
|
MF_BYPOSITION, idCmdFirst
+
ID_COPY_PATH, strMenuText);
return
ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, (USHORT)(ID_COPY_PATH
+
1
)));
}
InvokeCommand方法:用来处理菜单项的命令:
HRESULT STDMETHODCALLTYPE InvokeCommand( LPCMINVOKECOMMANDINFO lpici )
{
//
Ctrl键按下时拷贝的路径的"\"用"\\"代替,这样可以直接应用在代码中
BOOL bCtrlPress
=
( ( ::GetKeyState( VK_CONTROL )
&
0x8000
)
!=
0
);
if
( LOWORD( lpici
->
lpVerb )
==
ID_COPY_PATH )
{
//
如果是拷贝文件路径菜单项
CString strClipboard(_T(
""
));
for
(
int
nIndex
=
0
; nIndex
<
(
int
)m_arrFilePath.GetCount(); nIndex
++
)
{
//
遍历选中的文件(夹)列表
CString strItem
=
m_arrFilePath.GetAt( nIndex );
if
( bCtrlPress )
{
//
获取路径的代码格式
strItem.Replace( _T(
"
\\
"
), _T(
"
\\\\
"
) );
}
//
添加到总串并以换行结束
strClipboard.Append( strItem );
strClipboard.Append( _T(
"
\r\n
"
) );
}
//
拷贝进剪贴板
if
( ::OpenClipboard( NULL ) )
{
::EmptyClipboard();
HGLOBAL hGlobal
=
GlobalAlloc( GPTR, ( strClipboard.GetLength()
+
1
)
*
sizeof
( TCHAR ) );
LPTSTR lpszText
=
(LPTSTR)::GlobalLock( hGlobal );
if
( lpszText
!=
NULL )
{
_tcscpy_s( lpszText, strClipboard.GetLength()
+
1
, strClipboard );
}
::SetClipboardData( CF_TEXT_FORMAT, hGlobal);
::GlobalUnlock( hGlobal );
::CloseClipboard();
}
}
return
S_OK;
}
GetCommandString方法:在此处用不到,简单地返回E_NOTIMPL即可.
程序实现已完结,最后一步就是注册,需要在注册表的HKEY_CLASSES_ROOT\*\ShellEx\ContextMenuHandlers\键一自己的键,并把键值设为刚刚编写的COM的CLSID.
由于是用ATL实现,我只需在RGS文件的HKCR键下加上如下脚本:
NoRemove
*
{
NoRemove ShellEx
{
NoRemove ContextMenuHandlers
{
ForceRemove
'
GetFilePath
'
=
s
'
{AAD8C1A8-017E-44B3-8271-DFBA4CD8E75C}
'
}
}
}
AAD8C1A8-017E-44B3-8271-DFBA4CD8E75C是我的CLSID.
一切都已完成。希望大家愉快.
PS: 我不知道博客园能不能上传附件,如果有需要源码的朋友可以留下Email。