65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展 MFC Shell 控件功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (9投票s)

2016年1月21日

CPOL

4分钟阅读

viewsIcon

27598

downloadIcon

2359

重写 CMFCShellTreeCtrl 和 CMFCShellListCtrl 类。

 

引言

MFC Shell 控件(CMFCShellTreeCtrlCMFCShellListCtrl)都存在严重的限制。对于 CMFCShellTreeCtrl,无法设置自定义的根文件夹,树视图始终从“桌面”开始。至于 CMFCShellListCtrl,没有明确的过滤文件的方法。开发人员显然可以重写 EnumObjects,但为每个派生类这样做效率不高。此外,开发人员无法为 CMFCShellTreeCtrlCMFCShellListCtrl 项分配自定义数据。为了克服所有这些问题,我必须开发相应的派生类:CMFCShellTreeCtrlExCMFCShellListCtrlEx

背景

首先,我必须扩展两个 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 bFullPathTRUE 表示将完全限定的文件夹路径(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;
}

 

© . All rights reserved.