PropertyGrid是一个很强大的控件,使用该控件做属性设置面板的一个好处就是你只需要专注于代码而无需关注UI的呈现,PropertyGrid会默认根据变量类型选择合适的控件显示。但是这也带来了一个问题,就是控件的使用变得不是特别灵活,主要表现在你无法根据你的需求很好的选择控件,比如当你需要用Slider控件来设置int型变量时,PropertyGrid默认的模板选择器是不支持的。网上找了许多资料基本都是介绍WinForm的实现方式,主要用到了IWindowFromService这个接口,并未找到合适的适合WPF的Demo,后来在参考了DEVExpress的官方Demo之后我做了一个基于WPF和DEV 16.2的PropertyGrid Demo,基本实现了上述功能。

为了实现这一点,需要自定义一个DataTemplateSeletor类,这也是本文的核心代码。

1.创建一个CustomPropertyGrid自定义控件:

 <UserControl
x:Class="PropertyGridDemo.PropertyGridControl.CustomPropertyGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dxprg="http://schemas.devexpress.com/winfx/2008/xaml/propertygrid"
xmlns:local="clr-namespace:PropertyGridDemo.PropertyGridControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- 资源字典 -->
<ResourceDictionary Source="../PropertyGridControl/DynamicallyAssignDataEditorsResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<!-- PropertyDefinitionStyle:定义属性描述的风格模板 -->
<!-- PropertyDefinitionTemplateSelector:定义一个模板选择器,对应一个继承自DataTemplateSelector的类 -->
<!-- PropertyDefinitionsSource:定义一个获取数据属性集合的类,对应一个自定义类(本Demo中对应DataEditorsViewModel) -->
<dxprg:PropertyGridControl
x:Name="PropertyGridControl"
Margin="24"
DataContextChanged="PropertyGridControl_DataContextChanged"
ExpandCategoriesWhenSelectedObjectChanged="True"
PropertyDefinitionStyle="{StaticResource DynamicallyAssignDataEditorsPropertyDefinitionStyle}"
PropertyDefinitionTemplateSelector="{StaticResource DynamicallyAssignDataEditorsTemplateSelector}"
PropertyDefinitionsSource="{Binding Path=Properties, Source={StaticResource DemoDataProvider}}"
ShowCategories="True"
ShowDescriptionIn="Panel" />
</Grid>
</UserControl>

CustomPropertyGrid

该控件使用的资源字典如下:

 <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:dxprg="http://schemas.devexpress.com/winfx/2008/xaml/propertygrid"
xmlns:local="clr-namespace:PropertyGridDemo.PropertyGridControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> <local:DynamicallyAssignDataEditorsTemplateSelector x:Key="DynamicallyAssignDataEditorsTemplateSelector" />
<local:DataEditorsViewModel x:Key="DemoDataProvider" /> <DataTemplate x:Key="DescriptionTemplate">
<RichTextBox
x:Name="descriptionRichTextBox"
MinWidth="150"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
IsReadOnly="True"
IsTabStop="False" />
</DataTemplate>
<DataTemplate x:Key="descriptionTemplate">
<RichTextBox
x:Name="descriptionRichTextBox"
MinWidth="150"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
IsReadOnly="True"
IsTabStop="False" />
</DataTemplate> <!-- 设置控件的全局样式和数据绑定 -->
<Style x:Key="DynamicallyAssignDataEditorsPropertyDefinitionStyle" TargetType="dxprg:PropertyDefinition">
<Setter Property="Path" Value="{Binding Name}" />
<!--<Setter Property="Header" Value="{Binding Converter={StaticResource PropertyDescriptorToDisplayNameConverter}}"/>-->
<Setter Property="Description" Value="{Binding}" />
<Setter Property="DescriptionTemplate" Value="{StaticResource descriptionTemplate}" />
</Style>
<Style x:Key="DescriptionContainerStyle" TargetType="dxprg:PropertyDescriptionPresenterControl">
<Setter Property="ShowSelectedRowHeader" Value="False" />
<Setter Property="MinHeight" Value="70" />
</Style> <Style TargetType="Slider">
<Setter Property="Margin" Value="2" />
</Style>
<Style TargetType="dxe:ComboBoxEdit">
<Setter Property="IsTextEditable" Value="False" />
<Setter Property="ApplyItemTemplateToSelectedItem" Value="True" />
<Setter Property="Margin" Value="2" />
</Style> <!-- 测试直接从DataTemplate获取控件 -->
<DataTemplate x:Key="SliderTemplate" DataType="local:SliderExtend">
<!--<dxprg:PropertyDefinition>
<dxprg:PropertyDefinition.CellTemplate>-->
<!--<DataTemplate>-->
<StackPanel x:Name="Root">
<Slider
Maximum="{Binding Path=Max}"
Minimum="{Binding Path=Min}"
Value="{Binding Path=Value}" />
<TextBlock Text="{Binding Path=Value}" />
</StackPanel>
<!--</DataTemplate>-->
<!--</dxprg:PropertyDefinition.CellTemplate>
</dxprg:PropertyDefinition>-->
</DataTemplate> <DataTemplate x:Key="ComboBoxEditItemTemplate" DataType="Tuple">
<TextBlock
Height="20"
Margin="5,3,0,0"
VerticalAlignment="Center"
Text="{Binding Item1}" />
</DataTemplate>
</ResourceDictionary>

