【Silverlight】解决DataTemplate绑定附加属性

    本文 Silverlight 版本:4.0。

    首先定义数据类型,此文始终使用此定义类型。

1
2
3
4
5
6
7
8
9
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"); } }
}

    前台代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<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>

    后台代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 搞定:

1
2
3
4
5
6
<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 达到控制行列的目的?

    于是我们得到下面的思路,使用附加属性把相应的绑定关系提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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"。得到完整的前台代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<UserControl x:Class="TestValueBindingInItemTemplate.MainPage"
    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

posted @   Aimeast  阅读(2467)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示