本文 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 部分放置到此方法内。