RelativeSource属性绑定
- 介绍
- RelativeSource属性介绍
- RelativeSource的Mode属性有四种模式:
 
- 四种模式用法
- Self模式
- FindAncestor模式
- 使用规则:
 
- TemplatedParent模式
- PreviousData模式
 
- 特殊用法
- 绑定到其他ViewModel上的命令
 
 
介绍
RelativeSource 是一个标记扩展,扩展描述相对于绑定目标位置的绑定源位置。
RelativeSource 作用是改变绑定指向的源,默认使用绑定的源是对应的DataContext对象中的属性,如果想要绑定源为其他元素,比如自身、其他父级控件、其他ViewModel,就需要用RelativeSource 进行标记。它表示在绑定表达式中引用另一个元素的属性。它使开发人员可以根据元素的位置和类型来查找绑定源。
RelativeSource属性介绍
| 属性 | 解释 | 
|---|---|
| AncestorLevel | 以 FindAncestor 模式获取或设置要查找的上级级别。 使用 1 指示最靠近绑定目标元素的项。 | 
| AncestorType | 指定查找的上级节点的类型。 | 
| Mode | 获取或设置 RelativeSourceMode 值,该值描述相对于绑定目标的位置的绑定源的位置。Mode属性是一个RelativeSourceMode 枚举 | 
| PreviousData | 一个静态值,效果等同于 Mode=PreviousData。该值用于返回为 RelativeSource 模式构造的 PreviousData。 | 
| Self | 一个静态值,效果等同于 Mode=Self。该值用于返回为 RelativeSource 模式构造的 Self。 | 
| TemplatedParent | 一个静态值,效果等同于 Mode=TemplatedParent。该值用于返回为 RelativeSource 模式构造的 TemplatedParent。 | 
RelativeSource的Mode属性有四种模式:
RelativeSource属性有四种模式:
- FindAncestor:查找指定类型的父元素,和 AncestorType 或 AncestorLevel 一起使用。
- PreviousData:使用数据提供程序中的前一个数据项。
- Self:使用当前元素。
- TemplatedParent:查找元素的模板父级。
四种模式用法
Self模式
绑定控件本身的属性
示例:
实现正方形控件(宽和高相等),通过RelativeSource 实现,使用Self将Width绑定到Height上。
Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"
//或者
Width="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Height}"
控件的Xaml代码:
 <Button
            Width="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Height}"
            Height="100"
            Margin="40,67,0,0"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Content="Button"/>
效果:
只需要修改Height值,Width就会跟着变化

FindAncestor模式
绑定控件父元素及更上一级的元素,使用Mode=FindAncestor和AncestorType 、 AncestorLevel配合实现。
使用规则:
- 必须要设置AncestorType属性,只要设置了Mode=FindAncestor和AncestorLevel值,就必须要设置AncestorType属性,否则会报错。
- 如果未显式设置属性 Mode,则设置AncestorType或AncestorLevel属性会将属性值隐式锁定 Mode 为 FindAncestor。(Mode=FindAncestor可省略)
- AncestorLevel设置为1就是默认找到的第一个指定类型的父元素,2就是第二个
- 绑定成功的还有一点,对应父元素相关属性必须有值,如果对应父元素对应属性无值,则无效果。
如下几种定义效果是相同的
RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType=Grid}
RelativeSource={RelativeSource AncestorLevel=1, AncestorType=Grid}
RelativeSource={RelativeSource AncestorType=Grid}
示例:
通过设置一个Button的Width和不同父级布局控件的宽度绑定来演示FindAncestor模式
 
当前界面代码:
<Window
    x:Class="WpfRelativeSourceExample.MainWindow"
    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:local="clr-namespace:WpfRelativeSourceExample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <StackPanel Background="LightGray">
            <Grid
                Width="768"
                Height="380"
                Margin="0,27,0,0"
                HorizontalAlignment="Left"
                Background="Gray">
                <Grid
                    Width="396"
                    Height="344"
                    Margin="0,36,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Background="Linen">
                    <Canvas Margin="0,53,173,0" Background="DarkCyan" >
                        <Button
                            Canvas.Top="56"
                            Height="100"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Top"
                            Content="Button" />
                    </Canvas>
                </Grid>
            </Grid>
        </StackPanel>
    </Grid>
</Window>
将Button的Width设置和Canvas一样,增加下面的绑定
Width="{Binding Width, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Canvas}}"
此时并未生效

