Silverlight】解决DataTemplate绑定附加属性

本文 Silverlight 版本:4.0。

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

1 public class SimpleData : ViewModelBase
2 {
3     private string _text;
4     private int _column, _row;
5  
6     public string Text { get { return _text; } set { _text = value; OnPropertyChanged("Text"); } }
7     public int Column { get { return _column; } set { _column = value; OnPropertyChanged("Column"); } }
8     public int Row { get { return _row; } set { _row = value; OnPropertyChanged("Row"); } }
9 }

    前台代码:

01 <Grid x:Name="LayoutRoot" Background="White">
02     <ItemsControl ItemsSource="{Binding}">
03         <ItemsControl.ItemTemplate>
04             <DataTemplate>
05                 <TextBox Text="{Binding Text}"
06                          Foreground="Green"
07                          Grid.Row="{Binding Row}"
08                          Grid.Column="{Binding Column}"
09                          Height="30" Width="150"
10                          />
11             </DataTemplate>
12         </ItemsControl.ItemTemplate>
13         <ItemsControl.ItemsPanel>
14             <ItemsPanelTemplate>
15                 <Grid ShowGridLines="True">
16                     <Grid.RowDefinitions>
17                         <RowDefinition/>
18                         <RowDefinition/>
19                         <RowDefinition/>
20                     </Grid.RowDefinitions>
21                     <Grid.ColumnDefinitions>
22                         <ColumnDefinition/>
23                         <ColumnDefinition/>
24                     </Grid.ColumnDefinitions>
25                 </Grid>
26             </ItemsPanelTemplate>
27         </ItemsControl.ItemsPanel>
28     </ItemsControl>
29 </Grid>

    后台代码:

01 public partial class MainPage : UserControl
02 {
03     public MainPage()
04     {
05         InitializeComponent();
06         this.DataContext = new SimpleData[]
07         {
08             new SimpleData{ Text = "111111", Column = 0, Row = 0 },
09             new SimpleData{ Text = "222222", Column = 1, Row = 1 }, 
10             new SimpleData{ Text = "333333", Column = 0, Row = 2 }, 
11         };
12     }
13 }

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

    但是,你懂的!

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

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

1 <ItemsControl.ItemContainerStyle>
2     <Style>
3         <Setter Property="Grid.Row" Value="{Binding Row, Mode=OneWay}"/>
4         <Setter Property="Grid.Column" Value="{Binding Column, Mode=OneWay}"/>
5     </Style>
6 </ItemsControl.ItemContainerStyle>

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

 

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

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

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

