如何绑定到自定义silverlight控件?

时间:2012-07-16 15:42:33

标签: silverlight binding controls metadata

我遇到了与我的控件绑定的问题。我希望我的控件中的标签(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>

1 个答案:

答案 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