如何使用 StaticResources、DynamicResources 相互绑定属性,尽管您可以找到有关 RelativeSource 及其用例的信息,但即使在 Microsoft 文档中也没有更多详细信息。在本文中,我将公开 WPF 中 RelativeSources 的用例。
当我们尝试将给定对象的属性绑定到对象本身的另一个属性时,当我们尝试将对象的属性绑定到其相对父对象的另一个属性时,RelativeSource 是一种标记扩展,用于特定的绑定情况,在自定义控件开发的情况下以及最后在使用一系列绑定数据的差异的情况下将依赖属性值绑定到一段 XAML 时。所有这些情况都表示为相对源模式。我将一一揭露所有这些情况。
1. Mode Self:
想象一下这种情况,我们希望它的高度总是等于它的宽度的矩形,比如说一个正方形。我们可以使用元素名称
<Rectangle Fill="Red" Name="rectangle"
Height="100" Stroke="Black"
Canvas.Top="100" Canvas.Left="100"
Width="{Binding ElementName=rectangle,
Path=Height}"/>
但在上面的例子中,我们必须指出绑定对象的名称,即矩形。我们可以使用RelativeSource以不同的方式达到相同的目的
<Rectangle Fill="Red" Height="100"
Stroke="Black"
Width="{Binding RelativeSource={RelativeSource Self},
Path=Height}"/>
在这种情况下,我们没有义务提及绑定对象的名称,并且当高度发生改变时,Width总是等于Height。如果希望将Width参数设置为高度的一半,则可以通过向Binding标记扩展添加转换器来实现。
现在让我们想象另一个例子:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self},
Path=Parent.ActualWidth}"/>
上面的例子用于将一个给定元素的一个给定属性绑定到它的一个直接父元素上,因为这个元素拥有一个名为parent的属性。这将我们引向另一个相对源模式,也就是find祖先模式。
2. Mode FindAncestor
在本例中,给定元素的一个属性将被绑定到它的父元素之一,当然。与上述情况的主要区别在于,由您决定祖先类型和祖先在层次结构中的等级来绑定属性。顺便说一下,试着玩一下这个XAML
<Canvas Name="Parent0">
<Border Name="Parent1"
Width="{Binding RelativeSource={RelativeSource Self},
Path=Parent.ActualWidth}"
Height="{Binding RelativeSource={RelativeSource Self},
Path=Parent.ActualHeight}">
<Canvas Name="Parent2">
<Border Name="Parent3"
Width="{Binding RelativeSource={RelativeSource Self},
Path=Parent.ActualWidth}"
Height="{Binding RelativeSource={RelativeSource Self},
Path=Parent.ActualHeight}">
<Canvas Name="Parent4">
<TextBlock FontSize="16"
Margin="5" Text="Display the name of the ancestor"/>
<TextBlock FontSize="16"
Margin="50"
Text="{Binding RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type Border},
AncestorLevel=2},Path=Name}"
Width="200"/>
</Canvas>
</Border>
</Canvas>
</Border>
</Canvas>
上面的情况是两个TextBlock元素,它们被嵌入在一系列的边框和画布元素中,这些元素代表它们的分层父元素。第二个TextBlock将在相对源级别显示给定父类的名称。
因此尝试将 AncestorLevel=2 更改为 AncestorLevel=1 看看会发生什么。然后尝试更改类型从 AncestorType=Border 到 AncestorType=Canvas 的祖先,看看会发生什么。
3. TemplatedParent
此模式允许将给定的 ControlTemplate 属性绑定到应用 ControlTemplate 的控件的属性。为了更好地理解这里的问题,下面是一个示例
<Window.Resources>
<ControlTemplate x:Key="template">
<Canvas>
<Canvas.RenderTransform>
<RotateTransform Angle="20"/>
</Canvas.RenderTransform>
<Ellipse Height="100" Width="150"
Fill="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=Background}">
</Ellipse>
<ContentPresenter Margin="35"
Content="{Binding RelativeSource={RelativeSource
TemplatedParent},Path=Content}"/>
</Canvas>
</ControlTemplate>
</Window.Resources>
<Canvas Name="Parent0">
<Button Margin="50"
Template="{StaticResource template}" Height="0"
Canvas.Left="0" Canvas.Top="0" Width="0">
<TextBlock FontSize="22">Click me</TextBlock>
</Button>
</Canvas>
如果我想应用给定控件的属性到它的控件模板,那么我可以使用TemplatedParent模式。也有类似的一个标记扩展的TemplateBinding是一种短手的第一个,但TemplateBinding是在编译时进行的对比TemplatedParent评估就是在第一次运行时。如下图所示,背景和内容从按钮内应用到控件模板。
4. PreviousData
这是RelativeSource中最模糊和使用较少的模式,我指的是PreviousData模式。PreviousData用于特定的情况。它的目的是通过一个特定的赋值将给定的属性绑定到另一个属性;我的意思是它把属性的前一个值赋给了有界值。换句话说,如果您有一个具有文本属性的TextBox和另一个具有保存数据的value属性的控件。假设这个值实际上是5,之前是3。3赋值给TextBox的text属性,而不是5。这导致了这样一种想法,即这种RelativeSource经常用于items控件。
为了理解RelativeSource现象,让我们公开这个示例。我将添加一个ItemsControl到场景中,我将从一个自定义集合中删除它
<Grid>
<ItemsControl></ItemsControl>
</Grid>
这个ItemsControl使用这个集合来填充:
public class Items : ObservableCollection<Item>
{
public Items()
{
Add(new Item { Value = 80.23 });
Add(new Item { Value = 126.17 });
Add(new Item { Value = 130.21 });
Add(new Item { Value = 115.28 });
Add(new Item { Value = 131.21 });
Add(new Item { Value = 135.22 });
Add(new Item { Value = 120.27 });
Add(new Item { Value = 110.25 });
Add(new Item { Value = 90.20 });
}
}
它是我开发的Item类型的ObservableCollection,它保存一个简单的属性Value,它的类型是double。
public class Item :INotifyPropertyChanged
{
private double _value;
public double Value
{
get { return _value; }
set { _value = value; OnPropertyChanged("Value"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string PropertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this,
new PropertyChangedEventArgs(PropertyName));
}
}
}
现在,为了将ItemsControl绑定到集合数据,我将在window构造函数级别将整个窗口的DataContext属性设置为集合。
public Window1()
{
InitializeComponent();
this.DataContext = new Items();
}
然后我将指定ItemsControl的绑定
ItemsControlItemsSource="{Binding}" Margin="10"
结果是这样的。
因此,我们必须应用一些特性来增强该表示的视觉效果。
<ItemsControl ItemsSource="{Binding}" Margin="10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border CornerRadius="3" BorderThickness="3"
Width="80" Height="{Binding Value}"
Margin="0,0,35,0"
BorderBrush="Violet"
Background="BlueViolet">
<TextBlock Text="{Binding Value}"
FontWeight="bold"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="Wheat">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
<TranslateTransform Y="250"/>
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
简单地说,我将描述上面的XAML。首先,ItemsPanel将在水平StackPanel中安排项目,其次,使用DataTemplate将数据表示为边界;边框高度被绑定到项目类的Value,以反映集合保存的Value。同样的边框包括一个TextBlock,它显示Item对象的Value。
演示结果如下
现在,主要目的这个demo就是为了展示RelativeSource.PreviousData模式的特点。
这个想法包括添加一个 TextBox 并将 Text 属性绑定到项目列表中前一个边框的值。看起来像下面这样的东西
你可以注意到,每个 TextBlock 代表前一个项目所持有的前一个值。这其实就是RelativeSource模式的PreviousData的神奇之处。
想法是将 TextBlock 添加到 DataTemplate 为 Follow
<TextBlock FontSize="14" FontWeight="bold" Margin="20"
Text="{Binding RelativeSource={RelativeSource PreviousData},
Path=Value}">
<TextBlock.RenderTransform>
<ScaleTransform ScaleY="-1"/>
</TextBlock.RenderTransform>
</TextBlock>
那么整个就会这样的
<Grid>
<ItemsControl ItemsSource="{Binding}" Margin="10">
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
<TranslateTransform Y="250"/>
</TransformGroup>
</ItemsControl.RenderTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock FontSize="14" FontWeight="bold"
Margin="20"
Text="{Binding
RelativeSource={RelativeSource PreviousData},
Path=Value}">
<TextBlock.RenderTransform>
<ScaleTransform ScaleY="-1"/>
</TextBlock.RenderTransform>
</TextBlock>
<Border CornerRadius="3" BorderThickness="3"
Width="80" Height="{Binding Value}"
Margin="0,0,35,0"
BorderBrush="Violet" Background="BlueViolet">
<TextBlock Text="{Binding Value}"
FontWeight="bold" VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="Wheat">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>