ResourceDictionary

2.编写对应的模板选择类 DynamicallyAssignDataEditorsTemplateSelector:

 using DevExpress.Xpf.Editors;
using DevExpress.Xpf.PropertyGrid;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data; namespace PropertyGridDemo.PropertyGridControl
{
public class DynamicallyAssignDataEditorsTemplateSelector : DataTemplateSelector
{
private PropertyDescriptor _property = null;
private RootPropertyDefinition _element = null;
private PropertyDataContext _propertyDataContext => App.PropertyGridDataContext; /// <summary>
/// 当重写在派生类中,返回根据自定义逻辑的 <see cref="T:System.Windows.DataTemplate" /> 。
/// </summary>
/// <param name="item">数据对象可以选择模板。</param>
/// <param name="container">数据对象。</param>
/// <returns>
/// 返回 <see cref="T:System.Windows.DataTemplate" /> 或 null。默认值为 null。
/// </returns>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
_element = (RootPropertyDefinition)container;
DataTemplate resource = TryCreateResource(item);
return resource ?? base.SelectTemplate(item, container);
} /// <summary>
/// Tries the create resource.
/// </summary>
/// <param name="item">The item.</param>
/// <returns></returns>
private DataTemplate TryCreateResource(object item)
{
if (!(item is PropertyDescriptor)) return null;
PropertyDescriptor pd = (PropertyDescriptor)item;
_property = pd;
var customUIAttribute = (CustomUIAttribute)pd.Attributes[typeof(CustomUIAttribute)];
if (customUIAttribute == null) return null;
var customUIType = customUIAttribute.CustomUI;
return CreatePropertyDefinitionTemplate(customUIAttribute);
} /// <summary>
/// Gets the data context.
/// </summary>
/// <param name="dataContextPropertyName">Name of the data context property.</param>
/// <returns></returns>
private object GetDataContext(string dataContextPropertyName)
{
PropertyInfo property = _propertyDataContext?.GetType().GetProperty(dataContextPropertyName);
if (property == null) return null;
return property.GetValue(_propertyDataContext, null);
} /// <summary>
/// Creates the slider data template.
/// </summary>
/// <param name="customUIAttribute">The custom UI attribute.</param>
/// <returns></returns>
private DataTemplate CreateSliderDataTemplate(CustomUIAttribute customUIAttribute)
{
DataTemplate ct = new DataTemplate();
ct.VisualTree = new FrameworkElementFactory(typeof(StackPanel));
ct.VisualTree.SetValue(StackPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName)); FrameworkElementFactory sliderFactory = new FrameworkElementFactory(typeof(Slider));
sliderFactory.SetBinding(Slider.MaximumProperty, new Binding(nameof(SliderUIDataContext.Max)));
sliderFactory.SetBinding(Slider.MinimumProperty, new Binding(nameof(SliderUIDataContext.Min)));
sliderFactory.SetBinding(Slider.SmallChangeProperty, new Binding(nameof(SliderUIDataContext.SmallChange)));
sliderFactory.SetBinding(Slider.LargeChangeProperty, new Binding(nameof(SliderUIDataContext.LargeChange)));
sliderFactory.SetBinding(Slider.ValueProperty, new Binding(nameof(SliderUIDataContext.Value)));
ct.VisualTree.AppendChild(sliderFactory); FrameworkElementFactory textFacotry = new FrameworkElementFactory(typeof(TextBlock), "TextBlock");
textFacotry.SetValue(TextBlock.TextProperty, new Binding(nameof(SliderUIDataContext.Value)));
//textBoxFactory.AddHandler(TextBox.IsVisibleChanged, new DependencyPropertyChangedEventHandler(SearchBoxVisibleChanged));
ct.VisualTree.AppendChild(textFacotry);
ct.Seal();
return ct;
} /// <summary>
/// Creates the ComboBox edit template.
/// </summary>
/// <param name="customUIAttribute">The custom UI attribute.</param>
/// <returns></returns>
private DataTemplate CreateComboBoxEditTemplate(CustomUIAttribute customUIAttribute)
{
DataTemplate template = new DataTemplate();
template.VisualTree = new FrameworkElementFactory(typeof(DockPanel));
template.VisualTree.SetValue(DockPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName)); FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock)) ;
textFactory.SetValue(TextBlock.TextProperty, new Binding(nameof(ComboBoxEditDataContext.Name)));
template.VisualTree.AppendChild(textFactory); FrameworkElementFactory comboBoxEditFactory = new FrameworkElementFactory(typeof(ComboBoxEdit));
comboBoxEditFactory.SetBinding(ComboBoxEdit.ItemsSourceProperty, new Binding(nameof(ComboBoxEditDataContext.ItemSource)));
comboBoxEditFactory.SetBinding(ComboBoxEdit.EditValueProperty, new Binding(nameof(ComboBoxEditDataContext.EditValue)));
comboBoxEditFactory.SetBinding(ComboBoxEdit.SelectedIndexProperty, new Binding(nameof(ComboBoxEditDataContext.SelectedIndex)));
comboBoxEditFactory.SetValue(ComboBoxEdit.ItemTemplateProperty, (DataTemplate)_element.TryFindResource("ComboBoxEditItemTemplate"));
template.VisualTree.AppendChild(comboBoxEditFactory);
template.Seal();
return template;
} /// <summary>
/// Creates the property definition template.
/// </summary>
/// <param name="customUIAttribute">The custom UI attribute.</param>
/// <returns></returns>
private DataTemplate CreatePropertyDefinitionTemplate(CustomUIAttribute customUIAttribute)
{
DataTemplate dataTemplate = new DataTemplate();
DataTemplate cellTemplate = null;//单元格模板
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(PropertyDefinition));
dataTemplate.VisualTree = factory;
switch (customUIAttribute.CustomUI)
{
case CustomUITypes.Slider:
cellTemplate = CreateSliderDataTemplate(customUIAttribute); break;
//cellTemplate = (DataTemplate)_element.TryFindResource("SliderTemplate");break;
case CustomUITypes.ComboBoxEit:
cellTemplate = CreateComboBoxEditTemplate(customUIAttribute);break; } if (cellTemplate != null)
{
factory.SetValue(PropertyDefinition.CellTemplateProperty, cellTemplate);
dataTemplate.Seal(); }
else
{
return null;
}
return dataTemplate;
}
}
}

