在 Silverlight 中实现 RelativeSource 绑定






4.87/5 (6投票s)
这篇技术博客文章展示了如何使用 Silverlight 2.0 创建 FindAncestor 和 RelativeSource.Self 绑定
在我的上一篇文章中,我演示了如何通过附加行为在 Silverlight 中模拟 WPF 的 ElementName
样式绑定。 简单回顾一下,该技术涉及创建一个附加属性,该属性在绑定时,会为元素的 Loaded
事件添加一个处理程序。 加载元素后,事件处理程序会定位命名元素,并通过中继对象构造绑定。 这是正在使用的附加属性
<Rectangle Height="20" Fill="Green">
<local:BindingHelper.Binding>
<local:BindingProperties ElementName="Slider"
SourceProperty="Value" TargetProperty="Width"/>
</local:BindingHelper.Binding>
</Rectangle>
<Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/>
其中 Rectangle
的 Width
绑定到 Slider
的 Value
。 有关详细信息和源代码,请访问 此博客文章。 在这里,我将扩展此附加行为,以便模拟 WPF 的 RelativeSource
绑定。
大多数情况下,您希望通过元素的 DataContext
属性将您的视觉元素绑定到您的数据对象。 但是,通常情况下,您想要执行纯粹出于演示目的的绑定,例如,将两个元素的宽度绑定在一起。 在这种情况下,WPF 的 RelativeSource
和 ElementName
绑定被证明是绑定框架强大而有用的功能。
在这里,我将扩展我在上一篇文章中描述的附加行为,以添加 RelativeSource
绑定功能。 附加属性类型的属性已扩展
public class BindingProperties
{
public string SourceProperty { get; set; }
public string ElementName { get; set; }
public string TargetProperty { get; set; }
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public bool RelativeSourceSelf { get; set; }
public string RelativeSourceAncestorType { get; set; }
public int RelativeSourceAncestorLevel { get; set; }
public BindingProperties()
{
RelativeSourceAncestorLevel = 1;
}
}
其中 RelativeSourceSelf
、RelativeSourceAncestorType
和 RelativeSourceAncestorLevel
属性用于相对源绑定。 以 RelativeSourceSelf
作为我们的第一个例子,在 WPF 中,RelativeSource.Self 属性指示绑定的源应该是与绑定关联的元素。 (我知道 - 这听起来有点疯狂,但在 Google 上搜索,它非常有用!)。
private static void OnBinding(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement targetElement = depObj as FrameworkElement;
targetElement.Loaded += new RoutedEventHandler(TargetElement_Loaded);
}
private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement targetElement = sender as FrameworkElement;
// get the value of our attached property
BindingProperties bindingProperties = GetBinding(targetElement);
if (bindingProperties.ElementName != null)
{
// perform our 'ElementName' lookup
...
}
else if (bindingProperties.RelativeSourceSelf)
{
// bind an element to itself.
CreateRelayBinding(targetElement, targetElement, bindingProperties);
}
}
当附加属性附加到我们的目标元素时,它会为元素的 Loaded
事件添加一个处理程序(这就是 *附加行为*)。 在事件处理程序中,我们确定这是否是相对源绑定。 如果是这种情况,则调用 CreateRelayBinding
方法,其中源元素和目标元素参数是同一个元素。 有关 CreateRelayBinding
方法如何工作的详细信息,请参阅上一篇博客文章。 下面显示了相对源自身绑定的示例,其中 TextBox
的 Width
属性绑定到其 Text
,如果您输入新的文本值,则 TextBox Width
会相应地调整。
<TextBox Text="200" Margin="0,5,0,0">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Width" SourceProperty="Text"
RelativeSourceSelf="True"/>
</local:BindingHelper.Binding>
</TextBox>
我将要处理的下一种 RelativeSource
绑定是 FindAncestor
模式。 当您想要绑定到视觉树中比目标元素更靠上的特定类型的元素时,可以使用这种类型的绑定。 以下代码片段显示了附加行为如何实现这种类型的绑定
private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement targetElement = sender as FrameworkElement;
// get the value of our attached property
BindingProperties bindingProperties = GetBinding(targetElement);
if (bindingProperties.ElementName != null)
{
// perform our 'ElementName' lookup
...
}
else if (bindingProperties.RelativeSourceSelf)
{
// bind an element to itself.
...
}
else if (!string.IsNullOrEmpty(bindingProperties.RelativeSourceAncestorType))
{
// navigate up the tree to find the type
DependencyObject currentObject = targetElement;
int currentLevel = 0;
while (currentLevel < bindingProperties.RelativeSourceAncestorLevel)
{
do
{
currentObject = VisualTreeHelper.GetParent(currentObject);
}
while (currentObject.GetType().Name !=
bindingProperties.RelativeSourceAncestorType);
currentLevel++;
}
FrameworkElement sourceElement = currentObject as FrameworkElement;
// bind them
CreateRelayBinding(targetElement, sourceElement, bindingProperties);
}
}
上面的代码只是在视觉树中向上导航,以查找给定类型的第 n 个元素。 找到给定类型后,将构造两者之间的中继绑定。
这种类型的绑定非常灵活,以下显示了许多示例
<UserControl x:Class="SilverlightBinding.PageTwo">
<UserControl.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter"/>
</UserControl.Resources>
<StackPanel x:Name="TheOuterStackPanel" Background="White">
<StackPanel Name="theInnerStackPanel" Width="400">
<!-- bind the textbox width to its text property -->
<TextBox Text="200" Margin="0,5,0,0">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Width" SourceProperty="Text"
RelativeSourceSelf="True"/>
</local:BindingHelper.Binding>
</TextBox>
<!-- bind the textbox text to this UserControls width property -->
<TextBox Margin="0,5,0,0">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Text" SourceProperty="Width"
RelativeSourceAncestorType="PageTwo"/>
</local:BindingHelper.Binding>
</TextBox>
<!-- bind the textbox text to the inner stack panels name property -->
<TextBox Margin="0,5,0,0">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Text" SourceProperty="Name"
RelativeSourceAncestorType="StackPanel"/>
</local:BindingHelper.Binding>
</TextBox>
<!-- bind the textbox text to the outer stack panels name property -->
<TextBox Margin="0,5,0,0">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Text" SourceProperty="Name"
RelativeSourceAncestorType="StackPanel"
RelativeSourceAncestorLevel="2"/>
</local:BindingHelper.Binding>
</TextBox>
<!-- bind a slider's value to its parent grid's height property -->
<Grid Height="30" Margin="0,5,0,0" Background="CadetBlue"
HorizontalAlignment="Left" >
<Slider Width="400" HorizontalAlignment="Left"
Minimum="10" Maximum="40">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Value"
SourceProperty="Height"
RelativeSourceAncestorType="Grid"/>
</local:BindingHelper.Binding>
</Slider>
</Grid>
</StackPanel>
</StackPanel>
</UserControl>
您可以在上面的 XAML 中看到具有各种类型和祖先级别的相对源自身和查找祖先绑定的示例。 这是它的实际效果
[CodeProject 不支持 Silverlight 小程序,请在我的博客上观看实际效果。]
如果您在顶部文本框中输入一个新值,它的宽度会改变,或者移动滑块以查看其父网格的高度变化。 当然,这一切都没有任何代码隐藏!
上面显示的许多效果可以通过元素名称绑定来执行。 但是,您并不总是为屏幕上呈现的所有视觉元素提供创建者或模板提供者。 一个常见的例子是 ItemsControl
,而 ListBox
就是这种控件的一个例子。 在这里,您提供一个 DataTemplate
,您的视觉元素呈现在 ListBoxItem
容器内。 因此,无法通过元素名称或其他方式绑定到 ListBoxItem
。 但是,您可以通过使用相对源绑定在树中向上导航来访问 ListBoxItem
,如下所示
<ListBox Name="grid" Width="200" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="180">
<TextBlock Text="{Binding Forename}"/>
<Ellipse Width="10" Height="10"
Fill="Red" HorizontalAlignment="Right">
<local:BindingHelper.Binding>
<local:BindingProperties TargetProperty="Visibility"
SourceProperty="IsSelected"
Converter="{StaticResource VisibilityConverter}"
RelativeSourceAncestorType="ListBoxItem"/>
</local:BindingHelper.Binding>
</Ellipse>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
在上面的例子中,我们有一个包含椭圆的 DataTemplate
。 当 ListBox
生成每个“行”时,它会创建一个 ListBoxItem
并使用来自我们的数据模板的视觉元素填充它。 当我们的椭圆被创建并加载时,附加行为触发,在视觉树中向上导航以找到它遇到的第一个 ListBoxItem
。 当它找到它时,它会创建我们的中继对象的单个实例,通过中继对象将 ListBoxItem.IsSelected
和 Ellipse.Visibilty
(通过合适的 ValueConverter)绑定在一起。
这是它的实际效果(单击一个项目以查看椭圆)…
[CodeProject 不支持 Silverlight 小程序,请在我的博客上观看实际效果。]
您可以下载包含所有源代码的 Visual Studio 项目 这里。
祝您愉快,
Colin E.