管理文件系统

Shell 提供了多种管理文件系统的方法。 Shell 提供了一个函数 SHFileOperation,它允许应用程序以编程方式移动、复制、重命名和删除文件。 Shell 还支持一些其他文件管理功能。

  • HTML 文档可以 连接到 相关文件,如图形文件或样式表。 移动或复制文档时,也会自动移动或复制连接的文件。
  • 对于可供多个用户使用的系统,可以按用户管理文件。 用户可以轻松访问其数据文件,但不能访问属于其他用户的文件。
  • 如果添加或修改了文档文件,则可以将其添加到 Shell 的最近使用的文档列表中。 当用户单击“开始”菜单上的“ 文档” 命令时,将显示指向文档的链接列表。

本文档讨论这些文件管理技术的工作原理。 然后概述了如何使用 Shell 移动、复制、重命名和删除文件,以及如何管理回收站中的对象。

Per-User文件管理

Windows 2000 Shell 允许将文件与特定用户关联,以便对其他用户隐藏文件。 就文件系统而言,这些文件存储在用户的配置文件文件夹下,通常为 Windows 2000 系统上的 C:\Documents and Settings\Username\。 此功能允许许多个人使用同一台计算机,同时维护其他用户文件的隐私。 不同的用户可以有不同的程序可用。 它还为管理员和应用程序提供了一种简单的方法来存储诸如初始化 (.ini) 或链接 (.lnk) 文件等内容。 因此,应用程序可以为每个用户保留不同的状态,并在需要时轻松恢复该特定状态。 还有一个配置文件文件夹,用于存储所有用户通用的信息。

由于确定登录的用户及其文件所在的位置不方便,因此标准每用户文件夹是特殊文件夹,由 CSIDL 标识。 例如,每用户 Program Files 文件夹的 CSIDL CSIDL_PROGRAMS。 如果应用程序使用每用户 CSIDL 之一调用 SHGetFolderLocationSHGetFolderPath ,则函数将返回指向项标识符列表的指针, (PIDL) 或适用于当前登录用户的路径。 如果应用程序需要检索配置文件文件夹的路径或 PIDL,则会 CSIDL_PROFILE其 CSIDL。

“我的文档”和“我的图片”文件夹

桌面上的标准图标之一是 “我的文档”。 打开此文件夹时,它包含当前用户的文档文件。 “我的文档”的桌面实例是一个虚拟文件夹(用于物理存储用户文档的文件系统位置的别名),位于命名空间层次结构中桌面的正下方。

“我的文档”和“我的图片”文件夹的目的是提供一种简单且安全的方式,让用户在可能有多个用户的系统上访问其文档和图片文件。 每个用户都为其文件分配单独的文件系统文件夹。 例如,用户的文档文件夹在文件系统中的位置通常类似于 C:\Documents and Settings\username\My Documents。 用户无需了解其文件系统文件夹的物理位置。 他们只需通过“我的文档”图标访问其文件。

注意

“我的文档”允许用户访问其自己的文件,但不允许访问任何其他用户的文件。 如果多个个人使用同一台计算机,管理员可以将用户锁定在存储实际文件的文件系统中。 因此,用户可以通过“我的文档”文件夹处理自己的文档,但不能处理属于任何其他用户的文档。

 

通常,应用程序无需知道登录的用户或用户在文件系统中“我的文档”文件夹所在的位置。 相反,应用程序可以通过调用桌面的 IShellFolder::P arseDisplayName 方法检索“我的文档”桌面图标的 PIDL。 用于标识“我的文档”文件夹的解析名称不是文件路径,而是 ::{450d8fba-ad25-11d0-98a8-0800361b1103}。 括号表达式是“我的文档”GUID 的文本形式。 例如,若要检索“我的文档”的 PIDL,应用程序应使用此对 IShellFolder::P arseDisplayName 的调用。

hr = psfDeskTop->ParseDisplayName(NULL, 
                                  NULL, 
                                  L"::{450d8fba-ad25-11d0-98a8-0800361b1103}", 
                                  &chEaten, 
                                  &pidlDocFiles, 
                                  NULL);

应用程序具有“我的文档”PIDL 后,它可以像处理普通文件系统文件夹一样处理该文件夹,例如枚举项目、分析、绑定和执行任何其他有效的文件夹操作。 Shell 会自动将“我的文档”或其子文件夹中的更改映射到相应的文件系统文件夹。

如果应用程序需要访问包含当前用户文档的实际文件系统文件夹,请将CSIDL_PERSONAL传递给 SHGetFolderLocation。 函数返回当前用户的“我的文档”文件夹中显示的文件系统文件夹的 PIDL。

连接的文件

HTML 文档通常具有许多关联的图形文件、一个样式表文件、多个 Microsoft JScript (与 ECMA 262 语言规范兼容的) 文件等。 移动或复制主 HTML 文档时,通常还需要移动或复制其关联的文件,以避免中断链接。 遗憾的是,到目前为止,除了分析其内容之外,确定哪些文件与任何给定的 HTML 文档相关,还没有任何简单的方法。 为了缓解此问题,Windows 2000 提供了一种将主 HTML 文档 连接到 其关联文件组的简单方法。 如果启用了文件连接,则移动或复制文档时,将随文档连接的所有文件一起移动。

