Mini Shell 扩展框架 - 第 III 部分






4.96/5 (10投票s)
2005 年 9 月 18 日
16分钟阅读

152638

1404
讨论用于创建 Windows Shell 扩展 (IShellFolderImpl) 的小型 C++ 框架。
目录
引言
这是 mini shell framework 系列文章的第三部分。在这里,我将讨论如何通过创建自定义 ShellFolder 来创建命名空间扩展,该自定义 ShellFolder 可与标准的系统 FolderView
协同工作。ShellFolders 是复杂的 Shell 扩展,需要实现大量功能。SDK Shell 文档经常不完整或缺失。甚至一些关键的 Win32 Shell API 函数也是未公开的!ShellFolderImpl
类通过 VVV 示例进行解释。该示例的代码包含在框架中。该示例操作的是 .vvv 文件(重命名为 .ini 文件)。这是我在第一部分和第二部分中使用的相同示例,但在这里它已扩展到支持子文件夹。命名空间扩展有两种类型:根目录式和非根目录式。VVV 示例演示了非根目录式。
SHITEMID 和 PIDL
任何浏览过 MSDN 文档以学习和理解如何创建 Shell 扩展的人,迟早都会发现 SHITEMID
s 和 PIDL
s 这些术语。这些是 Shell 用于标识 Shell 对象而使用的关键数据结构。当前的 MSDN 文档对这些概念进行了详尽的解释。
ITEMIDLIST
的定义是
typedef struct _ITEMIDLIST { SHITEMID mkid; } ITEMIDLIST;
SHITEMID
的定义是
typedef struct _SHITEMID { USHORT cb; // Size of the ID (including // cb itself) BYTE abID[1]; // The item ID (variable length) } SHITEMID;
Shell 中的每个项都需要有自己的内部唯一标识。此 Shell 项标识符 (SHITEMID
) 需要在其自己的(虚拟)文件夹内是唯一的。SHITEMID
可以组合成 ID 列表(PIDL)。文件系统项的典型 PIDL 是
[C:] – [My Documents] – [sample.vvv] – [0]
此 PIDL 包含三个 SHITEMID
和一个大小为 0 的终止 SHITEMID
。
主要问题是为特定项使用哪个标识符。MSDN 文档对此不够清晰,因此这里提供一些指导原则
- 不要使用指针作为项标识符。虽然指针是唯一的,但在两个 ShellFolder 实例显示同一项时会引起问题。如果项 ID 指向同一项,则它们需要相同。这种情况的典型用例是用户打开两个指向同一文件夹位置的窗口。提示:始终验证在两个指向同一位置的 Shell 视图中使用扩展时是否正常工作。
- 对于简单的视图(不需要支持项删除、复制粘贴等),可以使用基于数组的解决方案,其中数组中的索引作为标识符。
- 对于文件系统项,最合乎逻辑的选择是文件名。大多数文件系统强制要求文件名在目录中是唯一的。Shell 可以处理这一点,项的 ID 可以在显示名称发生变化时更改。
SHChangeNotify
函数可用于触发“重命名”以通知系统某个 PIDL 被另一个 PIDL 替换。 - Shell 始终会调用 ShellFolder 实例来解析
SHITEMID
。这允许您将任何信息存储到SHITEMID
结构中。vvv 示例将所有信息存储到SHITEMID
中。 - 出于性能原因,需要缓存项。当一个以上的视图可用于编辑文件夹时,请务必刷新缓存。
对于 VVV 示例,我开始将所有项存储在 std::vector
中,并使用索引作为 PIDL。当尝试向示例添加编辑功能时,这被证明是一条死胡同。当前的示例使用唯一的槽位 ID 来标识项。这还允许示例支持子文件夹和相同的显示名称。
基本设计理念
Shell 命名空间扩展由几个 COM 对象协同工作。四个最重要的对象是
- 支持
IShellView
接口的 COM 对象。 - 支持
IShellFolder(2)
接口的 COM 对象。 - 支持
IEnumIDList
接口的 COM 对象。 - 支持
IDataObject
接口的 COM 对象。
IShellView 接口
Windows (shell32.dll) 为支持 IShellView
接口的 COM 对象提供了默认实现。此默认系统 COM 对象支持常见的视图模式(详细信息、图标等),并且可以处理大多数事件。系统视图对象使用回调接口 IShellFolderViewCBImpl
来传递事件并允许控制系统视图对象的行为。IShellFolderImpl
类默认使用此标准系统视图对象。使用默认 ShellView 的优点是与浏览器 UI 的其余部分具有相同的外观和行为。
IShellFolder(2) 接口
命名空间扩展的核心是 ShellFolder
对象。此对象负责维护文件夹内的项。对这些项的每个操作都由 ShellFolder
对象控制。提供的 IShellFolderImpl
模板类旨在轻松创建 ShellFolder
对象。它需要两个模板参数:TDerived
和 TItem
。TDerived
是派生类的类型名称。所有预处理的事件都将转发到此类。TDerived
类需要提供以下函数
CComPtr<IEnumIDList> CreateEnumIDList(HWND hwnd, DWORD grfFlags); SFGAOF GetAttributeOf(unsigned int cidl, const TItem& item, SFGAOF sfgofMask) const;
所有其他函数都由 IShellFolderImpl
类提供默认实现。
TItem
是 PIDL 项包装器的类型名称。IShellFolderImpl
将每个传入的 PIDL 包装在 TItem
对象中。TItem
类需要支持以下函数
TItem(const SHITEMID& shitemid); CString GetDisplayName(SHGDNF shgdnf = SHGDN_NORMAL) const; int Compare(const TItem& item, int nCompareBy, bool bCanonicalOnly) const;
IEnumIDList 接口
ShellFolder 必须向 ShellView 返回一个枚举器对象。ShellView 使用此枚举器对象来填充其内部显示列表。框架目前依赖 ATL 类 CComEnumOnSTL
来提供此功能。
IDataObject 接口
为了与复制/粘贴以及拖放进行交互,ShellFolder 必须支持一个 IDataObject
对象,该对象可以与 Shell 剪贴板操作进行交互。框架提供了一个 CShellFolderDataObjectImpl
模板类和一些剪贴板格式处理程序来实现所需的功能。
错误处理
框架依赖异常来处理运行时错误。预计会抛出两种类型的异常(另请参阅第一部分):_com_error
异常和派生自 std::exception
的异常。IShellFolder
的一些函数具有 HWND
参数,可用于向用户显示消息。如果发生异常,IShellFolderImpl
类会捕获它并将异常转发给 OnError
函数。OnError
的默认实现为空,但派生类可以将错误条件转换为字符串并向用户显示。下面显示了 VVV 示例代码
void OnError(HRESULT hr, HWND hwnd, EErrorContext /*errorcontext*/) { CString strMsg = LoadString(IDS_SHELLFOLDER_CANNOT_PERFORM) + FormatLastError(static_cast<DWORD>(hr)); IsolationAwareMessageBox(hwnd, strMsg, LoadString(IDS_SHELLEXT_ERROR_CAPTION), MB_OK | MB_ICONERROR); }
特点
在开始详细讨论实现细节之前,最好列出我们的示例命名空间扩展应能够执行的功能列表
只读功能
- 查看存储在 .vvv 文件中的项。
- 在详细视图模式下对项进行排序。
- 支持自定义项信息提示。
- 支持自定义属性页。
- 支持子文件夹。
编辑功能
- 重命名项,
- 删除项,
- 使用自定义属性页更改项的属性,
- 将项复制/剪切粘贴/拖放到文件系统(从 .vvv 文件),
- 将项复制/剪切粘贴/拖放(从文件系统到 .vvv 文件)。
ShellFolder 的 COM 注册
与所有其他 Shell 扩展一样,命名空间扩展必须先注册为 COM 对象才能使用。框架为此目的提供了注册函数和 ATL 注册脚本。Windows 98 需要一个特殊的注册脚本,因为它没有 shell32.dll 的 5.0 版。下面的代码显示了示例的静态注册函数,该函数将在需要注册时由 ATL 调用。
static HRESULT WINAPI CVVVSample::UpdateRegistry(BOOL bRegister) throw() { return IShellFolderImpl<CShellFolder, CVVVItem>::UpdateRegistry( IDR_SHELLFOLDER, IDR_SHELLFOLDER_WIN98, bRegister, L"Sample ShellExtension ShellFolder", wszVVVExtension, IDS_SHELLFOLDER_TYPE); }
构造和初始化
需要两个模板参数。第一个是类的类名。另一个是能够包装 PIDL 并对其进行操作的类的名称。许多请求都转发给 CVVVItem
类。
class ATL_NO_VTABLE CShellFolder : public IShellFolderImpl<CShellFolder, CVVVItem>,
示例的构造函数“注册”了文件夹支持的列。当 ShellView
对象被配置为以详细模式显示项时,它将查询这些列。
CShellFolder::CShellFolder() { // Register the columns the folder supports // in 'detailed' mode. RegisterColumn(IDS_SHELLEXT_NAME, LVCFMT_LEFT); RegisterColumn(IDS_SHELLEXT_SIZE, LVCFMT_RIGHT); }
IShellFolderImpl
类对 IShellFolder::Initialise
函数有一个默认实现。此默认实现仅存储根文件夹。可以使用 GetRootFolder
函数检索此根文件夹。
只读功能
在 ShellFolder 中查看项
ShellFolder
的主要任务是管理该文件夹中的项列表。ShellView
对象将调用 IShellFolder::EnumObjects
函数来检索所有项的枚举器。IShellFolderImpl
将此请求转发给 CreateEnumIDList
函数。此函数需要由用户类实现。
CComPtr<IEnumIDList> CreateEnumIDList(HWND /*hwnd*/, DWORD grfFlags) { auto_ptr<vector<CVVVItem> > qitems = CVVVFile(GetPathFolderFile(), _strSubFolder).GetItems(grfFlags); return CEnumIDList::CreateInstance(GetUnknown(), qitems); }
要构造请求的枚举器对象,示例会向 CVVVFile
对象请求当前项列表。CVVVFile
对象打开并解析当前的 .vvv 文件,并返回一个包含项的 std::vector
。然后使用此向量创建围绕它的枚举器对象。此枚举器对象基于 ATL CComEnumOnSTL
模板基类。Shell 将使用枚举器来获取所有 PIDL,然后释放枚举器对象。
对 ShellFolder 中的项进行排序
当系统 ShellView
以“详细”模式显示项时,用户可以通过单击 ListView
的标题来对项进行排序。ShellView
会将此事件转发给 ColumnClick
函数。框架的默认实现返回 S_FALSE
,表示系统 ShellView
应处理此事件。然后,系统 ShellView
对象将对列进行排序。如果框架检测到它运行的版本 shell32.dll 不支持此默认处理(Win 95\Win 98),它将显式触发 ShellView
进行排序。在排序过程中,ShellView
每次需要比较两个项以进行排序时都会调用 IShellFolder::CompareIDs
。IShellFolderImpl
将通过将 PIDL 包装在 TItems
中,然后将调用转发给项来处理此请求。下面的代码显示了示例如何处理此问题
int CVVVItem::Compare(const CVVVItem& item, int nCompareBy, bool /*bCanonicalOnly*/) const { switch (nCompareBy) { case COLUMN_NAME: return CompareByName(item); case COLUMN_SIZE: return UIntCmp(_nSize, item._nSize); default: ATLASSERT(!"Illegal nCompare option detected"); RaiseException(); } }
为 ShellFolder 中的项提供信息提示
当用户将鼠标指针悬停在某个项上时,系统 ShellView
会显示该项的信息提示,或在状态栏中显示状态文本。ShellFolder
对象确定并控制要显示的文本。ShellView
将调用 GetUIObjectOf
并请求 IQueryInfo
接口。IShellFolderImpl
类将检查是否仅为一个项请求了该接口,然后将 PIDL 包装在 TItem
中。然后查询此 TItem
以获取其工具提示文本。如果此字符串不为空,则创建一个 QueryInfo
对象并返回。
支持子文件夹
根据 Shell Folder 的要求,可能需要支持子文件夹。要支持子文件夹,必须遵循以下步骤
第一步是为是子文件夹的项返回 SFGAO_FOLDER
属性。然后,Explorer 中的树视图可用于访问这些子文件夹。当 Shell 需要访问某个文件夹时,它将遵循以下过程
- 创建
ShellFolder
实例。 - 调用
BindToObject
为绑定到正确子文件夹的新实例。此子文件夹可能有多级深度。 - 释放原始
ShellFolder
。
IShellFolderImpl
类支持处理子文件夹。它会将包含子文件夹的项列表解析为 TItem
的向量。然后,它将创建一个新的 ShellFolder
实例,初始化根文件夹,并将项向量传递给 InitializeSubFolder
函数。InitializeSubFolder
函数必须由派生类实现。示例代码演示了这一点。
为了支持用户在文件夹视图中打开子文件夹的标准功能,ShellFolder
必须为子文件夹项提供上下文菜单。
HRESULT CShellFolder::OnDfmMergeContextMenu( IDataObject* pdataobject, UINT /*uFlags*/, QCMINFO& qcminfo) { CCfShellIdList cfshellidlist(pdataobject); if (cfshellidlist.GetItemCount() == 1) { // Add 'open' if only 1 item is selected. CMenu menu(true); menu.AddDefaultItem(ID_DFM_CMD_OPEN, _T("&Open")); MergeMenus(qcminfo, menu); // Note: XP will automatic make // the first menu item the default. // Win98, ME and 2k don't do this, // so must add as default item. } return S_OK; }
实际的“打开”请求必须得到处理
void CShellFolder::OnOpen(HWND hwnd, IDataObject* pdataobject) { CVVVItems items; RetrieveItems(pdataobject, items); ATLASSERT(items.size() == 1); if (items[0].IsFolder()) { CPidl pidlFolder(items[0].CreateShellItemIdList()); GetShellBrowser().BrowseObject(pidlFolder, SBSP_DEFBROWSER | SBSP_RELATIVE); } else { CString strMessage = _T("Open on: ") + items[0].GetName(); IsolationAwareMessageBox(hwnd, strMessage, _T("Open"), MB_OK | MB_ICONQUESTION); } }
示例支持对文件夹和非文件夹的打开操作。对文件夹项的“打开”命令实际上会打开子文件夹,但非文件夹项只会显示一个消息框。
编辑功能
为编辑功能做准备
在编辑功能开始工作之前,需要满足两个重要要求。当用户创建一项选择并希望对其执行操作时,ShellView
会询问 ShellFolder
她可以对该项集合做什么。IShellFolderImpl
类提供了默认实现,该实现会首先尝试检测是否存在全局设置。如果不存在全局设置,它将为选择中的每个项调用 GetAttributeOf
。下一个示例代码显示了如何处理全局设置请求。示例将返回可以重命名、删除、复制和移动项。如果只选择了一个项,它将报告支持属性选项。sfgofMask
参数包含调用方真正想要知道的位掩码,可用于跳过昂贵的计算。
// Purpose: called by MSF to detect if a // global attribute setting exists. SFGAOF CShellFolder::GetAttributesOfGlobal(UINT cidl, SFGAOF /*sfgofMask*/) { // Tell the shell what it can do with // an item inside a VVV file. SFGAOF sfgaof = SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_CANCOPY | SFGAO_CANMOVE; if (cidl == 1) { sfgaof |= SFGAO_HASPROPSHEET; } return sfgaof; }
第二个要求是我们需要告诉系统文件夹视图,它应该为我们的 ShellFolder
生成的“更改”事件进行注册。要告知系统文件夹视图,我们需要在创建系统文件夹视图时提供一个 IShellFolderViewCB
接口,并处理 SFVM_GETNOTIFY
消息。
框架为 IShellFolderViewCB
接口提供了默认的 COM 对象实现。以下代码示例显示了如何使用此默认实现。
// Create a derived class and set the event bitmask.
CShellFolderViewCB() :
IShellFolderViewCBImpl<CShellFolderViewCB>(
SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER | SHCNE_DELETE)
{
};
// Create a helper function that can // create an instance of this COM // object and initialize it. static CComPtr<IShellFolderViewCB> CreateInstance(const ITEMIDLIST* pidlFolder) { CComObject<CShellFolderViewCB>* pinstance; RaiseExceptionIfFailed( CComObject<CShellFolderViewCB>::CreateInstance( &pinstance)); CComPtr<IShellFolderViewCB> shellfolderviewcb( pinstance); pinstance->SetFolder(pidlFolder); return shellfolderviewcb; }
// Replace the default framework // CreateShellFolderViewCB. CComPtr<IShellFolderViewCB> CShellFolder::CreateShellFolderViewCB() { return CShellFolderViewCB::CreateInstance(GetRootFolder()); }
提示:在测试编辑功能时,打开两个对同一文件夹的 Explorer 视图很有用。这可以快速反馈一个操作的通知是否到达了所有视图。
重命名项
要支持项的重命名,ShellFolder
函数 GetAttributeOf(Global)
需要返回 SFGAO_CANRENAME
。这将触发 FolderView
向用户提供“重命名”选项。当用户重命名了该项后,视图将调用 IShellFolder::SetNameOf
并传递新名称。IShellFolderImpl
将 PIDL 包装在 TItem
中,然后将调用传递给 OnSetNameOf
函数以执行实际更新。如果名称实际上可以更改,IShellFolderImpl
将触发 SHCNE_RENAMEITEM
通知以更新所有打开的 Explorer 视图。下面的代码显示了示例如何处理重命名请求
void CShellFolder::OnSetNameOf(HWND /*hwnd*/, CVVVItem& item, const TCHAR* szNewName, SHGDNF shgndf) { RaiseExceptionIf(shgndf != SHGDN_NORMAL && shgndf != SHGDN_INFOLDER); item.SetDisplayName(szNewName, shgndf); CVVVFile(GetPathFolderFile()).SetItem(item); }
删除项
IShellFolderImpl
支持删除项。当 ShellView
查询项的属性(转发到 GetAttributesOf
函数)并且 ShellFolder
返回 SFGAO_CANDELETE
时,Explorer 会向用户提供删除该项(些)的选项。当用户请求删除当前选定的项时,请求将被转发到 ShellFolder
。IShellFolderImpl
会执行快速检查以确保每个项都具有 SFGAO_CANDELETE
属性,然后调用 OnDelete
(Titem
s)。下面的代码显示了示例如何处理此问题
long CShellFolder::OnDelete(HWND hwnd, CVVVItems& items) { if (hwnd != NULL && !UserConfirmsFileDelete(hwnd, items)) return 0; // user wants to abort the // file deletion process. CVVVFile(GetPathFolderFile(), m_strSubFolder).DeleteItems(items); return SHCNE_DELETE; }
删除项后,OnDelete
函数应返回必须广播到系统的事件。此事件可确保所有其他视图更新自身。
编辑属性
通过属性页重命名项的另一种方法是使用属性页。这还允许用户查看和更改项的其他属性。当 IShellFolderImpl
收到显示“属性”对话框的请求时,它将调用 OnProperties
函数。
long CShellFolder::OnProperties(HWND hwnd, CVVVItems& items) { ATLASSERT(items.size() == 1); CVVVItem& item = items[0]; long wEventId; if (CVVVPropertySheet(item, this).DoModal(hwnd, wEventId) > 0 && wEventId != 0) { CVVVFile vvvfile(GetPathFolderFile()); vvvfile.SetItem(item); } return wEventId; }
shellfolder
控制属性对话框窗口的 GUI。由实现来选择合适的 GUI 窗口。它可以是简单的对话框,也可以是属性表。框架提供了一个简单的 CPropertySheet
类,可以与 ATL CSnapInPropertyPageImpl
类结合使用来创建属性表(带页)。
剪贴板交互和拖放
IShellFolderImpl
内置支持剪贴板事务和拖放操作。要启用拖放支持,派生类必须
将 IDropTarget
接口添加到接口映射中。
COM_INTERFACE_ENTRY(IDropTarget) // enable drag and drop support.
创建一个支持 IDataObject
接口的 CShellFolderDataObject
类。要实现此类,框架提供了 CShellFolderDataObjectImpl
模板基类。参与 Shell 拖放操作的 DataObject
的特别之处在于,它需要接受额外的剪贴板格式。Windows 提供 CIDLData_CreateFromIDArray
函数来创建此类 DataObject
。CShellFolderDataObjectImpl
类是对此 DataObject
的包装。除了包装之外,它还允许已注册的剪贴板格式优先。下面的示例代码显示了示例如何注册两个标准剪贴板格式(CFSTR_FILEDESCRIPTOR
和 CFSTR_FILECONTENTS
)的处理器。
void CShellFolderDataObject::Init(const ITEMIDLIST* pidlFolder, UINT cidl, const ITEMIDLIST** ppidl, IPerformedDropEffectSink* pperformeddropeffectsink) { __super::Init(pidlFolder, cidl, ppidl, pperformeddropeffectsink); auto_ptr<CCfHandler> qfiledescriptorhandler(new CCfFileDescriptorHandler(this)); RegisterCfHandler(qfiledescriptorhandler); auto_ptr<CCfHandler> qfilecontentshandler(new CCfFileContentsHandler(this)); RegisterCfHandler(qfilecontentshandler); }
支持哪些剪贴板格式以及如何将 PIDL 转换为例如文件系统对象,完全取决于实现。框架仅提供骨架,以便轻松将该功能打包到剪贴板格式处理程序类中。
为剪贴板操作选择项
将项复制到剪贴板是一个由多个步骤组成的进程。用户首先在视图窗口中选择项。然后,ShellView
将调用 GetUIObjectOf
函数并请求这些选定项的 IDataObject
接口。框架会将此请求转发给 CreateDataObject
函数。下面的示例代码显示了处理此请求的标准模式。文件夹对象创建一个新的 CShellFolderDataObject
COM 对象。
CComPtr<IDataObject> CShellFolder::CreateDataObject(const ITEMIDLIST* pidlFolder, UINT cidl, const ITEMIDLIST** ppidl) { return CShellFolderDataObject::CreateInstance(pidlFolder, cidl, ppidl, this); }
当 ShellView
拥有此 DataObject
后,它将根据用户的下一步操作来决定 ShellView
将如何处理它。用户可以删除、复制、剪切或请求选定项的上下文菜单。
将 DataObject 复制到剪贴板
由于 ShellView
一起传递 DataObject
和复制请求,因此 IShellFolderImpl
类可以自行处理复制操作。它将检查选定项是否都设置了 SFGAO_CANCOPY
,然后将 DataObject
放入剪贴板。
void IShellFolderImpl::OnCopy(HWND, IDataObject* pdataobject)
{
VerifyAttribute(pdataobject, SFGAO_CANCOPY);
CCfPreferredDropEffect::Set(pdataobject, DROPEFFECT_COPY);
RaiseExceptionIfFailed(OleSetClipboard(pdataobject));
}
将 DataObject 剪切到剪贴板
“剪切”操作也完全由框架处理。与复制操作的主要区别在于,必须通知 ShellView
一个“剪切”DataObject
已放入剪贴板。这将触发 ShellView
使“剪切”项变暗,并允许用户使用“Esc”键取消剪切操作。
void IShellFolderImpl::OnCut(HWND, IDataObject* pdataobject)
{
VerifyAttribute(pdataobject, SFGAO_CANMOVE);
CCfPreferredDropEffect::Set(pdataobject, DROPEFFECT_MOVE);
RaiseExceptionIfFailed(OleSetClipboard(pdataobject));
ShellFolderView_SetClipboard(GetHwndOwner(), DFM_CMD_CUT);
}
接收外部 DataObject
ShellFolder
可以通过两种方式接收外部 DataObject
。用户可以从剪贴板粘贴 DataObject
,也可以将项的选择拖放到 ShellView
上。IShellFolderImpl
类支持这两种操作。派生的 ShellFolder
类需要实现两个函数来接收外部 DataObject
:IsSupportedClipboardFormat
和 AddItemsFromDataObject
。
下面的代码来自 vvv 示例。当 DataObject
支持 CF_HDROP
(文件列表)剪贴板格式时,IsSupportedClipboardFormat
返回 true
,不支持其他格式。CCfHDrop
是框架提供的一个类。
bool CShellFolder::IsSupportedClipboardFormat( IDataObject* pdataobject) { return CCfHDrop::IsFormat(pdataobject); }
当用户“粘贴”了某些内容或拖放对象进入 ShellView
窗口时,会调用 IsSupportedClipboardFormat
。
当 ShellFolder
支持剪贴板格式时,框架将调用 AddItemsFromDataObject
函数。下面的示例代码演示了如何处理此问题
DWORD CShellFolder::AddItemsFromDataObject( DWORD dwDropEffect, IDataObject* pdataobject) { CCfHDrop cfhdrop(pdataobject); unsigned int nFiles = cfhdrop.GetFileCount(); for (unsigned int i = 0; i < nFiles; ++i) { AddItem(cfhdrop.GetFile(i)); } return dwDropEffect; }
根据用户的操作,dwDropEffect
参数设置为 DROPEFFECT_MOVE
或 DROPEFFECT_COPY
。VVV 示例始终将文件从文件系统复制到 VVV 文件。其他实现可以执行文件系统文件移动作为优化。
未来扩展
当前框架实现可以进行很多改进;支持 XP 任务栏、工具栏按钮、性能改进、根目录示例、更好的子文件夹支持等。
当前版本为创建 ShellFolder
命名空间扩展提供了基本基础。有了基本实现后,可以轻松添加小的改进。