我遇到了与我的控件绑定的问题。我希望我的控件中的标签(lblLabel)显示来自Field Property的任何内容的元数据。它当前显示“Field”作为标签。如何让它显示“客户名称:”,这是属性,客户名称的视图模型上的名称?
我的控件XAML
<UserControl x:Name="ctlRowItem" x:Class="ApplicationShell.Controls.RowItem"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:my="clr-namespace:SilverlightApplicationCore.Controls;assembly=SilverlightApplicationCore"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="g_required" Width="15" />
<ColumnDefinition x:Name="g_label" Width="200" />
<ColumnDefinition x:Name="g_control" Width="auto" />
<ColumnDefinition x:Name="g_fieldEnd" Width="*" />
</Grid.ColumnDefinitions>
<sdk:Label x:Name="lblRequired" Grid.Column="0" Grid.Row="0" />
<sdk:Label x:Name="lblLabel" Grid.Column="1" Grid.Row="3" Target="{Binding ElementName=txtControl}" PropertyPath="Field" />
<TextBox x:Name="txtControl" Grid.Column="2" Grid.Row="3" MaxLength="10" Width="150" Text="{Binding Field, Mode=TwoWay, ElementName=ctlRowItem}" />
</Grid>
</UserControl>
我的控制代码背后
using System.Windows;<BR>
using System.Windows.Controls;<BR>
using System.Windows.Data;<BR>
using ApplicationShell.Resources;<BR>
namespace ApplicationShell.Controls
{
public partial class RowItem : UserControl
{
#region Properties
public object Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
#region Dependency Properties
public static readonly DependencyProperty FieldProperty = DependencyProperty.Register("Field", typeof(object), typeof(RowItem), new PropertyMetadata(null, Field_PropertyChangedCallback));
#endregion
#endregion
#region Events
#region Dependency Properties
private static void Field_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
return;
var control = (RowItem)d;
control.Field = (object)e.NewValue;
}
#endregion
#endregion
#region Constructor
public RowItem()
{
InitializeComponent();
}
#endregion
}
}
查看模型
namespace ApplicationShell.Web.ViewModel
{
[Serializable]
public class Customers
{
[Display(AutoGenerateField = false, ShortName="CustomerName_Short", Name="CustomerName_Long", ResourceType = typeof(LocaleLibrary))]
public override string CustomerName { get; set; }
}
}
调用我的控件的XAML
此页面datacontext设置为Customers(View Model)类型的属性。
<controls:ChildWindow x:Class="ApplicationShell.CustomerWindow"
xmlns:my="clr-namespace:SilverlightApplicationCore.Controls;assembly=SilverlightApplicationCore"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Title="Customer View">
<my:RowItem x:name="test" Field="{Binding CustomerName,Mode=TwoWay}" />
</controls:ChildWindow>
答案 0 :(得分:0)
有一种方法可以获取绑定到的属性的显示名称,但遗憾的是它不是微不足道的,我们必须对所使用的属性路径做出假设。
我知道Silverlight Toolkit ValidationSummary能够自动找出绑定的属性名称,但是当我查看其源代码时,我发现它通过自己对绑定路径的评估来实现这一点。
所以,这就是我将采取的方法。
我修改了RowItem
用户控件的代码隐藏,这就是我想出的:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public partial class RowItem : UserControl
{
public RowItem()
{
InitializeComponent();
Dispatcher.BeginInvoke(SetFieldLabel);
}
public string Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
public static readonly DependencyProperty FieldProperty =
DependencyProperty.Register("Field", typeof(string), typeof(RowItem),
null);
/// <summary>
/// Return the display name of the property at the end of the given binding
/// path from the given source object.
/// </summary>
/// <remarks>
/// <para>
/// The display name of the property is the name of the property according
/// to a <see cref="DisplayAttribute"/> set on the property, if such an
/// attribute is found, otherwise the name of the property.
/// </para>
/// <para>
/// This method supports dot-separated binding paths only. Binding
/// expressions such <c>[0]</c> or <c>(...)</c> are not supported and will
/// cause this method to return null.
/// </para>
/// <para>
/// If no suitable property could be found (due to an intermediate value
/// of the property-path evaluating to <c>null</c>, or no property with a
/// given name being found), <c>null</c> is returned. The final property
/// in the path can have a <c>null</c> value, as that value is never used.
/// </para>
/// </remarks>
/// <param name="binding">The binding expression.</param>
/// <param name="source">
/// The source object at which to start the evaluation.
/// </param>
/// <returns>
/// The display name of the property at the end of the binding, or
/// <c>null</c> if this could not be determined.
/// </returns>
private string GetBindingPropertyDisplayName(BindingExpression binding,
object source)
{
if (binding == null)
{
throw new ArgumentNullException("binding");
}
string bindingPath = binding.ParentBinding.Path.Path;
object obj = source;
PropertyInfo propInfo = null;
foreach (string propertyName in bindingPath.Split('.'))
{
if (obj == null)
{
// Null object not at the end of the path.
return null;
}
Type type = obj.GetType();
propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
// No property with the given name.
return null;
}
obj = propInfo.GetValue(obj, null);
}
DisplayAttribute displayAttr =
propInfo.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (displayAttr != null)
{
return displayAttr.GetName();
}
else
{
return propInfo.Name;
}
}
private void SetFieldLabel()
{
BindingExpression binding = this.GetBindingExpression(FieldProperty);
string displayName = GetBindingPropertyDisplayName(binding,
DataContext);
if (lblLabel != null)
{
lblLabel.Content = displayName;
}
}
}
有几点需要注意:
要使用此代码,您的项目需要引用System.ComponentModel.DataAnnotations
。但是,这不应该是一个问题,因为使用Display
属性需要相同的参考。
调用函数SetFieldLabel
来设置字段的标签。我发现最可靠的地方是来自Dispatcher.BeginInvoke
。直接从构造函数内或从Loaded
事件处理程序中调用此方法不起作用,因为此时尚未设置绑定。
仅支持由以点分隔的属性名称列表组成的绑定路径。像SomeProp.SomeOtherProp.YetAnotherProp
这样的东西很好,但SomeProp.SomeList[0]
不受支持且不起作用。如果无法确定绑定属性的显示名称,则不会显示任何内容。
Field
依赖项属性上不再有PropertyChangedCallback。我们对用户更改控件中的文本时发生的事情并不感兴趣。它不会更改绑定到的属性的显示名称。
出于测试目的,我敲了下面的视图模型类:
public class ViewModel
{
// INotifyPropertyChanged implementation omitted.
[Display(Name = "This value is in a Display attribute")]
public string WithDisplay { get; set; }
public string WithoutDisplay { get; set; }
[Display(Name = "ExampleFieldNameKey", ResourceType = typeof(Strings))]
public string Localised { get; set; }
public object This { get { return this; } }
public object TheVerySame { get { return this; } }
}
(Resources集合Strings.resx
包含一个名为ExampleFieldNameKey
且值为This value is in a Resources.resx
的密钥。此集合的Access Modifier也设置为Public。)我测试了我的修改您使用以下XAML进行控制,并将DataContext
设置为上面显示的视图模型类的实例:
<StackPanel>
<local:RowItem Field="{Binding Path=WithDisplay, Mode=TwoWay}" />
<local:RowItem Field="{Binding Path=WithoutDisplay, Mode=TwoWay}" />
<local:RowItem Field="{Binding Path=Localised, Mode=TwoWay}" />
<local:RowItem Field="{Binding Path=This.This.TheVerySame.This.WithDisplay, Mode=TwoWay}" />
</StackPanel>
这给了我四个RowItems
,标签如下:
This value is in a Display attribute WithoutDisplay This value is in a Resources.resx This value is in a Display attribute