若要创建一组连接的文件,主文档必须具有.htm或.html文件扩展名。 创建主文档的父文件夹的子文件夹。 子文件夹的名称必须是主文档的名称,减去.htm或.html扩展名,后跟下面列出的扩展名之一。 最常用的扩展是“.files”或“_files”。 例如,如果主文档命名为 MyDoc.htm,则将子文件夹命名为“MyDoc_files”会将子文件夹定义为文档连接文件的容器。 如果移动或复制主文档,也会移动或复制子文件夹及其文件。

对于某些语言,可以使用本地化的等效项“_files”为连接的文件创建子文件夹。 下表列出了可追加到文档名称以创建连接的文件子文件夹的有效字符串。 请注意,其中一些字符串将“-”作为其第一个字符,而不是“_”或“.”。

“_archivos”

“_arquivos”

“_bestanden”

“_bylos”

“-Dateien”

“_datoteke”

“_dosyalar”

“_elemei”

“_failid”

“_fails”

“_fajlovi”

“_ficheiros”

“_fichiers”

“-filer”

“.files”

“_files”

“_file”

“_fitxers”

“_fitxategiak”

“_pliki”

“_soubory”

“_tiedostot”

 

注意

此功能对扩展大小写很敏感。 例如,对于上面给出的示例,名为“MyDoc_Files”的子文件夹不会连接到MyDoc.htm。

 

是启用还是禁用文件连接由以下注册表项 的REG_DWORD 值 NoFileFolderConnection 控制。

HKEY_CURRENT_USER
   Software
      Microsoft
         Windows
            CurrentVersion
               Explorer

此值通常未定义,并且启用文件连接。 如有必要,可以通过将此值添加到密钥并将其设置为 1 来禁用文件连接。 若要再次启用文件连接,请将 NoFileFolderConnection 设置为零。

注意

文件连接通常应启用,因为其他应用程序可能依赖于它。 仅当绝对必要时才禁用文件连接。

 

移动、复制、重命名和删除文件

命名空间不是静态的,应用程序通常需要通过执行以下操作之一来管理文件系统。

  • 将对象复制到另一个文件夹。
  • 将对象移动到另一个文件夹。
  • 正在删除对象。
  • 重命名对象。

这些操作都是使用 SHFileOperation 执行的。 此函数采用一个或多个源文件并生成相应的目标文件。 在删除操作的情况下,系统会尝试将已删除的文件放入回收站。

还可以使用 拖放 功能移动文件。

若要使用 函数,必须填充 SHFILEOPSTRUCT 结构的成员,并将其传递给 SHFileOperation。 结构的关键成员是 pFrompTo

pFrom 成员是一个以 null 结尾的双字符串,其中包含一个或多个源文件名称。 这些名称可以是完全限定的路径,也可以是标准 DOS 通配符,例如 *.*。 尽管此成员声明为 以 null 结尾的字符串,但它用作用于保存多个文件名的缓冲区。 每个文件名必须以通常的单 NULL 字符结尾。 必须在最终名称的末尾追加一个 额外的 NULL 字符,以指示 pFrom 的末尾。

pTo 成员是一个以 null 结尾的双字符串,非常类似于 pFrompTo 成员包含一个或多个完全限定目标名称的名称。 它们以与 pFrom 相同的方式打包到 pTo 中。 如果 pTo 包含多个名称,则还必须在 fFlags 成员中设置 FOF_MULTIDESTFILES 标志。 pTo 的用法取决于此处所述的操作。

  • 对于复制和移动操作,如果所有文件都转到单个目录, pTo 将包含完全限定的目录名称。 如果文件要转到不同的目标, pTo 还可以包含每个源文件的一个完全限定的目录或文件名。 如果目录不存在,系统会创建它。
  • 对于重命名操作, pTo 包含 pFrom 中每个源文件的一个完全限定路径。
  • 对于删除操作,不使用 pTo

通知 Shell

在使用 SHFileOperation 移动、复制、重命名或删除文件后,或者执行影响命名空间的任何其他操作后,通知 Shell 更改。 应附带通知的操作包括:

  • 添加或删除文件或文件夹。
  • 移动、复制或重命名文件或文件夹。
  • 更改文件关联。
  • 更改文件属性。
  • 添加或删除驱动器或存储介质。
  • 创建或禁用共享文件夹。
  • 更改系统映像列表。

应用程序通过调用 SHChangeNotify 来通知 Shell,其中包含已更改内容的详细信息。 然后,Shell 可以更新命名空间的映像,以准确反映其新状态。

使用 SHFileOperation 管理文件的简单示例

以下示例控制台应用程序演示了如何使用 SHFileOperation 将文件从一个目录复制到另一个目录。 为简单起见,源目录和目标目录 C:\My_Docs 和 C:\My_Docs2 已硬编码到应用程序中。