因为规则4中说明对应Canvas未定义,对应的Width属性,所有无法生效,只要对应Canvas设置了Width属性,RelativeSource就生效了。

然后依次跟上面两级的Grid的绑定
第一级:
  <Button
          Canvas.Top="56"
          Width="{Binding Width, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"
          Height="100"
          HorizontalAlignment="Center"
          VerticalAlignment="Top"
          Content="Button" />
                            
//等效                    
<Button
	       Canvas.Top="56"
	       Width="{Binding Width, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType=Grid}}"
	       Height="100"
	       HorizontalAlignment="Center"
	       VerticalAlignment="Top"
	       Content="Button" />

第二级:
<Button
        Canvas.Top="56"
          Width="{Binding Width, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=2, AncestorType=Grid}}"
          Height="100"
          HorizontalAlignment="Center"
          VerticalAlignment="Top"
          Content="Button" />

TemplatedParent模式
TemplatedParent模式主要用在重写或者定义控件模板Template中,用于将模板中的属性和控件本身的属性绑定,类似TemplateBinding,可以对TemplateBinding进行扩展,利用Converter实现一些TemplateBinding不支持的属性绑定
示例:
 <Button
            Margin="49,60,0,0"
            Padding="10"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Content="Button">
            <Button.Style>
                <Style TargetType="Button">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="Button">
                                <Border Background="{TemplateBinding Background}">
                                    <ContentPresenter Margin="{Binding Path=Padding, RelativeSource={RelativeSource TemplatedParent}}" Content="{TemplateBinding Content}" />
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Button.Style>
        </Button>
PreviousData模式
PreviousData可以在数据绑定表达式中使用前一个数据项。通常在ItemsControl中使用,用于根据前一个项的属性计算当前项的属性。PreviousData只能在ItemsControl或其派生类中使用。
使用场景,暂无示例
特殊用法
使用RelativeSource改变绑定的ViewModel对象。在嵌套的ViewModel中,命令一定要写在对应的VIewModel(或者Model)中才能实现绑定,这时可以通过RelativeSource绑定到其父级的ViewModel中的命令上。
比如在DataGrid列中有按钮事件,就必须对应的命令需要写在对应的Model中增加命令
正常下一个DataGird(在用户控件UserManageUC.xaml中)
<DataGrid
            Width="605"
            Height="341"
            Margin="59,74,0,0"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            AutoGenerateColumns="False"
            ItemsSource="{Binding Users}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Id}" Header="序号" />
                <DataGridTextColumn Binding="{Binding Name}" Header="姓名" />
                <DataGridTextColumn Binding="{Binding Age}" Header="年龄" />
                <DataGridTemplateColumn Header="操作">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Button Command="{Binding ModifyCmd}" Content="修改" />
                                <Button Content="删除" />
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
对应的数据模型UserModel
 public class UserModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public CommandBase ModifyCmd
        {
            get
            {
                return new CommandBase(o =>
                {
                    MessageBox.Show($"UserModel Modify:{Name}");
                });
            }
        }
    }
}
在对应的ViewModel创建集合并初始化
public class UserManageUCViewModel : ViewModelBase
    {
        public ObservableCollection<UserModel> Users{ get; set; }
        public UserManageUCViewModel()
        {
            Users= new ObservableCollection<UserModel>();
            Users.Add(new UserModel() { Id=1,Name="张三",Age=11});
            Users.Add(new UserModel() { Id=2,Name="李四",Age=22});
        }
    }
此时效果,正常使用命令

绑定到其他ViewModel上的命令
在UserManageUCViewModel 上添加命令
public CommandBase ModifyCmd
{
    get
    {
        return new CommandBase(o =>
        {
            var user=o as UserModel;
            MessageBox.Show($"UserManageUCViewModel Modify:{user.Name}");
        });
    }
}
改变按钮Command的绑定,使用RelativeSource的FindAncestor模式RelativeSource={RelativeSource AncestorType=local:UserManageUC}帮绑定对象改为UserManageUC用户控件,然后目标改为DataContext.ModifyCmd,然后增加CommandParameter,将该行绑定的数据作为命令参数传入
  <Button
        Command="{Binding DataContext.ModifyCmd, RelativeSource={RelativeSource AncestorType=local:UserManageUC}}"
        CommandParameter="{Binding}"
        Content="修改" />
效果:
 


















