本文 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" |
03 | xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" |
04 | xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" |
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 部分放置到此方法内。