DynamicallyAssignDataEditorsTemplateSelector

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; namespace PropertyGridDemo.PropertyGridControl
{
/// <summary>
///初始化所有属性并调用模板选择器进行匹配
/// </summary>
public class DataEditorsViewModel
{
public IEnumerable<PropertyDescriptor> Properties { get { return TypeDescriptor.GetProperties(typeof(TestPropertyGrid)).Cast<PropertyDescriptor>(); } }
}
}

DataEditorsViewModel

3.编写一个可用于构建模板的属性 CustomUIType:

using System;

namespace PropertyGridDemo.PropertyGridControl
{
public class CustomUIType
{ } public enum CustomUITypes
{
Slider,
ComboBoxEit,
SpinEdit,
CheckBoxEdit
} [AttributeUsage(AttributeTargets.Property)]
internal class CustomUIAttribute : Attribute
{
public string DataContextPropertyName { get; set; }
public CustomUITypes CustomUI { get; set; }
/// <summary>
/// 自定义控件属性构造函数
/// </summary>
/// <param name="uiTypes">The UI types.</param>
/// <param name="dataContextPropertyName">Name of the data context property.</param>
internal CustomUIAttribute(CustomUITypes uiTypes, string dataContextPropertyName)
{
CustomUI = uiTypes;
DataContextPropertyName = dataContextPropertyName;
}
} }

