Skip to content
分类目录:

DataBinding & Converter

Post date:
Author:

给WPF中的依赖项属性赋值,可以有多种方式,除了像普通属性赋值外,也可以使用绑定的方式,动态赋值,即构建一个Binding的对象。

Binding :

绑定的数据源Source可以是ViewModel中的单个属性或者多个属性。

BindingBase 有两个子类: Binding and MultiBinding,分别对应单个属性绑定和多个属性绑定。

当Binding中只指定了属性名称,则会从DataContext中查找,否则会从Source指定的地方查找

常见的Binding的构造函数:

1. ElementName绑定到UI的另外一个元素上,并指定Path为元素的某个属性

<Window>
  <StackPanel>
	<Slider Name="sliderFontSize" 
    Minimum="1" Maximum="40" Value="10" 
    TickFrequency="1" TickPlacement="TopLeft"/>

    <TextBlock Text="SimpleText" 
      FontSize="{Binding ElementName=sliderFontSize, Path=Value}"/>
  </StackPanel>  
</Window>
//binding sample with code
Binding binding = new Binding();
binding.Source=sliderObj;
binding.Path= new PropertyPath("Value");
binding.Mode=BindingMode.TwoWay;
tbObj.SetBinding(TextBlock.FontSize,binding);

//查询绑定
Binding binding = BindingOperations.GetBinding(tbObj,TextBlock.FontSize);

2. 通过Source或者StaticResource指定绑定源

使用Source可以绑定到静态类的属性上,或者Resource中定义的资源

x:Static:指向代码中的静态属性。

StaticResource: 一般指向XAML中定义的Resources中的资源,如Style,FontFamily,DataTemplate等

<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFammily},Path=.}"/>

<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFammily}}"/>

<TextBlock Text="{x:Static SystemFonts.IconFontFammily}"/>

<!--Or -->
<Window.Resources>
	<FontFamily x:Key="CustomFont">Calibri</FontFamily>
</Window.Resources>

<TextBlock Text="{Binding Source={StaticResource CustomFont},Path=.}" />
绑定到Enum

将通过x:Static将Enum的某个值赋给属性:

enum TubeStatus{  Removed,Inserted }
<Condition Binding="{Binding TubeStatus}"  
           Value="{x:Static CommonData:TubeStatus.Removed}" />
<Window x:Class="ArticleExample.BindEnumFull"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib" 
        SizeToContent="WidthAndHeight"
        Title="Enum binding">
    <Window.Resources>
        <ObjectDataProvider x:Key="EnumDataSource"
                            ObjectType="{x:Type sys:Enum}"
                            MethodName="GetValues">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="HorizontalAlignment" /> 
              // or <x:Type TypeName="octokit:ItemStateFilter" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    
    <StackPanel Width="300" Margin="10">
        <TextBlock>Choose the HorizontalAlignment value of the Button:</TextBlock>

        <ListBox Name="myComboBox" SelectedIndex="0"
                 ItemsSource="{Binding Source={StaticResource EnumDataSource}}"/>

        <Button Content="I'm a button"
                HorizontalAlignment="{Binding ElementName=myComboBox, Path=SelectedItem}" />
    </StackPanel>
</Window>

3.[常用]通过RelativeSource指定绑定源

RelativeSource主要包括:

Self: 表达式绑定到同一元素的另一个属性上

FindAncestor: 表达式绑定到父元素

PreviousData: 表达式绑定到数据绑定列表中的前一个数据项

TemplateParent: 表达式绑定到应用模板的元素,只有当绑定位于控件控件模板或数据模板内部时,这种模式才能工作。

通过RelatvieSource指定父节点后,如果需要绑定到父节点的ViewModel的属性,则需要显式的添加 DataContext.<属性名>

<TextBlock Text="{Binding Path=DataContext.Title,
  RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}} }"/>

<TextBlock Text="{Binding Path=Title,
  RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}} }"/>

<!-- FindAncestor模式-->
Self,FindAncestor,PreviousData,TemplatedParent

<ControlTemplate TargetType="ListBoxItem">
  <RadioButton 
    IsChecked=”{Binding 
                  Path=IsSelected,
                  RelativeSource={RelativeSource TemplatedParent},
                  Mode=TwoWay}”>
    <ContentPresenter></ContentPresenter>
