【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
浙公网安备 33010602011771号