2012-07-16 47 views
0

我有问题绑定到我的控制。我希望我的控件中的标签(lblLabel)显示绑定到Field Property的任何元数据。它当前显示“字段”作为标签。如何让它显示属性CustomerName的视图模型名称“Customer Name:”?如何绑定到自定义Silverlight控件?

我的控制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

有一个在绑定属性的显示名称得到的一种方式,但遗憾的是它是不平凡的,我们有权对使用的属性路径的假设。

我知道,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。直接从构造函数或事件处理函数内调用此方法不起作用,因为到那时还没有设置绑定。

  • 仅支持由点分隔的属性名称列表组成的绑定路径。像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这个系列也有其)我使用以下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 
+0

您好。谢谢回复。我无法得到它的工作。我的“propInfo”变量始终为空,所以我永远不会收到显示名称。我试图设置datacontext,但它仍然没有工作。你是否也可以为控件发送更新的XAML。谢谢。 – ChrisCrous 2012-07-24 10:54:30

+0

我没有更改您的RowItem控件的XAML。如果在调用GetProperty()后'propInfo'为'null',这意味着'obj'没有名称为'propertyName'的属性。是否将'obj'作为开始评估绑定属性路径的对象传入?对于循环中的每一步,'obj'上是否存在名为'propertyName'的属性? – 2012-07-24 20:39:32

+0

嗨在那里,调用source时为null,因此obj为null。然后我尝试设置我的控件(RowItem)的datacontext,但它仍然为空。我不知道为什么。我一直认为,如果你没有在控件上指定datacontext,那么它使用父级datacontext。所以底线是它没有设置控件的数据上下文。我如何解决这个问题? – ChrisCrous 2012-07-30 08:18:31