</ControlTemplate>

<!--还可以绑定到附加属性 -->
<Condition 
  Binding="{Binding Path=(local:TextBoxHelper.FocusedBorderBrush)
              ,RelativeSource={RelativeSource Self}
              , Mode=OneWay
              ,Converter={StaticResource IsNotNullConverter}}"
  Value="True" />

4.[常用]通过DataContext指定绑定源

此种方式无需指定Source,通过DataContext绑定,控件会沿着控件树向上依次查找本身或者父元素的不为空的DataContext。

<Window.Resources>
  <local:MainWindowViewModel x:Key="viewModel"/>
</Window.Resources>
<Window.DataContext>
  <Binding Source="{StaticResource viewModel}"/>
</Window.DataContext>

使用Prism自动应用绑定

xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DataContext

在Xaml文件中配置d:DataContext,可以在VS的设计模式,输入绑定属性时,提示ViewModel的信息,并且也可以F12导航到对应的代码。

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance local:MainWindowViewModel}"

<TextBox Text="{Binding Name}"/>

当Binding中写了. 符号后,绑定的对象试剂上是DataContext指向的对象,即ViewModel

如果是在ItemsControl中使用{Binding},则指向的是该行对应的集合里的单个Model

如:

<UserControl DataContext="{Binding VM}"> 
  <Button Command="{Binding TestCommand}" CommandParameter="{Binding .}">Test</Button>
</UserControl>

public class UserControlViewMiodel :BindableBase{
	public DelegateCommand<object> TestCommand{get;private set;} 
                 = new DelegateCommand<object>(TestCmdHandler);
    private void TestCmdHandler(object obj)
    {
      	//obj为Button对应的DataContext,如没设置,则找Button的父节点的DataContext
    }
}

5.TemlateBinding:

在ControlTemplate内部,如果内部元素要绑定到该控件的属性,可以直接使用TemplateBinding,注意,TemplateBinding只能是单向绑定,不能是双向的,所以如果要使用双向绑定,则应该使用普通的Binding,RelativeSource为TemplatedParent

以下面中的Button为例,

在ControlTemplate中绑定时,内部可视化树中的节点子控件,可以使用TemplateBinding将子控件的属性直接绑定到Button控件的属性上,而无需指定Source,RelativeSource或者DataContext,

如果要绑定到Button的附加属性上,

可以使用TemplateBinding直接绑定附加属性(无需添加括号),

也可以使用Binding+RelativeSource指向TemplatedParent。此情况下,在ControlTemplate中,使用附加属性和TemplateParent建立Binding时,不能省略Path和括号,需要按照这种格式写:Path=(hc:InfoElement.Title)

在Style中设置Setter或者使用控件时,给附加属性赋值无需加括号。

<ControlTemplate TargetType="Button">
    <DockPanel 
        HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
        Visibility="{Binding Path=(hc:InfoElement.Title)
                        ,RelativeSource={RelativeSource TemplatedParent},
                        Converter={StaticResource String2VisibilityConverter}}" />
     <Path Stroke="{TemplateBinding c:Helper.IconStroke}"
</ControlTemplate>
<!--设置附加属性 -->
<Style>
  <Setter Property="attach:Attached.CornerRadius" Value="2"/>
  <Setter Property="Tag" Value="{Binding Path=(hc:InfoElement.Title)}"/>
</Style>

<Button attach:Attached.CornerRadius="3" />

6 在Style中设置Binding:

 <Style x:Key="MultiColumnItem" TargetType="ComboBoxItem" >
     <Setter Property="Width">
         <Setter.Value>
             <Binding Path="(attach:ComboBoxHelper.ComboBoxItemWidth)"
               RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=ComboBox}"/>
         </Setter.Value>
     </Setter>
     <Setter Property="Template">
         <Setter.Value>
             <ControlTemplate TargetType="ComboBoxItem">
                 <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                     <Border.Effect>
                         <DropShadowEffect  Color="#80668db3" Opacity="0.5" BlurRadius="8" ShadowDepth="2"/>
                     </Border.Effect>
                     <ContentPresenter TextBlock.Foreground="Orange" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="Center"/>
                 </Border>
             </ControlTemplate>
         </Setter.Value>
     </Setter>
 </Style>

