You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows 11文件资源管理器自定义属性列无值显示求助

问题:Windows 11文件资源管理器自定义列无显示值

我现在感觉自己蠢透了,在这事上耗了太多时间,肯定是漏了某个基础环节。到底哪里错了?

我想在Windows 11的文件资源管理器里加个新列。查资料得知需要注册属性描述文件和属性处理程序(COM服务器)。我几乎没接触过COM,用C# .NET 8开发,所以选了这条路线而非C/C++(用CsWin32库生成Windows类型和PInvoke方法)。我只想做个类似“Hello World”的功能,让新列对所有文件都显示“Kyle”。我能在文件资源管理器里添加这个列,但列里始终没有任何文件的值。我加了日志,但完全没输出,事件查看器里也没错误。


属性描述文件

<?xml version="1.0" encoding="utf-8"?>

<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/windows/2006/propertydescription" schemaVersion="1.0">
    <propertyDescriptionList publisher="Kyle" product="Custom Properties">
        <propertyDescription name="Kyle.CustomProperty.Kyle" formatID="{4F4F4F4F-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" propID="100">
            <description>Always displays the name Kyle.</description>
            <searchInfo inInvertedIndex="false" isColumn="true" />
            <typeInfo type="String" multipleValues="false" isViewable="true" isQueryable="false" />
            <labelInfo label="Kyle" invitationText="My name" />
            <displayInfo alignment="left" />
        </propertyDescription>
    </propertyDescriptionList>
</schema>

属性描述注册工具

项目文件

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

    <ItemGroup>
        <None Remove="NativeMethods.json" />
        <None Remove="NativeMethods.txt" />
    </ItemGroup>

    <ItemGroup>
        <AdditionalFiles Include="NativeMethods.json" />
        <AdditionalFiles Include="NativeMethods.txt" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>

</Project>

代码文件

using System.Runtime.InteropServices;
using Windows.Win32;

public class Program
{
    private static void Main(string[] args)
    {
        if (args.Length < 2)
        {
            Console.WriteLine("Usage: RegisterPropertySchema.exe <register|unregister> <schemaPath>");
            return;
        }

        var action     = args[0];
        var schemaPath = args[1];

        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600))
        {
            throw new PlatformNotSupportedException("PSRegisterPropertySchema and PSUnregisterPropertySchema are only supported on Windows.");
        }

        switch (action.ToLower())
        {
            case "register":
                RegisterPropertySchema(schemaPath);
                break;
            case "unregister":
                UnregisterPropertySchema(schemaPath);
                break;
            default:
                Console.WriteLine("Invalid action. Use 'register' or 'unregister'.");
                break;
        }
    }

    private static void RegisterPropertySchema(string schemaPath)
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600))
        {
            throw new PlatformNotSupportedException("PSRegisterPropertySchema and PSUnregisterPropertySchema are only supported on Windows.");
        }

        var result = PInvoke.PSRegisterPropertySchema(schemaPath);

        if (result == 0)
        {
            Console.WriteLine("Property schema registered successfully.");
        }
        else if (result == 0x000401A0)  // INPLACE_S_TRUNCATED
        {
            Console.WriteLine("Property schema PARTIALLY registered. INPLACE_S_TRUNCATED");
        }
        else
        {
            Console.WriteLine($"Failed to register property schema. HRESULT: {result:X}");
        }
    }

    private static void UnregisterPropertySchema(string schemaPath)
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600))
        {
            throw new PlatformNotSupportedException("PSRegisterPropertySchema and PSUnregisterPropertySchema are only supported on Windows.");
        }

        var result = PInvoke.PSUnregisterPropertySchema(schemaPath);

        if (result == 0)
        {
            Console.WriteLine("Property schema unregistered successfully.");
        }
        else
        {
            Console.WriteLine($"Failed to unregister property schema. HRESULT: {result:X}");
        }
    }
}

注册命令

.\RegisterPropertySchema.exe register "C:\Program Files\Kyle\CustomProperty\CustomPropertyKyle.propdesc"

注册表添加内容

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertySchema\0003]
@="C:\\Program Files\\Kyle\\CustomProperty\\CustomPropertyKyle.propdesc"
"SchemaId"="{D84D0B56-EAB4-4E60-A125-DD699A684B3C}"
"URI"="custompropertykyle.propdesc"
"CompactURI"="66a088697b1669173504a8c3d90b3614e147d922d8f298de8116bbcd74e5c6b4"

