• (C#)Windows Shell 外壳编程系列2 解释,从“桌面”开始展开


    (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)

    让我们详细解释一下 Shell 编程中最基本的一些函数、结构体和枚举。
    SHGetDesktopFolder
    获取桌面的 IShellFolder 接口

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

    要使用这个函数,必须先定义一个 IntPtr 指针。然后通过指针,使用 GetObjectForIUnknown 返回通过指向 COM 对象的 IShellFolder 接口的指针实例。于是需要编写以下函数:

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

    ParseDisplayName
    获得对象的PIDL,即便对象在目录树中处于当前目录下一层或更多层。例如,对于文件对象来说,它的解析名就是它的路径,我们用文件系统对象的完全路径名来调用桌面的IshellFolder接口的 ParseDisplayName 方法,它会返回这个对象的完全PIDL。定义:

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

    里面最重要的参数就是 out IntPtr ppidl 了,它返回 pszDisplayName 指定路径对应的 PIDL。然而仅仅是 PIDL 并不能让你做更多的事情。这时候还需要调用 BindToObject 来返回 IShellFolder 接口。
    BindToObject
    根据 PIDL 创建和初始化 IShellFolder 对象。定义:

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

    里面有一个 [In()] ref Guid riid 参数,表示接口的接口标识符 (IID)。GUID其实就是一个唯一的标识符。世界上的任何两台计算机都不会生成重复的 GUID 值。GUID 主要用于在拥有多个节点、多台计算机的网络或系统中,分配必须具有唯一性的标识符。我们这里使用 IID_IShellFolder 表示它获取的是一个 IShellFolder 接口。

    public static Guid IID_IShellFolder = new Guid("{000214E6-0000-0000-C000-000000000046}");

    另外介绍 IEnumIDList 接口。IEnumIDList 接口使资源管理器获得文件夹包含的全部对象的PIDL,PIDL然后可以用来获得这些对象的信息。
    因此,我们使用 EnumObjects 函数返回的将是 IEnumIDList 的指针:

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

    其中 flags 是 SHCONTF 枚举类型,它决定了枚举的内容:

    SHCONTF
    public enum SHCONTF
    {
            FOLDERS = 0x20,
            NONFOLDERS = 0x40,
            INCLUDEHIDDEN = 0x80,
            INIT_ON_FIRST_NEXT = 0x100,
            NETPRINTERSRCH = 0x200,
            SHAREABLE = 0x400,
            STORAGE = 0x800
        }

    因此,我们可以通过 flags 的不同来分别列举子文件和子目录。这里会遇到一个问题,怎么获取 PIDL 对象的名称呢。这里编写了2个函数,可以通过 PIDL 或者 IShellFolder 返回对象的名称(详细解释留到下一节):

    获取名称
    /**//// <summary>
    /// 获取显示名称
    /// </summary>
    public static string GetNameByIShell(IShellFolder Root, IntPtr pidlSub)
    {
                IntPtr strr = Marshal.AllocCoTaskMem(MAX_PATH * 2 + 4);
                Marshal.WriteInt32(strr, 0, 0);
                StringBuilder buf = new StringBuilder(MAX_PATH);
                Root.GetDisplayNameOf(pidlSub, SHGNO.INFOLDER, strr);
                API.StrRetToBuf(strr, pidlSub, buf, MAX_PATH);
    return buf.ToString();
            }

    /**//// <summary>
    /// 根据 PIDL 获取显示名称
    /// </summary>
    public static string GetNameByPIDL(IntPtr pidl)
    {
                SHFILEINFO info = new SHFILEINFO();
                API.SHGetFileInfo(pidl, 0, ref info, Marshal.SizeOf(typeof(SHFILEINFO)),
                    SHGFI.PIDL | SHGFI.DISPLAYNAME | SHGFI.TYPENAME);
    return info.szDisplayName;
            }

    例子二,从“桌面”开始展开
    这个例子将使你深入理解之前的内容。它是这样的一个例子,允许你从“桌面”开始,一直展开到最深层的对象。

    例2
    public partial class Sample2 : Form
    {
    private IShellFolder deskTop;

    public Sample2()
    {
                InitializeComponent();
            }

    private void Form1_Load(object sender, EventArgs e)
    {
    //获得桌面 PIDL
                IntPtr deskTopPtr;
                deskTop = API.GetDesktopFolder(out deskTopPtr);

    //添加 桌面 节点
                TreeNode tnDesktop = new TreeNode("桌面");
                tnDesktop.Tag = deskTop;
                tnDesktop.Nodes.Add("");

    //把节点添加到树中
                Tree1.Nodes.Add(tnDesktop);
                tnDesktop.Expand();
            }

    private void Tree1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
    {
    判断节点是否已经展开#region 判断节点是否已经展开
    if (e.Node.Nodes.Count != 1)
    {
    return;
                }
    else
    {
    if (e.Node.FirstNode.Text != "")
    {
    return;
                    }
                }

                e.Node.Nodes.Clear(); 
    #endregion

                IShellFolder root = (IShellFolder)e.Node.Tag;

    //循环查找子项
                IEnumIDList Enum = null;
                IntPtr EnumPtr = IntPtr.Zero;
                IntPtr pidlSub;
    int celtFetched;

    if (root.EnumObjects(this.Handle, SHCONTF.FOLDERS, out EnumPtr) == API.S_OK)
    {
                    Enum = (IEnumIDList)Marshal.GetObjectForIUnknown(EnumPtr);
    while (Enum.Next(1, out pidlSub, out celtFetched) == 0 && celtFetched == API.S_FALSE)
    {
    string name = API.GetNameByIShell(root, pidlSub);
                        IShellFolder iSub;
                        root.BindToObject(pidlSub, IntPtr.Zero, ref Guids.IID_IShellFolder, out iSub);

                        TreeNode nodeSub = new TreeNode(name);
                        nodeSub.Tag = iSub;
                        nodeSub.Nodes.Add("");
                        e.Node.Nodes.Add(nodeSub);
                    }
                }
            }

    private void Sample2_FormClosing(object sender, FormClosingEventArgs e)
    {
    //释放资源
                Marshal.ReleaseComObject(deskTop);
            }

        }

    照例,附图片和源代码:

    源代码:/Files/lemony/WinShell2.rar

  • 相关阅读:
    Python解释器相关知识
    简单了解下Flask
    Scoket编程
    __file__的作用
    jquery编写可折叠列表
    浑浑噩噩的一天
    js实现杨辉三角
    js闭包
    python读取word表格
    HTMLTestRunner报告
  • 原文地址:https://www.cnblogs.com/MaxWoods/p/1764037.html
Copyright © 2020-2023  润新知