绑定模式(Mode):

BindingMode主要包括以下几种:

OneWay:单向绑定,当数据源变化(ViewModel)时,更新目标属性(View)

TwoWay:双向绑定,主要是一些表单控件,当源属性变化时,更新目标属性,并且当目标属性变化时更新数据源

OneTime:只有第一次的绑定会生效

OneWayToSource:单向绑定,与OneWay相反

Default:大多数的行为是OneWay,但TextBox.Text是双向的。

绑定更新:

PropertyChanged:当控件属性变化时,更新数据源

LostFocus:当控件属性发生变化且丢失焦点时,更新数据源

Explicit:除非调用BindingExpression.UpdateSource(),否则无法更新数据源

Default: 大多数的默认行为时PropertyChanged,但TextBox.Text的默认行为是LostFocus

注: 对于一些输入控件,如TextBox,CheckBox,ComboBox,建立绑定时,最好明确设置TwoWay和PropertyChanged等属性

IsChecked="{Binding IsSelectedForBatchOp,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"  

指定显示格式

在Binding中可以设置StringFormat指定数据在控件中的显示格式,如

绑定Model

如果是集合类型,则使用ObservableCollection<T>类型来实现集合绑定,增加或者删除元素时才具有通知的功能。

如果是使用普通的LIST,则只有View初始化的时候才会应用一次绑定,再次增删元素则View的内容不会接收到通知。

如果要绑定到集合的某个项,则可以通过索引的方式:

<Button DataContext="{Binding CellList[0]}"/>

MultiBinding:

绑定的数据源为多个属性时,需要配合IvalueConverter使用。

<Button.CommandParameter>
  <MultiBinding Converter="{StaticResource MultiCombinerConverter}">
    <Binding Path="."
      RelativeSource="{RelativeSource AncestorType=TabItem}" />
    <Binding Path="."
      RelativeSource="{RelativeSource AncestorType=TabControl}" />
  </MultiBinding>
</Button.CommandParameter>

<TextBlock Text="{Binding KeyValue}">
  <TextBlock.Foreground>
    <MultiBinding Converter="{StaticResource boolBrushValueConverter}">
      <Binding Path="KeyName"/>
      <Binding Path="KeyValue"/>
      <Binding Path="KeyReferValue"/>
    </MultiBinding>
  </TextBlock.Foreground>
</TextBlock>

<TextBlock.Text>
  <MultiBinding StringFormat="{}{0} / {1}{5} / {2}({3}{4})">
    <Binding Path="Sex" Converter="{StaticResource SexConverter}"/>
    <Binding Path="PatientWeight"/>
    <Binding Path="Birthdate" Converter="{StaticResource BirthDateConverter}"/>
    <Binding Path="Age" />
    <Binding Path="AgeType" Converter="{StaticResource AgeTypeConverter}" />                                    
    <Binding Source="{StaticResource kg}"/>                                    
  </MultiBinding>
</TextBlock.Text>

参考 WPF 编程宝典第 4 版:第 20 章

IValueConverter 的使用:

.Net内置的Converter:

System.Windows.Controls.BooleanToVisibilityConverter

当ViewModel中的属性不能直接用来在View里显示时,或者当使用双向绑定时,View上的值和ViewModel中的属性不匹配,需要使用Converter。

1.新建类实现 IvalueConverter 接口

//要加此句验证

if (value == DependencyProperty.UnsetValue)) return Default..;

2.在 xaml 文件中引入 xmlns 对应的程序集和命名空间

3.在 xaml 文件的 Resource 中定义此 key

4.在 xaml 文件绑定的元素出使用此 converter

5.可以添加ConverterParameter来添加额外的参数

如:

