扩展 MFC Shell 控件功能






4.11/5 (9投票s)
重写 CMFCShellTreeCtrl 和 CMFCShellListCtrl 类。
引言
MFC Shell 控件(CMFCShellTreeCtrl
和 CMFCShellListCtrl
)都存在严重的限制。对于 CMFCShellTreeCtrl
,无法设置自定义的根文件夹,树视图始终从“桌面”开始。至于 CMFCShellListCtrl
,没有明确的过滤文件的方法。开发人员显然可以重写 EnumObjects,但为每个派生类这样做效率不高。此外,开发人员无法为 CMFCShellTreeCtrl
或 CMFCShellListCtrl
项分配自定义数据。为了克服所有这些问题,我必须开发相应的派生类:CMFCShellTreeCtrlEx
和 CMFCShellListCtrlEx
。
背景
首先,我必须扩展两个 Shell 控件都使用的 AFX_SHELLITEMINFO
结构,通过添加 DWORD_PTR
dwItemData 成员来保存自定义项数据。新的 AFX_SHELLITEMINFOEX
结构定义在新的 CMFCShellUtils
类头文件中。为了在 Shell 树视图中支持自定义项数据,在 CMFCShellTreeCtrlEx
中添加了以下函数:
// Same as GetItemData in CTreeCtrl DWORD_PTR GetItemDataEx(HTREEITEM hItem) const; // Same as SetItemData in CTreeCtrl BOOL SetItemDataEx(HTREEITEM hItem, DWORD_PTR dwData);
可重写函数
// Called to free custom data when shell tree item is deleted virtual void FreeItemData(HTREEITEM hItem, DWORD_PTR dwItemData); // Notifies derived class that a new item has been inserted virtual void OnItemInserted(HTREEITEM hItem, LPCTSTR szFolderPath);
同样,在 CMFCShellListCtrlEx
中添加了以下函数:
// Same as GetItemData in CListCtrl DWORD_PTR GetItemDataEx(int nItem) const; // Same as SetItemData in CListCtrl BOOL SetItemDataEx(int nItem, DWORD_PTR dwData);
可重写函数
// Called to free custom data when shell list item is deleted virtual void FreeItemData(int nItem, DWORD_PTR dwItemData); // Notifies derived class that a new item has been inserted virtual void OnItemInserted(int nItem);
新的 CMFCShellTreeCtrlEx 方法
构造函数
CMFCShellTreeCtrlEx(DWORD dwProp = 0);
DWORD dwProp
指定其他 Shell 树视图属性。此参数可以是以下标志的组合:
SHELLTREEEX_QUICK_CHLDDETECT
– 如果设置了此标志,Shell 树视图将使用 FGAO_HASSUBFOLDER|SFGAO_REMOVABLE
文件夹属性而不是 SFGAO_HASSUBFOLDER|SFGAO_FILESYSANCESTOR
来检测当前文件夹是否包含子文件夹。在这种情况下,Shell 树视图看起来更像 Windows 资源管理器中的视图。在 CMFCShellTreeCtrl
中,大多数项最初都是可展开的,因为几乎每个文件夹都具有 SFGAO_FILESYSANCESTOR
属性。
SHELLTREEEX_KEEP_CHILDREN
– CMFCShellTreeCtrl
每次折叠或展开父项时都会删除并重新创建子项。如果设置了此标志,Shell 树视图的操作效率更高,可以保持先前插入的项完好无损。为了避免在当前 CMFCShellTreeCtrlEx
类实例之外创建的新文件夹未在树视图中显示的情况,开发人员可以保留适当的快捷键(例如 F5)来调用 CMFCShellTreeCtrlEx::RefreshEx
(见下文)。
SHELLTREEEX_EXPAND_ALL
– 如果设置了此标志,CMFCShellTreeCtrlEx
在创建时将展开所有文件夹和子文件夹。如果未设置自定义根文件夹,则忽略此选项。
void SetRootFolder(LPCTSTR szRootDir, BOOL bFullPath = FALSE, DWORD *pdwProp = NULL);
设置自定义根文件夹。
参数
LPCTSTR szRootDir
– 完全限定的文件夹路径。
BOOL bFullPath
– TRUE
表示将完全限定的文件夹路径(szRootDir
参数)显示为根文件夹名称,FALSE
表示否则。
DWORD *pdwProp
- 指向 Shell 树视图属性的可选指针(参见上面的 CMFCShellTreeCtrlEx
构造函数)。
void RefreshEx();
CMFCShellTreeCtrl::Refresh
的替代方法。
void SetFlagsEx(DWORD dwFlags, BOOL bRefresh);
CMFCShellTreeCtrl::SetFlags
的替代方法。
新的 CMFCShellListCtrlEx 方法
BOOL CopyItems(const CMFCShellListCtrlEx& cSrcListCtrl, const CUIntArray& cItemPosArr);
将选定的项从一个 Shell 列表控件复制到另一个。
参数
const CMFCShellListCtrlEx& cSrcListCtrl
– 对源 Shell 列表控件的引用,其项将被复制到当前的 Shell 列表控件。
const CUIntArray&
cItemPosArr
- 对 CUIntArray
的引用,其中包含要复制的项的索引。
返回值
如果选定的项已成功复制,则返回 TRUE
,否则返回 FALSE
。
可重写函数
virtual void PreEnumObjects(LPCTSTR szFolderPath);
在 Shell 列表控件开始枚举当前文件夹项之前调用。
参数
LPCTSTR szFolderPath
– 当前文件夹的完全限定路径。
virtual BOOL IncludeItem(LPCTSTR szFileName);
参数
LPCTSTR szFileName
– 要添加到 Shell 列表控件的项的名称。要构造一个完全限定的项路径名,请使用传递给 PreEnumObjects
方法(见上文)的 szFolderPath
参数。例如,如果 szFileName
是“Default.aspx”,szFolderPath
是“C:\MyProjects\MyWebsite”,那么完全限定的项路径名将是“C:\MyProjects\MyWebsite\Default.aspx”。
如果应包含该项,则返回 TRUE,否则返回 FALSE。
使用代码
在许多情况下,我们需要两个 Shell 列表控件来从当前文件夹或多个文件夹中选择特定的文件。通常,第一个列表控件包含所有可用文件,第二个列表控件包含用户选择的文件。两个按钮(“添加”、“删除”)可以完成这项工作。第一个列表控件也可以包含“剩余”(即“未选择”)文件,而不是所有可用文件。旧版 Visual SourceSafe 就是这样做的。对我来说,第二个选项更可取。在这种情况下,选定的文件只是从一个列表控件移动到另一个。
此演示项目允许从多个文件夹中选择文件。每个文件夹的选定文件列表使用 SetItemDataEx 方法进行记忆。该应用程序还演示了 CMFCShellTreeCtrlEx::SetRootFolder
方法的用法。
主对话框窗口(CMFCShellExtensionDlg)包含三个 Shell 控件,由以下变量表示:
CMFCShellTreeCtrlEx m_cTreeCtrl; // Shell tree view CProjectListCtrl m_cListCtrlSel; // Shell list control containing selected files CProjectListCtrl m_cListCtrlRem; // Shell list control containing remaining (not selected) files
CProjectListCtrl 是一个派生自 CMFCShellListCtrlEx 的类。它支持以下文件过滤模式:
enum LISTFILTER { LISTFILTER_ALL, // No filtering, all folder files are displayed in the list LISTFILTER_SELECTED, // Displays folder files selected by the user LISTFILTER_REMAINING, // Displays folder files not selected by the user (remaining files) LISTFILTER_NONE // Not displaying any files (empty list) };
初始过滤模式会传递给 CProjectListCtrl 构造函数:
CProjectListCtrl(LISTFILTER nFilter = LISTFILTER_NONE);
并且可以通过 SetFilter 函数更改:
void SetFilter(LISTFILTER nFilter) { m_nFilter = nFilter; }
CMFCShellExtensionDlg 将所有选定的文件存储在 m_cProjFileMap 变量中:
CProjFilesArray m_cProjFileMap;
CProjFilesArray 是一个派生自 CMapStringToOb 的类,其中 CString 包含完整的文件夹路径名,CObject 是一个 CStringArray,包含选定的文件名。
Shell 控件在 CMFCShellExtensionDlg::OnInitDialog 中初始化:
BOOL CMFCShellExtensionDlg::OnInitDialog() { CDialogEx::OnInitDialog(); SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon CheckRadioButton(IDC_RADIO_DEFAULT, IDC_RADIO_CUSTOM, IDC_RADIO_DEFAULT); // Not displaying subfolders m_cListCtrlSel.SetItemTypes(SHCONTF_NONFOLDERS); m_cListCtrlRem.SetItemTypes(SHCONTF_NONFOLDERS); // Passing project files map pointer to both shell list controls m_cListCtrlSel.SetProjectFiles(&m_cProjFileMap); m_cListCtrlRem.SetProjectFiles(&m_cProjFileMap); // Setting list control filters m_cListCtrlSel.SetFilter(LISTFILTER_SELECTED); m_cListCtrlRem.SetFilter(LISTFILTER_REMAINING); // Selecting and expanding top (desktop) folder item HTREEITEM hParentItem = m_cTreeCtrl.GetRootItem(); m_cTreeCtrl.SelectItem(hParentItem); m_cTreeCtrl.Expand(hParentItem, TVE_EXPAND); return TRUE; }
每次用户选择一个文件夹时,选定的 Shell 树节点都会被赋予相应的 cProjFileMap 元素值(指向 CStringArray 的指针),并且两个列表控件都会刷新:
void CMFCShellExtensionDlg::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult) { HTREEITEM hItem = m_cTreeCtrl.GetSelectedItem(); CString cFolderPath; if (m_cTreeCtrl.GetItemPath(cFolderPath, hItem)) { // Retrieving an existing (or adding a new: bAddIfNotFound = TRUE) project files array CStringArray *pFilesArr = m_cProjFileMap.GetFiles(cFolderPath, TRUE); // and using the resultant pointer as the selected tree item data m_cTreeCtrl.SetItemDataEx(hItem, (DWORD_PTR)pFilesArr); // Refreshing both list controls contents m_cListCtrlSel.DisplayFolder(cFolderPath); m_cListCtrlRem.DisplayFolder(cFolderPath); } }
// GetFiles 方法在这里
// Getting the list of selected files (a CStringArray object) corresponding to the folder // specified by szFolderPath parameters. // If bAddIfNotFound is TRUE and the map does not contain the required key // a new entry with an empty string array is added to the map. CStringArray *CProjFilesArray::GetFiles(LPCTSTR szFolderPath, BOOL bAddIfNotFound) { CStringArray *pArray = NULL; BOOL bFound = Lookup(szFolderPath, (CObject*&)pArray); if (!bFound && bAddIfNotFound) { pArray = new CStringArray; SetAt(szFolderPath, pArray); } return pArray; }
每当单击“添加到项目文件”或“从项目文件中删除”按钮时,选定的文件就会从一个列表控件移动到另一个:
void CMFCShellExtensionDlg::CopyFiles(BOOL bDelete) { HTREEITEM hItem = m_cTreeCtrl.GetSelectedItem(); CString cFolderPath; CStringArray *pFilesArr = (hItem && m_cTreeCtrl.GetItemPath(cFolderPath, hItem)) ? m_cProjFileMap.GetFiles(cFolderPath, FALSE) : NULL; if (!pFilesArr) return; // Moving files from one list control to the other CProjectListCtrl *pSrcList = bDelete ? &m_cListCtrlSel : &m_cListCtrlRem; CProjectListCtrl *pDstList = bDelete ? &m_cListCtrlRem : &m_cListCtrlSel; CUIntArray cItemPosArr; POSITION pos = pSrcList->GetFirstSelectedItemPosition(); while (pos) { int nItem = pSrcList->GetNextSelectedItem(pos); cItemPosArr.Add((UINT)nItem); } int nCount = cItemPosArr.GetSize(); if (nCount > 0) { // Copying selected files from source to destination list control pDstList->CopyItems((const CMFCShellListCtrlEx&)*pSrcList, cItemPosArr); // and removing them from the source list control pos = pSrcList->GetFirstSelectedItemPosition(); while (pos) { int nItem = pSrcList->GetNextSelectedItem(pos); pSrcList->DeleteItem(nItem); pos = pSrcList->GetFirstSelectedItemPosition(); } } // Updating project files map pFilesArr->RemoveAll(); nCount = m_cListCtrlSel.GetItemCount(); for (int i = 0; i < nCount; i++) { CString cFileName = m_cListCtrlSel.GetItemText(i, CMFCShellListCtrl::AFX_ShellList_ColumnName); pFilesArr->Add(cFileName); } }
下面展示了 CMFCShellListCtrlEx::CopyItems 方法:
BOOL CMFCShellListCtrlEx::CopyItems(const CMFCShellListCtrlEx& cSrcListCtrl, const CUIntArray& cItemPosArr) { if (!m_psfCurFolder) return FALSE; int nItemCount = GetItemCount(); BOOL bResult = TRUE; // Check if non of the items to be copied is already in the list for (int i = 0; i < cItemPosArr.GetSize(); i++) { int nItem = cItemPosArr[i]; LPAFX_SHELLITEMINFOEX pItem = (nItem >= 0 && nItem < cSrcListCtrl.GetItemCount()) ? (LPAFX_SHELLITEMINFOEX)cSrcListCtrl.GetItemData(nItem) : NULL; BOOL bRemove = pItem == NULL || pItem->pParentFolder == NULL; if (!bRemove) { CString cItemName = CMFCShellUtils::GetDisplayName(pItem->pParentFolder, pItem->pidlRel, FALSE); for (int j = 0; j < nItemCount; j++) { CString cName = GetItemText(j, AFX_ShellList_ColumnName); if (!cName.CompareNoCase(cItemName)) { bRemove = TRUE; break; } } // If item already exists => remove the appropriate m_cCopyNamesArr element if (!bRemove) m_cCopyNamesArr.Add(cItemName); } } bResult = m_cCopyNamesArr.GetSize() > 0; // If copy items array isn't empty... if (bResult) { CWaitCursor wait; SetRedraw(FALSE); // call EnumObjects to add new files to the list bResult = SUCCEEDED(EnumObjects(m_psfCurFolder, m_pidlCurFQ)); m_cCopyNamesArr.RemoveAll(); // and re-sort the list if (bResult && (GetStyle() & LVS_REPORT)) Sort(AFX_ShellList_ColumnName); SetRedraw(TRUE); RedrawWindow(); } return bResult; }