CustomUIType

4.编写对应的DataContext类 TestPropertyGrid:

 using DevExpress.Mvvm.DataAnnotations;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Timers;
using System.Windows; namespace PropertyGridDemo.PropertyGridControl
{
[MetadataType(typeof(DynamicallyAssignDataEditorsMetadata))]
public class TestPropertyGrid : PropertyDataContext
{
private double _count = ;
private SliderUIDataContext _countSource = null;
private ComboBoxEditDataContext _comboSource = null;
private double _value=; public TestPropertyGrid()
{
Password = "";
Notes = "Hello";
Text = "Hello hi";
} [Browsable(false)]
public SliderUIDataContext CountSource
{
get
{
if (_countSource != null)
{ return _countSource;
}
else
{
_countSource = new SliderUIDataContext(, , Count, 0.1, );
_countSource.PropertyChanged += (object o, PropertyChangedEventArgs e) =>
{
this.Count = _countSource.Value;
};
return _countSource;
}
}
} [Browsable(false)]
public ComboBoxEditDataContext ComboSource
{
get
{
if(_comboSource==null)
{
_comboSource =new ComboBoxEditDataContext(ComboBoxEditItemSource.TestItemSource,Value);
_comboSource.PropertyChanged += (object o, PropertyChangedEventArgs e) =>
{
this.Value =Convert.ToDouble(_comboSource.EditValue.Item2);
}; }
return _comboSource;
}
} [Display(Name = "SliderEdit", GroupName = "CustomUI")]
[CustomUI(CustomUITypes.Slider, nameof(CountSource))]
public double Count
{
get => _count;
set
{
_count = value;
CountSource.Value = value;
RaisePropertyChanged(nameof(Count));
}
} [Display(Name = "ComboBoxEditItem", GroupName = "CustomUI")]
[CustomUI(CustomUITypes.ComboBoxEit, nameof(ComboSource))]
public double Value
{
get => _value;
set
{
if (_value == value) return;
_value = value;
//ComboSource.Value = value;
RaisePropertyChanged(nameof(Value));
}
} [Display(Name = "Password", GroupName = "DefaultUI")]
public string Password { get; set; } [Display(Name = "TextEdit", GroupName = "DefaultUI")]
public string Text { get; set; } [Display(Name = "Notes", GroupName = "DefaultUI")]
public string Notes { get; set; } [Display(Name = "Double", GroupName = "DefaultUI")]
[DefaultValue()]
public double TestDouble { get; set; } [Display(Name = "Items", GroupName = "DefaultUI")]
[DefaultValue(Visibility.Visible)]
public Visibility TestItems { get; set; }
} public static class DynamicallyAssignDataEditorsMetadata
{
public static void BuildMetadata(MetadataBuilder<TestPropertyGrid> builder)
{
builder.Property(x => x.Password)
.PasswordDataType(); builder.Property(x => x.Notes)
.MultilineTextDataType();
}
}
}