001 using System;
002 using System.Collections.Generic;
003 using System.Globalization;
004 using System.Linq;
005 using System.Reflection;
006 using System.Windows;
007 using System.Windows.Controls;
008 using System.Windows.Data;
009 using System.Windows.Media;
010   
011 namespace Delay
012 {
013     public class UpUp : DependencyObject
014     {
015         // Using a DependencyProperty as the backing store for Up.  This enables animation, styling, binding, etc...
016         public static readonly DependencyProperty UpProperty =
017             DependencyProperty.RegisterAttached("Up", typeof(string), typeof(UpUp), new PropertyMetadata(string.Empty));
018   
019         public static void SetUp(FrameworkElement element, string value)
020         {
021             HanderClosure hander = element.GetValue(UpProperty) as HanderClosure;
022             if (hander == null)
023             {
024                 hander = new HanderClosure(element, value);
025                 element.SetValue(UpProperty, value);
026                 element.LayoutUpdated += new EventHandler(hander.element_LayoutUpdated);
027             }
028         }
029         public static string GetUp(FrameworkElement element)
030         {
031             HanderClosure hander = element.GetValue(UpProperty) as HanderClosure;
032             if (hander == null)
033                 return null;
034             else
035                 return hander.OrgParamenter;
036         }
037   
038         private class HanderClosure
039         {
040             private FrameworkElement _elem = null;
041             private string[] propertys = null;
042             private int _level;
043             private UpMode _mode;
044             private string _orgParamenter;
045   
046             public string OrgParamenter { get { return _orgParamenter; } }
047   
048             public HanderClosure(FrameworkElement element, string parameter)
049             {
050                 if (element == null)
051                     throw new ArgumentNullException("element");
052                 if (parameter == null)
053                     throw new ArgumentNullException("parameter");
054                 _elem = element;
055                 _level = 1;
056                 _mode = UpMode.Copy;
057                 _orgParamenter = parameter;
058   
059                 string[] array = parameter.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
060                 if (array.Length == 0)
061                     throw new ArgumentException("parameter");
062                 propertys = array[0].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
063                 if (array.Length > 1)
064                 {
065                     int num;
066                     if (int.TryParse(array[1].Trim(), out num))
067                     {
068                         _level = num;
069                     }
070                 }
071                 if (array.Length > 2)
072                 {
073                     UpMode mode;
074                     if (Enum.TryParse<UpMode>(array[2].Trim(), true, out mode))
075                     {
076                         _mode = mode;
077                     }
078                 }
079             }
080   
081             public void element_LayoutUpdated(object sender, EventArgs e)
082             {
083                 FrameworkElement parent = _elem;
084                 for (int i = 0; i < _level && parent != null; i++)
085                 {
086                     parent = VisualTreeHelper.GetParent(parent) as FrameworkElement;
087                 }
088                 if (parent == null)
089                     return;
090   
091                 foreach (string property in propertys)
092                 {
093                     Apply(_elem, parent, property.Trim());
094                 }
095             }
096   
097             // Copyright (C) Microsoft Corporation. All Rights Reserved.
098             // This code released under the terms of the Microsoft Public License
099             // (Ms-PL, http://opensource.org/licenses/ms-pl.html).
100             private void Apply(FrameworkElement element1, FrameworkElement element2, string property)
101             {
102                 var array = property.Split('.');
103                 if (array.Length != 2)
104                     throw new ArgumentException("property");
105                 string typeName = array[0].Trim();
106                 string propertyName = array[1].Trim();
107   
108                 Type type = null;
109                 foreach (var assembly in AssembliesToSearch)
110                 {
111                     // Match on short or full name
112                     type = assembly.GetTypes()
113                        .Where(t => (t.FullName == typeName) || (t.Name == typeName))
114                        .FirstOrDefault();
115                     if (type != null)
116                         break;
117                 }
118                 if (null == type)
119                 {
120                     // Unable to find the requested type anywhere
121                     throw new ArgumentException(
122                         string.Format(
123                             CultureInfo.CurrentCulture,
124                             "Unable to access type \"{0}\". Try using an assembly qualified type name.",
125                             typeName));
126                 }
127   
128                 // Get the DependencyProperty for which to set the Binding
129                 DependencyProperty dp = null;
130                 var field = type.GetField(
131                     propertyName + "Property",
132                     BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
133                 if (null != field)
134                 {
135                     dp = field.GetValue(null) as DependencyProperty;
136                 }
137                 if (null == dp)
138                 {
139                     // Unable to find the requsted property
140                     throw new ArgumentException(
141                         string.Format(
142                             CultureInfo.CurrentCulture,
143                             "Unable to access DependencyProperty \"{0}\" on type \"{1}\".",
144                             propertyName,
145                             type.Name));
146                 }
147   
148                 BindingExpression binding = element1.GetBindingExpression(dp);
149                 object value = element1.GetValue(dp);
150                 if (binding != null)
151                 {
152                     element2.SetBinding(dp, binding.ParentBinding);
153                 }
154                 else if (value != null)
155                 {
156                     element2.SetValue(dp, value);
157                 }
158                 if (_mode == UpMode.Move)
159                     element1.ClearValue(dp);
160             }
161   
162             // Copyright (C) Microsoft Corporation. All Rights Reserved.
163             // This code released under the terms of the Microsoft Public License
164             // (Ms-PL, http://opensource.org/licenses/ms-pl.html).
165             /// <summary>
166             /// Gets a sequence of assemblies to search for the provided type name.
167             /// </summary>
168             private IEnumerable<Assembly> AssembliesToSearch
169             {
170                 get
171                 {
172                     // Start with the System.Windows assembly (home of all core controls)
173                     yield return typeof(Control).Assembly;
174   
175 #if SILVERLIGHT && !WINDOWS_PHONE
176                     // Fall back by trying each of the assemblies in the Deployment's Parts list
177                     foreach (var part in Deployment.Current.Parts)
178                     {
179                         var streamResourceInfo = Application.GetResourceStream(
180                             new Uri(part.Source, UriKind.Relative));
181                         using (var stream = streamResourceInfo.Stream)
182                         {
183                             yield return part.Load(stream);
184                         }
185                     }
186 #endif
187                 }
188             }
189         }
190   
191         private enum UpMode
192         {
193             Move,
194             Copy,
195         }
196     }
197 }

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

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

01 <UserControl x:Class="TestValueBindingInItemTemplate.MainPage"
06     xmlns:delay="clr-namespace:Delay"
07     mc:Ignorable="d"
08     d:DesignHeight="300" d:DesignWidth="400">
09   
10     <Grid x:Name="LayoutRoot" Background="White">
11         <ItemsControl ItemsSource="{Binding}">
12             <ItemsControl.ItemTemplate>
13                 <DataTemplate>
14                     <TextBox Text="{Binding Text}"
15                              Foreground="Green"
16                              Grid.Row="{Binding Row}"
17                              Grid.Column="{Binding Column}"
18                              Height="30" Width="150"
19                              delay:UpUp.Up="Grid.Row,Grid.Column"
20                              />
21                 </DataTemplate>
22             </ItemsControl.ItemTemplate>
23             <ItemsControl.ItemsPanel>
24                 <ItemsPanelTemplate>
25                     <Grid ShowGridLines="True">
26                         <Grid.RowDefinitions>
27                             <RowDefinition/>
28                             <RowDefinition/>
29                             <RowDefinition/>
30                         </Grid.RowDefinitions>
31                         <Grid.ColumnDefinitions>
32                             <ColumnDefinition/>
33                             <ColumnDefinition/>
34                         </Grid.ColumnDefinitions>
35                     </Grid>
36                 </ItemsPanelTemplate>
37             </ItemsControl.ItemsPanel>
38         </ItemsControl>
39     </Grid>
40 </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 部分放置到此方法内。

posted on 2011-09-14 08:49  SplendidMe  阅读(1067)  评论(0编辑  收藏  举报