DataBinding & Converter
给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中的目标依赖项或附加属性
目标类型:依赖项属性或者附加属性的属性类型