TestPropertyGrid

该类中用到的其他类主要有以下几个,以下几个类主要用于数据绑定:

namespace PropertyGridDemo.PropertyGridControl
{
public class SliderUIDataContext:PropertyDataContext
{
private double _value = ;
private double _max = ;
private double _min = ;
private double _smallChange = ;
private double _largeChange=; public SliderUIDataContext()
{ } /// <summary>
/// Initializes a new instance of the <see cref="SliderUIDataContext"/> class.
/// </summary>
/// <param name="min">The minimum.</param>
/// <param name="max">The maximum.</param>
/// <param name="value">The value.</param>
/// <param name="smallChange">The small change.</param>
/// <param name="largeChange">The large change.</param>
public SliderUIDataContext(double min, double max, double value,double smallChange=0.01,double largeChange=0.1)
{
SmallChange = smallChange;
LargeChange = largeChange;
Max = max;
Min = min;
Value = value;
} /// <summary>
/// Gets or sets the small change.
/// </summary>
/// <value>
/// The small change.
/// </value>
public double SmallChange
{
get => _smallChange;
set
{
if (value == _min) return;
_min = value;
RaisePropertyChanged(nameof(SmallChange));
}
} /// <summary>
/// Gets or sets the large change.
/// </summary>
/// <value>
/// The large change.
/// </value>
public double LargeChange
{
get => _largeChange;
set
{
if (Value == _largeChange) return;
_largeChange = value;
RaisePropertyChanged(nameof(LargeChange));
}
} /// <summary>
/// Gets or sets the maximum.
/// </summary>
/// <value>
/// The maximum.
/// </value>
public double Max
{
get => _max;
set
{
if (value == _max) return;
_max = value;
RaisePropertyChanged(nameof(Max));
}
} /// <summary>
/// Gets or sets the minimum.
/// </summary>
/// <value>
/// The minimum.
/// </value>
public double Min
{
get => _min;
set
{
if (value == _min) return;
_min = value;
RaisePropertyChanged(nameof(Min));
}
} /// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public double Value
{
get => _value;
set
{
if (value == _value) return;
_value = value;
RaisePropertyChanged(nameof(Value));
}
}
}
}

SliderUIDataContext

using System;
using System.Linq; namespace PropertyGridDemo.PropertyGridControl
{
public class ComboBoxEditDataContext:PropertyDataContext
{
private Tuple<string, object>[] _itemSource;
private Tuple<string, object> _editValue;
private int _selectedIndex; /// <summary>
/// Initializes a new instance of the <see cref="ComboBoxEditDataContext"/> class.
/// </summary>
/// <param name="itemSource">The item source.</param>
/// <param name="editValue">The edit value.</param>
public ComboBoxEditDataContext(Tuple<string,object>[] itemSource,Tuple<string,object> editValue)
{
_itemSource = itemSource;
_editValue = _itemSource.FirstOrDefault(x => x?.Item1.ToString() == editValue?.Item1.ToString() && x?.Item2?.ToString() == x?.Item2?.ToString());
} /// <summary>
/// Initializes a new instance of the <see cref="ComboBoxEditDataContext" /> class.
/// </summary>
/// <param name="itemSource">The item source.</param>
/// <param name="value">The value.</param>
public ComboBoxEditDataContext(Tuple<string, object>[] itemSource, object value)
{
_itemSource = itemSource;
_editValue = _itemSource.FirstOrDefault(x => x?.Item2.ToString() == value.ToString() );
} public string Name
{
get;set;
} /// <summary>
/// Gets or sets the item source.
/// </summary>
/// <value>
/// The item source.
/// </value>
public Tuple<string,object>[] ItemSource
{
get => _itemSource;
set
{
//if (_itemSource == value) return;
_itemSource = value;
RaisePropertyChanged(nameof(ItemSource));
}
} /// <summary>
/// Gets or sets the edit value.
/// </summary>
/// <value>
/// The edit value.
/// </value>
public Tuple<string,object> EditValue
{
get => _editValue;
set
{
if (_editValue == value) return;
_editValue = value;
RaisePropertyChanged(nameof(EditValue));
}
} public object Value
{
set
{
EditValue = ItemSource.FirstOrDefault(x => x.Item2.Equals(value));
}
} /// <summary>
/// Gets or sets the index of the selected.
/// </summary>
/// <value>
/// The index of the selected.
/// </value>
public int SelectedIndex
{
get => _selectedIndex;
set
{
if (_selectedIndex == value || value==-) return;
_selectedIndex = value;
EditValue = ItemSource[value];
RaisePropertyChanged(nameof(SelectedIndex));
}
}
}
}

