0

我在绑定到我的控件时遇到问题。我希望我的控件中的标签(lblLabel)显示来自绑定到字段属性的任何内容的元数据。它当前将“字段”显示为标签。如何让它显示“客户名称:”,它是视图模型中属性 CustomerName 的名称?

我的控件 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; }
    }
}

调用 My Control 的 XAML

此页面数据上下文设置为客户类型的属性(视图模型)。

<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>
4

1 回答 1

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来设置字段的标签。我发现最可靠的称呼是 from 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包含一个带有 nameExampleFieldNameKey和 value的键This value is in a Resources.resx。该集合的 Access Modifier 也设置为 Public。)我使用以下 XAML 测试了我对控件的修改,并DataContext设置为 view-model 的实例上面介绍的类:

<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,带有以下标签:

此值在显示属性中
无显示
该值在 Resources.resx 中
此值在显示属性中
于 2012-07-19T22:24:54.650 回答