(C#)Windows Shell 外壳编程系列1 - 基础,浏览一个文件夹

1 - 基础,浏览一个文件夹

我们知道,在win32中是以外壳名字空间的形式来组织文件系统的,在外壳名字空间里的每一个对象(注)都实现了一个IShellFolder的接口,通过这个接口我们可以直接查询或间接得到其他相关的接口。
(注:这里的对象指的是外壳名字空间中的一个节点,对象有可能是一个文件夹,有可能是一个文件,也有可能是一个虚拟文件夹,例如:我的电脑,网上邻居,控制面板等)

在C#中,我们这样定义 IShellFolder 接口:


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace WinShell
{
    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("000214E6-0000-0000-C000-000000000046")]
    public interface IShellFolder
    {
    }

}


当然,这个接口还没有列出细节函数。我们要做的仅仅是从最基础开始。

首先我们必须了解,在外壳编程中,要使用 PIDL 路径代替普通路径(如果对 PIDL 不熟悉,请看Windows外壳名字空间的浏览)。

“桌面”是最顶级的文件夹,外壳名字空间中其他各项都可以用从“桌面”开始的 PIDL 加以表示。

如何获取“桌面”的 PIDL 和其 IShellFolder 接口呢,可以通过 API SHGetDesktopFolder:

[DllImport("shell32.dll")]
         public  static  extern Int32 SHGetDesktopFolder( out IntPtr ppshf);

/// <summary>
        
/// 获得桌面 Shell
        
/// </summary>

         public  static IShellFolder GetDesktopFolder( out IntPtr ppshf)
         {
            SHGetDesktopFolder(out ppshf);
            Object obj = Marshal.GetObjectForIUnknown(ppshf);
            return (IShellFolder)obj;
        }


// 获得桌面 PIDL
            IntPtr desktopPtr;
            IShellFolder desktop = API.GetDesktopFolder( out desktopPtr);


好的,我们取得“桌面”的 IShellFolder 接口,就已经成功了一半。现在我需要通过“桌面”,来获取“C:\”这个路径的 PIDL 和 IShellFolder 接口,可以通过 IShellFolder 的 ParseDisplayName 和 BindToObject 函数来实现:

void ParseDisplayName(
            IntPtr hwnd,
            IntPtr pbc,
            [MarshalAs(UnmanagedType.LPWStr)]  string pszDisplayName,
             out  uint pchEaten,
             out IntPtr ppidl,
             ref  uint pdwAttributes);

void BindToObject(
            IntPtr pidl,
            IntPtr pbc,
            [In()]  ref Guid riid,
             out IShellFolder ppv);



// 获取 C 盘的 PIDL
             string FolderPath = @"C:\";
            IntPtr Pidl = IntPtr.Zero;
            IShellFolder Root;
             uint i, j = 0;
            desktop.ParseDisplayName(Handle, IntPtr.Zero, FolderPath,  out i,  out Pidl,  ref j);
            desktop.BindToObject(Pidl, IntPtr.Zero,  ref Guids.IID_IShellFolder,  out Root);


前提是你应该保证路径存在,因为我没有做任何出错控制。这样我们就获得了一个 Root,它表示C盘。通过这个Root,我们可以用 EnumObjects 来循环获取其子项(子文件和子文件夹):

[PreserveSig]
         int EnumObjects(IntPtr hWnd, SHCONTF flags,  out IntPtr enumIDList);

 

// 循环查找 C 盘下面的文件/文件夹的 PIDL
            IEnumIDList fileEnum =  null;
            IEnumIDList folderEnum =  null;
            IntPtr fileEnumPtr = IntPtr.Zero;
            IntPtr folderEnumPtr = IntPtr.Zero;
            IntPtr pidlSub;
             int celtFetched;

             // 获取子文件夹
             if (Root.EnumObjects( this.Handle, SHCONTF.FOLDERS | SHCONTF.INCLUDEHIDDEN,  out fileEnumPtr) == API.S_OK)
             {
                fileEnum = (IEnumIDList)Marshal.GetObjectForIUnknown(fileEnumPtr);
                while (fileEnum.Next(1, out pidlSub, out celtFetched) == 0 && celtFetched == API.S_FALSE)
                {
                    //获取显示名称
                    string name = API.GetNameByPIDL(pidlSub);
                    lvFile.Items.Add(name, 1);
                }

            }


             // 获取子文件
             if (Root.EnumObjects( this.Handle, SHCONTF.NONFOLDERS | SHCONTF.INCLUDEHIDDEN,  out folderEnumPtr) == API.S_OK)
             {
                folderEnum = (IEnumIDList)Marshal.GetObjectForIUnknown(folderEnumPtr);
                while (folderEnum.Next(1, out pidlSub, out celtFetched) == 0 && celtFetched == API.S_FALSE)
                {
                    string name = API.GetNameByPIDL(pidlSub);
                    lvFile.Items.Add(name, 0);
                }

            }


事实上,代码到此结束。然而我发现有太多的结构体和枚举没有介绍(以后会有更多),有兴趣的朋友可以等待我下一节介绍

你可能感兴趣的:(windows)