ComboBoxEditDataContext

using System.ComponentModel;

namespace PropertyGridDemo.PropertyGridControl
{
public class PropertyDataContext:INotifyPropertyChanged
{
/// <summary>
/// 在更改属性值时发生。
/// </summary>
public event PropertyChangedEventHandler PropertyChanged; /// <summary>
/// 触发属性变化
/// </summary>
/// <param name="propertyName"></param>
public virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

PropertyDataContext

using System;

namespace PropertyGridDemo.PropertyGridControl
{
internal static class ComboBoxEditItemSource
{
internal static Tuple<string, object>[] TestItemSource = new Tuple<string, object>[] {
new Tuple<string, object>("",),
new Tuple<string, object>("",),
new Tuple<string, object>("",)
};
}
}

ComboBoxEditItemSource

5.将以上的CustomPropertyGrid丢进容器中即可,这里我直接用Mainwindow来演示:

 <Window
x:Class="PropertyGridDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:PropertyGridControl="clr-namespace:PropertyGridDemo.PropertyGridControl"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PropertyGridDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
WindowState="Maximized"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="259*" />
<ColumnDefinition Width="259*" />
</Grid.ColumnDefinitions> <TextBox
x:Name="OutputBox"
Grid.ColumnSpan="1"
HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True" />
<PropertyGridControl:CustomPropertyGrid x:Name="PropertyGrid" Grid.Column="1" />
</Grid>
</Window>

MainWindow

运行示意图:

以上就是自定义PropertyGrid控件的实现代码,本人只实现了简单的Slider和ComboBoxEdit控件,实际上可以根据自己的需要仿照以上的方法扩展到其他控件,这个就看需求了。

个人感觉以上方案还是有所欠缺,主要是自定义控件的模板是由代码生成的,如果可以直接从资源文件中读取将会更加方便,不过本人尝试了几次并不能成功的实现数据的绑定,如果大家有什么好的解决方案欢迎在评论区留言,也欢迎大家在评论区进行讨论。

以上内容均为原创,转发请注明出处,谢谢!

PropertyGrid自定义控件的更多相关文章

  1. C# 如何定义让PropertyGrid控件显示[...]按钮,并且点击后以下拉框形式显示自定义控件编辑属性值

    关于PropertyGrid控件的详细用法请参考文献: 1.C# PropertyGrid控件应用心得 2.C#自定义PropertyGrid属性 首先定义一个要在下拉框显示的控件: using Sy ...

  2. PropertyGrid控件由浅入深(二):基础用法

    目录 PropertyGrid控件由浅入深(一):文章大纲 PropertyGrid控件由浅入深(二):基础用法 控件的外观构成 控件的外观构成如下图所示: PropertyGrid控件包含以下几个要 ...

  3. 【C#】妈妈再也不用担心自定义控件如何给特殊类型的属性添加默认值了,附自定义GroupBox一枚

    ------------------更新:201411190903------------------ 经过思考和实践,发现套路中的第1条是不必要的,就是完全可以不用定义一个名为Default+属性名 ...

  4. 自定义控件如何给特殊类型的属性添加默认值 z