属性处理程序

项目文件

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <EnableComHosting>true</EnableComHosting>
        <EnableRegFreeCom>true</EnableRegFreeCom>
    </PropertyGroup>

    <ItemGroup>
        <None Remove="NativeMethods.txt" />
    </ItemGroup>

    <ItemGroup>
        <AdditionalFiles Include="NativeMethods.txt" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>

</Project>

代码文件

using System.Runtime.InteropServices;
using Windows.Win32.System.Com.StructuredStorage;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Shell.PropertiesSystem;

[ComVisible(true)]
[Guid("12345678-1234-1234-1234-1234567890AB")]
public unsafe class CustomPropertyHandler : IPropertyStore
{
    private static string LogFilePath { get; } = @"C:\abc\CustomPropertyHandler.txt";

    private static PROPERTYKEY PropertyKeyTitle { get; } = new()
                                                           {
                                                               fmtid = new ("4F4F4F4F-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
                                                             , pid   = 100
                                                           };

    public void GetCount(out uint propertyCount)
    {
        try
        {
            Log(nameof(GetCount), "START");
            propertyCount = 1;
        }
        catch (Exception ex)
        {
            Log(nameof(GetCount), $"   EXCEPTION: {ex.Message}");
            propertyCount = 1;
        }
        finally
        {
            Log(nameof(GetCount), "   END");
        }
    }

    public void GetAt(uint propertyIndex, PROPERTYKEY* key)
    {
        try
        {
            Log(nameof(GetAt), $"START  propertyIndex: {propertyIndex}");

            ArgumentOutOfRangeException.ThrowIfNotEqual<uint>(propertyIndex, 0);

            *key = PropertyKeyTitle;
        }
        catch (Exception ex)
        {
            Log(nameof(GetAt), $"   EXCEPTION: {ex.Message}");
        }
        finally
        {
            Log(nameof(GetAt), "   END");
        }
    }

    public void GetValue(PROPERTYKEY* key, out PROPVARIANT pv)
    {
        try
        {
            Log(nameof(GetValue), $"START  key: fmtid={key->fmtid} pid={key->pid}");

            pv = new ();

            if (key->fmtid == PropertyKeyTitle.fmtid && key->pid == PropertyKeyTitle.pid)
            {
                Log(nameof(GetValue), "   MATCH");

                pv.Anonymous.Anonymous.vt                = VARENUM.VT_LPWSTR;
                pv.Anonymous.Anonymous.Anonymous.pwszVal = (char*)Marshal.StringToCoTaskMemUni("Kyle");
            }
            else
            {
                Log(nameof(GetValue), "   NO MATCH");

                pv.Anonymous.Anonymous.vt = VARENUM.VT_EMPTY;
            }
        }
        catch (Exception ex)
        {
            Log(nameof(GetValue), $"   EXCEPTION: {ex.Message}");
            pv = new();
        }

        Log(nameof(GetValue), "   END");
    }

    public void SetValue(PROPERTYKEY* key, in PROPVARIANT propvar) => Log(nameof(SetValue), $"START  key: {key->fmtid} {key->pid}    propvar: vt={propvar.Anonymous.Anonymous.vt} pwszVal={propvar.Anonymous.Anonymous.Anonymous.pwszVal}");

    public void Commit() => Log(nameof(Commit),  "START");

    private static void Log(string method, string message)
    {
        using var writer = new StreamWriter(LogFilePath, append: true);

        writer.WriteLine($"{DateTime.Now:HH:mm:ss.sss}   {method}  {message}");
    }
}

注册命令

regsvr32 "C:\Program Files\Kyle\CustomProperty\CustomPropertyKyle.comhost.dll"

注册表添加内容

[HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-1234567890AB}]
@=".CustomPropertyHandler"

[HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-1234567890AB}\InProcServer32]
@="C:\\Program Files\\Kyle\\CustomProperty\\CustomPropertyKyle.comhost.dll"
"ThreadingModel"="Both"

[HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-1234567890AB}\ProgID]
@=".CustomPropertyHandler"

内容的提问来源于stack exchange,提问作者Kyle K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 15:35:55