namespace System.Windows.Data;
public interface IValueConverter
{
    //从Data到UI
    // value: 为Xaml中使用Binding绑定的数据源
    //parameter:为Xaml中ConverterParameter绑定或直接赋的值,可以为常量值,或者使用Binding
    object Convert(object value, Type targetType, object parameter, CultureInfo culture);
    //从UI到Data
    object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

Xaml 文件中的更改:

xmlns:local="clr-namespace:UI.GradientStatus"

<UserControl.Resources>
  <local:StatusValueConverter x:Key="StatusValueConverter"/>
</UserControl.Resources>

<!-- 此处的Parameter为string类型-->
<!-- ConverterParameter可以使用常量赋值, -->
<Rectangle Width="10" Height="10" 
  Fill="{Binding Path=StatusValue,
  Converter={StaticResource StatusValueConverter},
  ConverterParameter=1}"/>

<!-- 此处的Parameter为string类型-->
<!-- 字符串类型,无需加引号 -->
<!-- 注意 1 是字符串,不是int类型 -->
ConverterParameter=1
ConverterParameter=Left

<!-- 也可以使用Binding -->
ConverterParameter="{x:Static helper:ParameterDefinitions.Absent}"

<!--ConverterParameter 中绑定常量或者Static变量 -->
<Rectangle Fill="{Binding 
  IsLeftTipTrayEditing,
  Converter={StaticResource My.Converters.GBooleanToVisibilityConverter},
  ConverterParameter={x:Static commonHelper:ParameterDefinitions.Reverse}}"/>

IMultiValueConverter:

参考 WPF 编程宝典第 4 版:20.2.6

绑定到ViewModel上的多个属性,转换为UI的状态。

1.定义 Convert

public class BoolBrushValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        //要加此句验证
        if (values.Any(v => v == DependencyProperty.UnsetValue)) return Brushes.White;
        if (values.Length != 3) return Brushes.White;
        String keyName = values[0].ToString();
        String keyValue = values[1].ToString();
        String referValue = values[2].ToString();
        return Brushes.Red;
    }
}

2.在 XAML 文件中定义 Convert 资源

<UserControl.Resources>
<diagnosis:BoolBrushValueConverter x:Key="boolBrushValueConverter"/>
</UserControl.Resources>

3.MultiBinding 的使用

<DataGrid IsReadOnly="True" ItemsSource="{Binding DspInfo_1}" Margin="10,10"
    ColumnHeaderStyle="{StaticResource Header}" 
    RowStyle="{StaticResource RowStyle}"
    HorizontalAlignment="Stretch" 
    VerticalAlignment="Stretch" 
    AutoGenerateColumns="False" 
    Style="{DynamicResource Style}">
    <DataGrid.Columns>
        <DataGridTextColumn Width="*" Header="Name" 
                    Binding="{Binding KeyName }" 
                    CellStyle="{StaticResource DataGridCell}" />
    <DataGridTemplateColumn Width="*" Header="Value">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding KeyValue}">
                        <TextBlock.Foreground>
                            <MultiBinding 
                                  Converter="{StaticResource boolBrushValueConverter}">
                                <Binding Path="KeyName"/>
                                <Binding Path="KeyValue"/>
                                <Binding Path="KeyReferValue"/>
                            </MultiBinding>
                        </TextBlock.Foreground>
                    </TextBlock>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Width="*" Header="ReferValue" 
                    Binding="{Binding KeyReferValue,Mode=TwoWay}" 
                    CellStyle="{StaticResource DataGridCell}" />
    </DataGrid.Columns>
</DataGrid>

或者结合 RelativeSource

<TextBlock.Visibility>
     <MultiBinding Converter="{StaticResource SnVisibConverter}">
         <Binding Path="IsSelected" 
                  RelativeSource="{RelativeSource AncestorType=ListBoxItem}" />
         <Binding Path="IsMouseOver"
                  RelativeSource="{RelativeSource AncestorType=ListBoxItem}" />
    </MultiBinding>
</TextBlock.Visibility>

VisualStudio中的绑定出错分析

在WPF运行过程中,VisualStudio的XAML绑定失败窗口中可以列出绑定有问题的地方。

数据上下文:表示DataContext指向的类型

绑定路径:XAML文件中写的Bindig Path

目标:XAML中的目标依赖项或附加属性

目标类型:依赖项属性或者附加属性的属性类型

豫ICP备2021008859号-1