【Silverlight】解决DataTemplate绑定附加属性

    本文 Silverlight 版本:4.0。

    首先定义数据类型,此文始终使用此定义类型。

    public class SimpleData : ViewModelBase
    {
        private string _text;
        private int _column, _row;

        public string Text { get { return _text; } set { _text = value; OnPropertyChanged("Text"); } }
        public int Column { get { return _column; } set { _column = value; OnPropertyChanged("Column"); } }
        public int Row { get { return _row; } set { _row = value; OnPropertyChanged("Row"); } }
    }

    前台代码:

    <Grid x:Name="LayoutRoot" Background="White">
        <ItemsControl ItemsSource="{Binding}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Text}"
                             Foreground="Green"
                             Grid.Row="{Binding Row}"
                             Grid.Column="{Binding Column}"
                             Height="30" Width="150"
                             />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid ShowGridLines="True">
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                    </Grid>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>

    后台代码:

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.DataContext = new SimpleData[]
            {
                new SimpleData{ Text = "111111", Column = 0, Row = 0 },
                new SimpleData{ Text = "222222", Column = 1, Row = 1 }, 
                new SimpleData{ Text = "333333", Column = 0, Row = 2 }, 
            };
        }
    }

    可以看出这段代码的本意是通过绑定的方式设置,在 ItemsControl 里面显示 3 个 TextBox,同时指定了相应在 Grid 的行和列。

    但是,你懂的!

    这样的代码肯定是不能正确运行。特别是在Silverlight。

    如果这是在 WPF 环境,很庆幸你还可以用 ItemContainerStyle 搞定:

            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Grid.Row" Value="{Binding Row, Mode=OneWay}"/>
                    <Setter Property="Grid.Column" Value="{Binding Column, Mode=OneWay}"/>
                </Style>
            </ItemsControl.ItemContainerStyle>

    只可惜这是在 Silverlight 环境。我们只能够想别的办法了。

 

    为什么不可以?拿出 Silverlight Spy 或者 Snoop 查看相应的 VisualTree。可以看到在 TextBox 外面还套了一个 ContextPresenter

    于是我们可以想到,能不能设置 ContextPresenter 的 Grid.Row 和 Grid.Colume 达到控制行列的目的?

    于是我们得到下面的思路,使用附加属性把相应的绑定关系提升。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace Delay
{
    public class UpUp : DependencyObject
    {
        // Using a DependencyProperty as the backing store for Up.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UpProperty =
            DependencyProperty.RegisterAttached("Up", typeof(string), typeof(UpUp), new PropertyMetadata(string.Empty));

        public static void SetUp(FrameworkElement element, string value)
        {
            HanderClosure hander = element.GetValue(UpProperty) as HanderClosure;
            if (hander == null)
            {
                hander = new HanderClosure(element, value);
                element.SetValue(UpProperty, value);
                element.LayoutUpdated += new EventHandler(hander.element_LayoutUpdated);
            }
        }
        public static string GetUp(FrameworkElement element)
        {
            HanderClosure hander = element.GetValue(UpProperty) as HanderClosure;
            if (hander == null)
                return null;
            else
                return hander.OrgParamenter;
        }

        private class HanderClosure
        {
            private FrameworkElement _elem = null;
            private string[] propertys = null;
            private int _level;
            private UpMode _mode;
            private string _orgParamenter;

            public string OrgParamenter { get { return _orgParamenter; } }

            public HanderClosure(FrameworkElement element, string parameter)
            {
                if (element == null)
                    throw new ArgumentNullException("element");
                if (parameter == null)
                    throw new ArgumentNullException("parameter");
                _elem = element;
                _level = 1;
                _mode = UpMode.Copy;
                _orgParamenter = parameter;

                string[] array = parameter.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                if (array.Length == 0)
                    throw new ArgumentException("parameter");
                propertys = array[0].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                if (array.Length > 1)
                {
                    int num;
                    if (int.TryParse(array[1].Trim(), out num))
                    {
                        _level = num;
                    }
                }
                if (array.Length > 2)
                {
                    UpMode mode;
                    if (Enum.TryParse<UpMode>(array[2].Trim(), true, out mode))
                    {
                        _mode = mode;
                    }
                }
            }

            public void element_LayoutUpdated(object sender, EventArgs e)
            {
                FrameworkElement parent = _elem;
                for (int i = 0; i < _level && parent != null; i++)
                {
                    parent = VisualTreeHelper.GetParent(parent) as FrameworkElement;
                }
                if (parent == null)
                    return;

                foreach (string property in propertys)
                {
                    Apply(_elem, parent, property.Trim());
                }
            }

            // Copyright (C) Microsoft Corporation. All Rights Reserved.
            // This code released under the terms of the Microsoft Public License
            // (Ms-PL, http://opensource.org/licenses/ms-pl.html).
            private void Apply(FrameworkElement element1, FrameworkElement element2, string property)
            {
                var array = property.Split('.');
                if (array.Length != 2)
                    throw new ArgumentException("property");
                string typeName = array[0].Trim();
                string propertyName = array[1].Trim();

                Type type = null;
                foreach (var assembly in AssembliesToSearch)
                {
                    // Match on short or full name
                    type = assembly.GetTypes()
                       .Where(t => (t.FullName == typeName) || (t.Name == typeName))
                       .FirstOrDefault();
                    if (type != null)
                        break;
                }
                if (null == type)
                {
                    // Unable to find the requested type anywhere
                    throw new ArgumentException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            "Unable to access type \"{0}\". Try using an assembly qualified type name.",
                            typeName));
                }

                // Get the DependencyProperty for which to set the Binding
                DependencyProperty dp = null;
                var field = type.GetField(
                    propertyName + "Property",
                    BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
                if (null != field)
                {
                    dp = field.GetValue(null) as DependencyProperty;
                }
                if (null == dp)
                {
                    // Unable to find the requsted property
                    throw new ArgumentException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            "Unable to access DependencyProperty \"{0}\" on type \"{1}\".",
                            propertyName,
                            type.Name));
                }

                BindingExpression binding = element1.GetBindingExpression(dp);
                object value = element1.GetValue(dp);
                if (binding != null)
                {
                    element2.SetBinding(dp, binding.ParentBinding);
                }
                else if (value != null)
                {
                    element2.SetValue(dp, value);
                }
                if (_mode == UpMode.Move)
                    element1.ClearValue(dp);
            }

            // Copyright (C) Microsoft Corporation. All Rights Reserved.
            // This code released under the terms of the Microsoft Public License
            // (Ms-PL, http://opensource.org/licenses/ms-pl.html).
            /// <summary>
            /// Gets a sequence of assemblies to search for the provided type name.
            /// </summary>
            private IEnumerable<Assembly> AssembliesToSearch
            {
                get
                {
                    // Start with the System.Windows assembly (home of all core controls)
                    yield return typeof(Control).Assembly;

#if SILVERLIGHT && !WINDOWS_PHONE
                    // Fall back by trying each of the assemblies in the Deployment's Parts list
                    foreach (var part in Deployment.Current.Parts)
                    {
                        var streamResourceInfo = Application.GetResourceStream(
                            new Uri(part.Source, UriKind.Relative));
                        using (var stream = streamResourceInfo.Stream)
                        {
                            yield return part.Load(stream);
                        }
                    }
#endif
                }
            }
        }

        private enum UpMode
        {
            Move,
            Copy,
        }
    }
}

    如何使用?使用非常简单!

    在你的项目中增加 UpUp 之后,在需要提升绑定级别的 Page 的 Xaml 中引入命名空间 xmlns:delay="clr-namespace:Delay"。然后在需要提升绑定级别的控件中加入属性 delay:UpUp.Up="Grid.Row,Grid.Column"。得到完整的前台代码如下:

