コンパイル時に型が確定しないオブジェクト(Dictionary<string, object> など)のプロパティを XamPropertyGrid に動的に表示するには、対象オブジェクトに ICustomTypeDescriptor を実装し、GetProperties() で PropertyDescriptorCollection を実行時に生成します。

XamPropertyGrid の設定

XAML 側では ig:XamPropertyGrid を配置するだけです。

<Window x:Class="PropertyGridDynamicSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ig="http://schemas.infragistics.com/xaml">
    <Grid>
        <ig:XamPropertyGrid x:Name="MyPropertyGrid" />
    </Grid>
</Window>

コードビハインドでは、動的プロパティを持つオブジェクトを SelectedObject に渡すだけで、既定の TypeDescriptorPropertyGenerator が自動的に検出します。

public partial class MainWindow : Window
{
    private readonly DynamicSettingsItem _settings = new();

    public MainWindow()
    {
        InitializeComponent();
        SeedSampleData();

        MyPropertyGrid.SelectedObject = _settings;
    }
}

ICustomTypeDescriptor.GetProperties() の実装

対象オブジェクトに ICustomTypeDescriptor を実装し、GetProperties() が呼ばれるたびに保持しているディクショナリの中身から PropertyDescriptor を動的生成します。

public class DynamicSettingsItem : ICustomTypeDescriptor
{
    public Dictionary<string, object> Values { get; } = new();
    public Dictionary<string, DynamicPropertyMetadata> Metadata { get; } = new();

    public PropertyDescriptorCollection GetProperties(Attribute[]? attributes) => GetProperties();

    public PropertyDescriptorCollection GetProperties()
    {
        var descriptors = Values
            .Select(kvp =>
            {
                var meta = Metadata.TryGetValue(kvp.Key, out var m) ? m : new DynamicPropertyMetadata();

                var attrs = new List<Attribute>
                {
                    new CategoryAttribute(meta.Category),
                    new DescriptionAttribute(meta.Description),
                };
                if (meta.IsReadOnly)
                {
                    attrs.Add(new ReadOnlyAttribute(true));
                }

                var valueType = kvp.Value?.GetType() ?? typeof(object);
                return (PropertyDescriptor)new DynamicPropertyDescriptor(
                    kvp.Key, valueType, attrs.ToArray());
            })
            .OrderBy(d => Metadata.TryGetValue(d.Name, out var m) ? m.SortOrder : 0)
            .ToArray();

        return new PropertyDescriptorCollection(descriptors);
    }

    // ---- 残りのICustomTypeDescriptorメンバーは省略 ----
}

カスタム PropertyDescriptor での値の橋渡し

DynamicPropertyDescriptor は PropertyDescriptor を継承し、GetValue/SetValue でディクショナリへの読み書きを仲介します。

public class DynamicPropertyDescriptor : PropertyDescriptor
{
    private readonly Type _propertyType;

    public DynamicPropertyDescriptor(string name, Type propertyType, Attribute[] attributes)
        : base(name, attributes)
    {
        _propertyType = propertyType;
    }

    public override Type PropertyType => _propertyType;

    public override object? GetValue(object? component)
    {
        if (component is not DynamicSettingsItem item)
        {
            return null;
        }

        return item.Values.TryGetValue(Name, out var value) ? value : null;
    }

    public override void SetValue(object? component, object? value)
    {
        if (component is not DynamicSettingsItem item)
        {
            return;
        }

        item.Values[Name] = value!;
        OnValueChanged(component, EventArgs.Empty);
    }
}

RegenerateProperties() による再表示

実行時にプロパティを追加した場合、既に生成済みの一覧はそのままなので、RegenerateProperties() を呼び出してスキーマ変更を反映させます。

<Button x:Name="AddPropertyButton"
        Content="実行時にプロパティを追加"
        Click="AddPropertyButton_Click" />
private void AddPropertyButton_Click(object sender, RoutedEventArgs e)
{
    _extraPropertyCount++;
    var key = $"CustomOption{_extraPropertyCount}";

    _settings.Values[key] = $"値{_extraPropertyCount}";
    _settings.Metadata[key] = new DynamicPropertyMetadata { Category = "追加設定" };

    MyPropertyGrid.RegenerateProperties();
}

実行結果

PropertyGrid には「接続設定」「外観設定」「診断設定」といったカテゴリでキーごとのプロパティが表示され、ボタンをクリックすると新しいプロパティが「追加設定」カテゴリに動的に追加されます。

Tagged:

製品について

Ultimate UI for WPF