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" |
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 部分放置到此方法内。
浙公网安备 33010602011771号