    定义控件如何给特殊类型的属性添加默认值了,附自定义GroupBox一枚 标题有点那啥,但确实能表达我掌握此法后的心情. 写自定义控件时往往会有一个需求,就是给属性指定一个默认值(就是可以在VS中右键该 ...

  5. 自定义控件如何给特殊类型的属性添加默认值 z(转)

    自定义控件如何给特殊类型的属性添加默认值 z 定义控件如何给特殊类型的属性添加默认值了,附自定义GroupBox一枚 标题有点那啥,但确实能表达我掌握此法后的心情. 写自定义控件时往往会有一个需求,就 ...

  6. android自定义控件一站式入门

    自定义控件 Android系统提供了一系列UI相关的类来帮助我们构造app的界面,以及完成交互的处理. 一般的,所有可以在窗口中被展示的UI对象类型,最终都是继承自View的类,这包括展示最终内容的非 ...

  7. ASP.NET MVC学习之母版页和自定义控件的使用

    一.母板页_Layout.cshtml类似于传统WebForm中的.master文件,起到页面整体框架重用的目地1.母板页代码预览 <!DOCTYPE html> <html> ...

  8. C# 自定义控件VS用户控件

    1 自定义控件与用户控件区别 WinForm中, 用户控件(User Control):继承自 UserControl,主要用于开发 Container 控件,Container控件可以添加其他Con ...

  9. 自定义控件之 圆形 / 圆角 ImageView

    一.问题在哪里? 问题来源于app开发中一个很常见的场景——用户头像要展示成圆的:       二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在 ...

随机推荐

  1. 新手介绍简单一下iOS开发中几种界面传值

    首先在处理iOS-UI中,也许在很多地方需要用到两种甚至多种不同界面之间的传值,相比这也是很多iOS入门成员头疼问题,同样作为新手的我在接触这类传值时候也一脸懵然,经过一段时间的研究,对于简单的传值有 ...

  2. React之key详解

    一个例子 有这样的一个场景如下图所示,有一组动态数量的input,可以增加和删除和重新排序,数组元素生成的组件用index作为key的值,例如下图生成的ui展示: 上面例子中的input组件渲染的代码 ...

  3. webrtc学习笔记2(Android端demo代码结构)

    最近正在修改webrtc的Android端demo和服务器交互的内容,介绍一下demo的大体结构吧,以便能快速回忆. 环境:Android5.0以上.libjingle_peerconnection_ ...

  4. js中的事件委托详解

    概述: 那什么叫事件委托呢?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件.那这是什么意思呢?网上的各位 ...

  5. 杜教筛 && bzoj3944 Sum

    Description Input 一共T+1行 第1行为数据组数T(T<=10) 第2~T+1行每行一个非负整数N,代表一组询问 Output 一共T行,每行两个用空格分隔的数ans1,ans ...

  6. web.xml 中配置了error-page但不起作用问题

    问题: 在web.xml 中配置了 error-page,但是好像不起作用,就是跳转不到指定的页面. 配置信息如下: <!-- 400错误 --> <error-page> & ...

  7. 【react】利用prop-types第三方库对组件的props中的变量进行类型检测

    1.引言--JavaScript就是一个熊孩子   1.1对于JSer们来说,js是自由的,但同时又有许多让人烦恼的地方.javascript很多时候就是这么一个熊孩子,他很多时候并不会像C和java ...

  8. Linux Shell——函数的使用

    文/一介书生,一枚码农. scripts are for lazy people. 函数是存在内存里的一组代码的命名的元素.函数创建于脚本运行环境之中,并且可以执行. 函数的语法结构为: functi ...

  9. C#基础知识-编写第一个程序(二)

    通过上一篇数据类型已经介绍了C#中最基本的15种预定义数据类型,了解每一种类型代表的数据以及每种类型的取值范围,这是很重要也是最基本.下面我们通过实例来了解每个类型如何去使用.编写C#程序时我们需要用 ...

  10. 简单五子棋,没有电脑AI

    刚学了C#委托,做了个五子棋练习,把前台绘制和后台逻辑分开,前台绘制方法用委托传给后台逻辑. 界面好简单... 先看类图 控制类控制整个游戏的逻辑,包括调用棋盘类的属性初始化棋盘.初始化两个棋手.轮流 ...