<UserControl x:Class="TestValueBindingInItemTemplate.MainPage"
    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:delay="clr-namespace:Delay"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <ItemsControl ItemsSource="{Binding}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Text}"
                             Foreground="Green"
                             Grid.Row="{Binding Row}"
                             Grid.Column="{Binding Column}"
                             Height="30" Width="150"
                             delay:UpUp.Up="Grid.Row,Grid.Column"
                             />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid ShowGridLines="True">
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                    </Grid>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</UserControl>

    UpUp.Up 应该如何填写?实际上 UpUp.Up 属性有具体的语法格式:

 

UpUp.Up="Type.Property[,Type.Property ...][;Level[;Move|Copy]]"

 

    其中

    Type.Property 是需要提升绑定关系的属性名称,可以用逗号把多个属性名称隔开。

    Level 是整数,表示需要提升的层次。在 VisualTree 中向上一层为一个层次。

    Move|Copy 是枚举类型,表示提升之后保留原来的绑定关系。

    例如:delay:UpUp.Up="Grid.Row,Grid.Column;1;Copy"

 

    有了 UpUp 之后,对于类似的绑定问题可以轻而易举的完成了!

 

PS:WPF 也可以用此方法实现,但是有细节方面的差异。

1、不能够使用SetXXX GetXXX,要使用 XXX 属性。

2、需要注册 PropertyChangedCallback 事件,并将相关注册 Hander 部分放置到此方法内。

 

本文完整代码在此下载:https://files.cnblogs.com/Aimeast/SLTestValueBindingInItemTemplate.zip

posted @ 2011-09-11 21:45  Aimeast  阅读(2465)  评论(0编辑  收藏  举报