#include <shlobj.h>
#include <shlwapi.h>
#include <strsafe.h>

int main(void)
{
    IShellFolder *psfDeskTop = NULL;
    IShellFolder *psfDocFiles = NULL;
    LPITEMIDLIST pidlDocFiles = NULL;
    LPITEMIDLIST pidlItems = NULL;
    IEnumIDList *ppenum = NULL;
    SHFILEOPSTRUCT sfo;
    STRRET strDispName;
    TCHAR szParseName[MAX_PATH];
    TCHAR szSourceFiles[256];
    int i;
    int iBufPos = 0;
    ULONG chEaten;
    ULONG celtFetched;
    size_t ParseNameSize = 0;
    HRESULT hr;
    

    szSourceFiles[0] = '\0';
    hr = SHGetDesktopFolder(&psfDeskTop);

    hr = psfDeskTop->ParseDisplayName(NULL, NULL, L"c:\\My_Docs", 
         &chEaten, &pidlDocFiles, NULL);
    hr = psfDeskTop->BindToObject(pidlDocFiles, NULL, IID_IShellFolder, 
         (LPVOID *) &psfDocFiles);
    hr = psfDeskTop->Release();

    hr = psfDocFiles->EnumObjects(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, 
         &ppenum);

    while( (hr = ppenum->Next(1,&pidlItems, &celtFetched)) == S_OK 
       && (celtFetched) == 1)
    {
        psfDocFiles->GetDisplayNameOf(pidlItems, SHGDN_FORPARSING, 
            &strDispName);
        StrRetToBuf(&strDispName, pidlItems, szParseName, MAX_PATH);
        
        hr = StringCchLength(szParseName, MAX_PATH, &ParseNameSize);
        
        if (SUCCEEDED(hr))
        {
            for(i=0; i<=ParseNameSize; i++)
            {
                szSourceFiles[iBufPos++] = szParseName[i];
            }
            CoTaskMemFree(pidlItems);
        }
    }
    ppenum->Release();
    
    szSourceFiles[iBufPos] = '\0';

    sfo.hwnd = NULL;
    sfo.wFunc = FO_COPY;
    sfo.pFrom = szSourceFiles;
    sfo.pTo = "c:\\My_Docs2\0";
    sfo.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR;

    hr = SHFileOperation(&sfo);
    
    SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, (LPCVOID) "c:\\My_Docs2", 0);

    CoTaskMemFree(pidlDocFiles);
    psfDocFiles->Release();

    return 0;
}

应用程序首先检索指向桌面的 IShellFolder 接口的指针。 然后,它通过将完全限定的路径传递到 IShellFolder::P arseDisplayName 来检索源目录的 PIDL。 请注意, IShellFolder::P arseDisplayName 要求目录的路径为 Unicode 字符串。 然后,应用程序绑定到源目录,并使用其 IShellFolder 接口检索枚举器对象的 IEnumIDList 接口。

枚举源目录中的每个文件时, 将使用 IShellFolder::GetDisplayNameOf 检索其名称。 设置SHGDN_FORPARSING标志,使 IShellFolder::GetDisplayNameOf 返回文件的完全限定路径。 文件路径(包括终止 NULL 字符)串联到单个数组 szSourceFiles 中。 第二个 NULL 字符将追加到最终路径以正确终止数组。

枚举完成后,应用程序会将值分配给 SHFILEOPSTRUCT 结构。 请注意,分配给 pTo 以指定目标的数组也必须以双 NULL 终止。 在这种情况下,它只是包含在分配给 pTo 的字符串中。 由于这是一个控制台应用程序,因此FOF_SILENT、FOF_NOCONFIRMATION和FOF_NOCONFIRMMKDIR标志设置为禁止显示任何对话框。 SHFileOperation 返回后,将调用 SHChangeNotify 以通知 Shell 更改。 然后,应用程序执行常规清理并返回 。

将文件添加到 Shell 的最近使用的文档列表

Shell 为每个用户维护最近添加或修改的文档的列表。 用户可以通过单击“开始”菜单上的“文档”来显示指向这些文件的链接列表。 与“我的文档”一样,每个用户都有一个文件系统目录来保存实际链接。 若要检索当前用户的“最近”目录的 PIDL,应用程序可以使用 CSIDL_RECENT 调用 SHGetFolderLocation ,或调用 SHGetFolderPath 来检索其路径。

应用程序可以使用本文档前面所述的技术枚举“最近”文件夹的内容。 但是,应用程序不应像修改普通文件系统文件夹一样修改文件夹的内容。 如果这样做,Shell 的最新文档列表将不会正确更新,更改也不会反映在“开始”菜单中。 相反,若要将文档链接添加到用户的“最近”文件夹,应用程序可以调用 SHAddToRecentDocs。 Shell 将添加指向相应文件系统文件夹的链接,并更新其最近使用的文档列表和“开始”菜单。 还可以使用此函数清除文件夹。