如何绑定到自定义的Silverlight控件? [英] How do I bind to a custom silverlight control?

查看:82
本文介绍了如何绑定到自定义的Silverlight控件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在绑定控件时遇到问题.我希望控件中的label(lblLabel)显示绑定到Field属性的任何内容的元数据.当前,它显示字段"作为标签.如何显示"Customer Name:"(客户名称:),它是视图模型上属性"CustomerName"的名称?

I am having problems binding to my control. I would like the label(lblLabel) in my control to display the metadata from whatever is bound to the Field Property. It currently displays "Field" as a label. How do I get it to display "Customer Name :" which is the Name on the view model for property, 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; }
    }
}

调用我的控件的XAML

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

This pages datacontext is set to a property of type 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>

推荐答案

有一种获取绑定属性的显示名称的方法,但可惜的是它并不简单,我们必须对属性路径进行假设使用.

There is a way of getting at the display names of the properties bound to, but sadly it is not trivial and we have to make assumptions about the property-paths used.

我知道

I'm aware that the Silverlight Toolkit ValidationSummary is able to find out property names of bindings automatically, but when I looked through its source code, I found that it does this by doing its own evaluation of the binding path.

所以,这就是我在这里采用的方法.

So, that's the approach I'll take here.

我修改了RowItem用户控件的代码,这就是我想出的:

I modified the code-behind of your RowItem user-control, and this is what I came up with:

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属性所需的参考相同.

  • To use this code, your project will need a reference to System.ComponentModel.DataAnnotations. However, that shouldn't be a problem as that's the same reference you need in order to use the Display attribute.

调用函数SetFieldLabel来设置字段的标签.我发现最可靠的调用位置是Dispatcher.BeginInvoke.直接从构造函数内部或从Loaded事件处理程序内部调用此方法不起作用,因为那时尚未设置绑定.

The function SetFieldLabel is called to set the label of the field. I found that the most reliable place to call it was from Dispatcher.BeginInvoke. Calling this method directly from within the constructor or from within a Loaded event handler did not work, as the binding had not been set up by then.

仅支持由点名分隔的属性名称列表组成的绑定路径.像SomeProp.SomeOtherProp.YetAnotherProp之类的东西就可以了,但是SomeProp.SomeList[0]不被支持并且无法使用.如果无法确定绑定属性的显示名称,将不会显示任何内容.

Only binding paths consisting of a dot-separated list of property names are supported. Something like SomeProp.SomeOtherProp.YetAnotherProp are fine, but SomeProp.SomeList[0] is not supported and will not work. If the display name of the binding property cannot be determined, nothing will be displayed.

Field依赖项属性上不再有PropertyChangedCallback.当用户更改控件中的文本时,我们对发生的事情并不真正感兴趣.不会更改绑定到的属性的显示名称.

There's no longer a PropertyChangedCallback on the Field dependency property. We're not really interested in what happens whenever the user changes the text in the control. It's not going to change the display name of the property bound to.

出于测试目的,我关闭了以下视图模型类:

For test purposes, I knocked up the following view-model class:

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设置为上面显示的视图模型类的实例:

(The Resources collection Strings.resx contains a single key, with name ExampleFieldNameKey and value This value is in a Resources.resx. This collection also has its Access Modifier set to Public.) I tested out my modifications to your control using the following XAML, with the DataContext set to an instance of the view-model class presented above:

<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 gave me four RowItems, with the following labels:


This value is in a Display attribute
WithoutDisplay
This value is in a Resources.resx
This value is in a Display attribute

这篇关于如何绑定到自定义的Silverlight控件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