基于通用对象属性的 WPF UserControl
WPF UserControl based on generic object properties
我有一个基于字节数据的对象,它包含我关心的 200 多个属性,从某种意义上说,我想 (1) 知道值,以及 (2) 知道值何时从一条消息变为接下来。
我正在使用的 XAML 的一个片段:
<Label Content="Prop Name" />
<TextBlock Text="{Binding PropName}"
Background="{Binding PropName,
Converter={StaticResource CompareToLastValueConverter}}" />
目前,我为每个 属性 粘贴了这些行,并设置了适当的网格位置。
我的问题是:有没有一种很好的方法来创建一个 嵌套 WPF UserControl,它从 模型 属性 中获取通用对象 属性 =23=] 并处理将名称(带空格)分配给 Label,然后像上面的示例一样将 属性 的值分配给 TextBlock?
此外,这是思考这个问题的最佳方式吗,还是我在做事的 "WPF way" 中遗漏了一个 link?
我经常想试试这个。我会为 PropertyInfo 创建一个 ItemsControl 模板。
我创建了一个测试 class:
public class MyClass
{
public string PropertyTest1 {get;set;}
public string PropertyTest2 { get; set; }
public string PropertyTest3 { get; set; }
public string PropertyTest4 { get; set; }
}
显示属性。在我的显示数据上下文中,我有两个要绑定的东西。 PropertyInfos 的列表,以及有问题的对象。由于 PropertyInfo 是静态的,您可以使用转换器或其他东西以更好的方式执行此操作,而无需将其绑定到 属性:
public PropertyInfo[] Properties
{
get { return typeof(MyClass).GetProperties(); }
}
public MyClass MyObject
{
get { return new MyClass { PropertyTest1 = "test", PropertyTest3 = "Some string", PropertyTest4 = "Last Property" }; }
}
现在,显示属性很容易:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.Resources>
<local:PropertyInfoValueConverter x:Key="PropertyInfoValueConverter"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Margin="4,2"/>
<TextBlock Grid.Column="1" Margin="4,2"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
但是这些是'static',我们不能绑定到任何值。解决这个问题的一种方法是使用标签 属性 和多绑定转换器:
因此,让我们将 Tag="{Binding MyObject}"
添加到我们的 ItemsSource,并将其和 PropertyInfo 放入第二个文本块的值转换器中:
<TextBlock Grid.Column="1" Margin="4,2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PropertyInfoValueConverter}">
<Binding Path=""/>
<Binding ElementName="PropertyDisplay" Path="Tag"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
转换器实际上非常简单,特别是因为您没有使用文本框(因此只进入只读方向):
public class PropertyInfoValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PropertyInfo propertyInfo = values[0] as PropertyInfo;
return propertyInfo.GetValue(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
这是结果:
你说你想要名字的空格,这可以通过一个转换器来完成,这个转换器有一些逻辑来寻找你所拥有的任何命名约定(大写字母前的空格?)。
使用模板选择器来选择布尔、字符串、浮点模板并区别对待它们会很有趣。 (复选框、文本、00.00 格式化文本等)
编辑:探索模板选择器
这是一个示例模板选择器:
public class PropertyInfoTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate DecimalTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = item as PropertyInfo;
if (propertyInfo.PropertyType == typeof(string))
{
return StringTemplate;
}
else if (propertyInfo.PropertyType == typeof(int))
{
return IntegerTemplate;
}
else if (propertyInfo.PropertyType == typeof(float) || propertyInfo.PropertyType == typeof(double))
{
return DecimalTemplate;
}
else if (propertyInfo.PropertyType == typeof(bool))
{
return BooleanTemplate;
}
return DefaultTemplate;
}
}
我们的 ItemsControl 现在很简单:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
Tag="{Binding MyObject}"
ItemTemplateSelector="{StaticResource PropertyInfoTemplateSelector}"
Margin="20"/>
我还使用此转换器在名称中添加了空格:
public class PropertyInfoNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (string.IsNullOrWhiteSpace(text))
return string.Empty;
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]))
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
(char.IsUpper(text[i - 1]) &&
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
(归功于此:)。
更新我们的 class 以包含一些布尔值和 fload 字段:
我有一个基于字节数据的对象,它包含我关心的 200 多个属性,从某种意义上说,我想 (1) 知道值,以及 (2) 知道值何时从一条消息变为接下来。
我正在使用的 XAML 的一个片段:
<Label Content="Prop Name" />
<TextBlock Text="{Binding PropName}"
Background="{Binding PropName,
Converter={StaticResource CompareToLastValueConverter}}" />
目前,我为每个 属性 粘贴了这些行,并设置了适当的网格位置。
我的问题是:有没有一种很好的方法来创建一个 嵌套 WPF UserControl,它从 模型 属性 中获取通用对象 属性 =23=] 并处理将名称(带空格)分配给 Label,然后像上面的示例一样将 属性 的值分配给 TextBlock?
此外,这是思考这个问题的最佳方式吗,还是我在做事的 "WPF way" 中遗漏了一个 link?
我经常想试试这个。我会为 PropertyInfo 创建一个 ItemsControl 模板。
我创建了一个测试 class:
public class MyClass
{
public string PropertyTest1 {get;set;}
public string PropertyTest2 { get; set; }
public string PropertyTest3 { get; set; }
public string PropertyTest4 { get; set; }
}
显示属性。在我的显示数据上下文中,我有两个要绑定的东西。 PropertyInfos 的列表,以及有问题的对象。由于 PropertyInfo 是静态的,您可以使用转换器或其他东西以更好的方式执行此操作,而无需将其绑定到 属性:
public PropertyInfo[] Properties
{
get { return typeof(MyClass).GetProperties(); }
}
public MyClass MyObject
{
get { return new MyClass { PropertyTest1 = "test", PropertyTest3 = "Some string", PropertyTest4 = "Last Property" }; }
}
现在,显示属性很容易:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.Resources>
<local:PropertyInfoValueConverter x:Key="PropertyInfoValueConverter"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Margin="4,2"/>
<TextBlock Grid.Column="1" Margin="4,2"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
但是这些是'static',我们不能绑定到任何值。解决这个问题的一种方法是使用标签 属性 和多绑定转换器:
因此,让我们将 Tag="{Binding MyObject}"
添加到我们的 ItemsSource,并将其和 PropertyInfo 放入第二个文本块的值转换器中:
<TextBlock Grid.Column="1" Margin="4,2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PropertyInfoValueConverter}">
<Binding Path=""/>
<Binding ElementName="PropertyDisplay" Path="Tag"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
转换器实际上非常简单,特别是因为您没有使用文本框(因此只进入只读方向):
public class PropertyInfoValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PropertyInfo propertyInfo = values[0] as PropertyInfo;
return propertyInfo.GetValue(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
这是结果:
你说你想要名字的空格,这可以通过一个转换器来完成,这个转换器有一些逻辑来寻找你所拥有的任何命名约定(大写字母前的空格?)。
使用模板选择器来选择布尔、字符串、浮点模板并区别对待它们会很有趣。 (复选框、文本、00.00 格式化文本等)
编辑:探索模板选择器
这是一个示例模板选择器:
public class PropertyInfoTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate DecimalTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = item as PropertyInfo;
if (propertyInfo.PropertyType == typeof(string))
{
return StringTemplate;
}
else if (propertyInfo.PropertyType == typeof(int))
{
return IntegerTemplate;
}
else if (propertyInfo.PropertyType == typeof(float) || propertyInfo.PropertyType == typeof(double))
{
return DecimalTemplate;
}
else if (propertyInfo.PropertyType == typeof(bool))
{
return BooleanTemplate;
}
return DefaultTemplate;
}
}
我们的 ItemsControl 现在很简单:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
Tag="{Binding MyObject}"
ItemTemplateSelector="{StaticResource PropertyInfoTemplateSelector}"
Margin="20"/>
我还使用此转换器在名称中添加了空格:
public class PropertyInfoNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (string.IsNullOrWhiteSpace(text))
return string.Empty;
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]))
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
(char.IsUpper(text[i - 1]) &&
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
(归功于此:)。
更新我们的 class 以包含一些布尔值和 fload 字段: