本文 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 部分放置到此方法內。
本文完整代碼在此下載:http://files.cnblogs.com/Aimeast/SLTestValueBindingInItemTemplate.zip