コンパイル時に型が確定しないオブジェクト(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 には「接続設定」「外観設定」「診断設定」といったカテゴリでキーごとのプロパティが表示され、ボタンをクリックすると新しいプロパティが「追加設定」カテゴリに動的に追加されます。
