目录
5.4 设置类的CategoryOrderAttribute特性的值
1. Nuget安装
2. 调用
//xaml里 添加命名空间
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
<Grid>
<xctk:PropertyGrid SelectedObject="{Binding PropeObject}" Name="PropertyGrid"
SelectedPropertyItemChanged="PropertyGrid_SelectedPropertyItemChanged" Margin="10" ShowAdvancedOptions="True" ShowDescriptionByTooltip="True" NameColumnWidth="270"
FontSize="14" ShowTitle="False" ShowSortOptions="False" ShowSearchBox="False" CategoryGroupHeaderTemplate="{StaticResource Category}">
</xctk:PropertyGrid>
</Grid>
SelectedPropertyItemChanged事件
private string _propname;
public string PropName
{
get { return _propname; }
set { _propname = value; }
}
private void PropertyGrid_SelectedPropertyItemChanged(object sender, RoutedPropertyChangedEventArgs<Xceed.Wpf.Toolkit.PropertyGrid.PropertyItemBase> e)
{
PropertyGrid propertyGrid = sender as PropertyGrid;
//获取当前选择项的父类类型
//需要做一次类型转换 否则获取不到相应属性
PropertyItem item = propertyGrid.SelectedPropertyItem?.ParentElement as PropertyItem;
PropName = item?.PropertyType.Name;
}
3. 常用属性
Id | Property | Description |
1 | SelectedObject | Gets or sets the current object the PropertyGrid is inspecting. |
2 | IsReadOnly | Gets or sets a value indicating whether the property grid is read-only. |
3 | NameColumnWidth | Gets or sets the width of the property name column. |
4 | ShowSearchBox | Gets or sets a value indicating whether the search box is displayed. By default, true. |
5 | ShowSortOptions | Gets or sets a value indicating whether the show sort options are displayed (Categorized and Alphabetical). By default, true. |
6 | ShowTitle | Gets or sets a value indicating whether the PropertyGrid's title is displayed. |
7 | ShowDescriptionByTooltip | Gets or sets if the Description of the PropertyItem will be displayed as a tooltip on the PART_Name of the PropertyItem. When ShowDescriptionByTooltip is True and the DescriptionAttribute on the PropertyItem is not null and not empty, it will be displayed as a tooltip. |
4. Model层设置
4.1 测试类
private TestPropertyGrid _pro;
public TestPropertyGrid PropeObject
{
get => _pro;
set => SetProperty(ref _pro, value);
}
public class TestPropertyGrid
{
public int PropertyA { get; set; }
public string PropertyB { get; set; }
public TestA TestA { get; set; }
public List<TestB> TestBList { get; set; }
}
public class TestA
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TestB
{
public int Id { get; set; }
public string Address { get; set; }
}
4.2 为对象添加相关特性
添加引用
using System.ComponentModel;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
4.2.1 添加描述特性
[Description("属性A")]
public int PropertyA { get; set; }
4.2.2 添加分组
[Category("分组1")]
public int PropertyA { get; set; }
4.2.3 添加属性是否显示标识
[Browsable(true)]
public int PropertyA { get; set; }
4.2.4 添加类属性可展开特性
[ExpandableObject]
public class TestA
{
}
4.2.5 添加类属性显示顺序
[PropertyOrder(2)]
public int Id { get; set; }
[PropertyOrder(1)]
public string Name { get; set; }
4.2.6 对分组[Category]再进行排序
说明:分组的优先级高于PropertyOrder,而分组是根据组名按拼音进行排序,部分情况需对其进行再排序
[CategoryOrder("分组1",2), CategoryOrder("分组2", 1)]
public class TestPropertyGrid
{
[Category("分组1")]
public int PropertyA { get; set; }
[Category("分组2")]
public string PropertyB { get; set; }
}
4.3 自定义List集合的显示
可展开的集合类
public class ExpandableObservableCollection<T> : ObservableCollection<T>,
ICustomTypeDescriptor
{
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
// Create a collection object to hold property descriptors
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
for (int i = 0; i < Count; i++)
{
pds.Add(new ItemPropertyDescriptor<T>(this, i));
}
return pds;
}
#region Use default TypeDescriptor stuff
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(this, noCustomTypeDesc: true);
}
string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(this, noCustomTypeDesc: true);
}
string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(this, noCustomTypeDesc: true);
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(this, noCustomTypeDesc: true);
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, noCustomTypeDesc: true);
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, noCustomTypeDesc: true);
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, noCustomTypeDesc: true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, noCustomTypeDesc: true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, noCustomTypeDesc: true);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
return TypeDescriptor.GetProperties(this, attributes, noCustomTypeDesc: true);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
}
public class ItemPropertyDescriptor<T> : PropertyDescriptor
{
private readonly ObservableCollection<T> _owner;
private readonly int _index;
public ItemPropertyDescriptor(ObservableCollection<T> owner, int index)
: base($"[{index}]" , null)
{
_owner = owner;
_index = index;
}
public override AttributeCollection Attributes
{
get
{
var attributes = TypeDescriptor.GetAttributes(GetValue(null), false);
if (!attributes.OfType<ExpandableObjectAttribute>().Any())
{
// copy all the attributes plus an extra one (the
// ExpandableObjectAttribute)
// this ensures that even if the type of the object itself doesn't have the
// ExpandableObjectAttribute, it will still be expandable.
var newAttributes = new Attribute[attributes.Count + 1];
attributes.CopyTo(newAttributes, 0);
newAttributes[newAttributes.Length - 1] = new ExpandableObjectAttribute();
// overwrite the original
attributes = new AttributeCollection(newAttributes);
}
return attributes;
}
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return Value;
}
private T Value
=> _owner[_index];
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
_owner[_index] = (T)value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType
=> _owner.GetType();
public override bool IsReadOnly
=> false;
public override Type PropertyType
=> Value?.GetType();
}
调用:List类型改为ExpandableObservableCollection,再添加可展开特性
[ExpandableObject]
public ExpandableObservableCollection<TestB> TestBList { get; set; }
效果:
5. 代码动态获取/设置相关特性的值
5.1 设置Browsable特性的值
public void SetPropertyVisibility(object obj, string propertyName, bool visible)
{
Type type = typeof(BrowsableAttribute);
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(obj);
AttributeCollection attrs = props[propertyName].Attributes;
FieldInfo fld = type.GetField("browsable", BindingFlags.Instance | BindingFlags.NonPublic);
fld?.SetValue(attrs[type], visible);
}
5.2 设置Category特性的值
public void SetCategoryValue(object obj, string propertyName,string str)
{
Type type = typeof(CategoryAttribute);
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(obj);
AttributeCollection attrs = props[propertyName].Attributes;
FieldInfo fld = type.GetField("categoryValue", BindingFlags.Instance | BindingFlags.NonPublic);
fld?.SetValue(attrs[type], str);
}
5.3 获取类的属性成员的Category特性的值
public string GetCategoryValue(object obj, string propertyName)
{
Type type = typeof(CategoryAttribute);
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(obj);
AttributeCollection attrs = props[propertyName].Attributes;
FieldInfo fld = type.GetField("categoryValue", BindingFlags.Instance | BindingFlags.NonPublic);
var str = fld.GetValue(attrs[type])?.ToString();
return str;
}
5.4 设置类的CategoryOrderAttribute特性的值
public void SetCategoryOrderValue(object obj,CategoryOrderAttribute[] categoryOrders)
{
TypeDescriptor.AddAttributes(obj.GetType(), categoryOrders);
}
5.5 嵌套属性赋值
/// <summary>
/// 使用反射设置嵌套属性的值
/// </summary>
/// <param name="obj">对象 </param>
/// <param name="propename">第一层属性</param>
/// <param name="targetname">目标属性(是第一层属性的成员)</param>
/// <param name="val">赋的值</param>
private void NestPropeSetValue(object obj, string propename, string targetname, object val)
{
PropertyInfo[] propertyInfos = obj.GetType().GetProperties();
var prope = propertyInfos.Single(x => propename == x.PropertyType.Name);
var value = prope.GetValue(obj, null);
var pr = value.GetType().GetProperties().Single(x => x.Name == targetname);
pr?.SetValue(value, val);
}
6. 自定义Category样式
<UserControl.Resources>
<DataTemplate x:Key="Category">
<TextBlock Text="{Binding}" FontSize="20" Foreground="#4EB9E4" />
</DataTemplate>
</UserControl.Resources>
<!--调用:-->
CategoryGroupHeaderTemplate="{StaticResource Category}"
7. 自定义属性编辑模块
<xctk:PropertyGrid SelectedObject="{Binding PropeObject}" >
<xctk:PropertyGrid.EditorDefinitions>
<!--EditorTemplateDefinition可添加多个-->
<!--要修改编辑模板的属性的名称-->
<xctk:EditorTemplateDefinition TargetProperties="PropertyA,PropertyB">
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<!--此处可自由发挥-->
<Grid>
<!-- Command生效: DataTemplate的DataContext指代不明确,需要改为父类的DataContext。 参数Value表示原对象-->
<ToggleButton Command="{Binding Path=DataContext.PropeSetCommand ,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=xctk:PropertyGrid}}"
CommandParameter="{Binding Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="25" Content="点击" Width="300" HorizontalAlignment="Left"></ToggleButton>
</Grid>
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
</xctk:PropertyGrid.EditorDefinitions>
</xctk:PropertyGrid>
ToggleButton样式
<UserControl.Resources>
<Style TargetType="{x:Type ToggleButton}" >
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Microsoft YaHei"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="0" CornerRadius="2">
<Border.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FF71E0C6" Offset="0.0" />
<GradientStop Color="#FF43A88D" Offset="0.2" />
<GradientStop Color="#FF5BB07D" Offset="0.0" />
</LinearGradientBrush>
</Border.Background>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" ></ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ButtonBase.IsPressed" Value="True">
<Setter Property="UIElement.Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" Color="#276AB0" Direction="0" Opacity="0.9" RenderingBias="Performance" ShadowDepth="0" />
</Setter.Value>
</Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="0.9" />
</Setter.Value>
</Setter>
<Setter Property="RenderTransformOrigin" Value=".5,.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
8. 最终效果图
参考:
【1】 PropertyGrid · xceedsoftware/wpftoolkit Wiki · GitHub
【2】 c# - Category Ordering In Xceed PropertyGrid - Stack Overflow