WPF数据绑定对象Binding中的辅助属性
数据绑定方向——Model
namespace System.Windows.Data
{
public enum BindingMode
{
TwoWay = 0,
OneWay = 1,
OneTime = 2,
OneWayToSource = 3,
Default = 4
}
}
TwoWay = 0,
绑定的双方,值可以相互传递
OneWay = 1,
绑定后,数据从源到目标 Data.Value->TextBox.Text
OneTime = 2,
绑定的双向,在初始化的时候,数据同步一次,Data.Value->TextBox.Text
OneWayToSource = 3,
绑定后,数据从目标到源,TextBox.Text->Data.Value
Default = 4
默认,没有写Model属性的时候是这个值,目标依赖属性的什么形式就是什么形式
public class Data
{
public string Value { get; set; } = "123";
}
<TabItem Header="关于Mode属性">
<StackPanel>
<!--==================关于Mode属性=========================-->
<!--TextBlock的Text属性默认是单向绑定,TextBox的Text属性默认是双向绑定-->
<TextBlock Text="{Binding Value}"/>
<!--原因是:上面对象是只是做显示,下面对象可以做编辑(页面输入时,信息会回写,写到Value属性里去)-->
<!--这里问题与Value的属性类型无关-->
<TextBox Text="{Binding Value,Mode=TwoWay}"/>
<TextBox Text="{Binding Value,Mode=OneTime}"/>
<TextBox Text="{Binding Value,Mode=OneWayToSource}"/>
<TextBox/>
</StackPanel>
</TabItem>
更新数据源时机——UpdateSourceTrigger
namespace System.Windows.Data
{
public enum UpdateSourceTrigger
{
Default = 0,
PropertyChanged = 1,
LostFocus = 2,
Explicit = 3
}
}
Default = 0,
不更新源
PropertyChanged = 1,
属性值发生变化的时候,更新源
LostFocus = 2,
失去焦点,更新源
Explicit = 3
明确的告诉进行更新
<TabItem Header="关于UpdateSourceTrigger属性">
<StackPanel>
<!--==============关于UpdateSourceTrigger属性==============-->
<!--TextBox的Text属性推送给源的时机是控件失去焦点-->
<Button Content="Button" Click="Button_Click"/>
<TextBox Text="{Binding Value,Mode=TwoWay}"/>
<TextBox Text="{Binding Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Value,Mode=TwoWay,UpdateSourceTrigger=Explicit}" Name="tb"/>
</StackPanel>
</TabItem>
private void Button_Click(object sender, RoutedEventArgs e)
{
// 获取对应控件属性的绑定表达式
BindingExpression bindingExpression = tb.GetBindingExpression(TextBox.TextProperty);
// 指定更新源
bindingExpression.UpdateSource();
}
延时更新数据源——Delay
<TabItem Header="关于Delay属性">
<!--
// 变化的时候做什么逻辑
// UpdateSrouce的时候,每次变化都会触发Set,如果Set里有逻辑 跟不上,
// 希望每次输入都能直接触发更新,不需要失去焦点,不希望每个字符都更新
// 可以让更新的时机稍等下 Delay
// Text属性被连续更新后多少毫秒
-->
<StackPanel>
<!--这里更新的时机是失去焦点,Delay无效,不会等待2000-->
<TextBox Text="{Binding Value,Mode=TwoWay,Delay=2000}"/>
<!--实时同步更新源的时候,可以等待-->
<TextBox Text="{Binding Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,Delay=2000}"/>
<TextBox/>
</StackPanel>
</TabItem>
数据显示格式——StringFormat
<TabItem Header="StringFormat">
<StackPanel>
<TextBlock Text="{Binding IntValue,StringFormat={}{0:000000}}"/>
<TextBlock Text="{Binding IntValue,StringFormat={}{0:P2}}"/>
<TextBlock Text="{Binding FloatValue,StringFormat={}{0:0.000}}"/>
<TextBlock Text="{Binding FloatValue,StringFormat={}{0:N3}}"/>
<TextBlock Text="{Binding FloatValue,StringFormat={}{0:C3},ConverterCulture=zh-cn}"/>
<TextBlock Text="{Binding DateTimeValue,StringFormat={}{0:yyyy-MM-dd}}"/>
<Border Height="1" Background="Red"/>
<TextBlock Text="{Binding IntValue,StringFormat=\{0:000000\}}"/>
<TextBlock Text="{Binding IntValue,StringFormat=\{0:P2\}}"/>
</StackPanel>
</TabItem>
触发器——Converter
使用自定义触发器转义字符
public class Data
{
public string Value { get; set; } = "123";
public int IntValue { get; set; } = 100;
public float FloatValue { get; set; } = 0.1f;
public DateTime DateTimeValue { get; set; } = DateTime.Now;
public int CodeValue { get; set; } = 64; // '@'
public Data()
{
//IntValue.ToString("000000.00");
//DateTimeValue.ToString("yyyy-MM-dd HH:mm:ss");
}
}
// MarupExtension这个基类不是必须继承,主要目标是希望CharConverter可以以{}的形式在XAML中实例化
// 比如:<TextBlock Text="{Binding CodeValue,Converter={local:CharConverter}}"/>
// 没有继承MarkupExtensionn的情况下,XAML中以资源或标签对象的方式进行引用
// 比如:<TextBlock Text="{Binding CodeValue,Converter={StaticResource cc}}"/>
public class CharConverter :MarkupExtension, IValueConverter
{
// 数据从源到目标的时候,执行这个方法,将这个方法的结果显示在目标
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//int.Parse(value.ToString()) * 0.1;
// &#@e618
// (char)e618 ' '
// 源的数据 64 int
return (char)int.Parse(value.ToString());
}
// 数据从目标到源的时候。执行这个方法,将这个方法的结果提交给源
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// 目标的数据 a 97
//value
return (int)(value.ToString()[0]);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
<Window.Resources>
<local:CharConverter x:Key="cc"/>
</Window.Resources>
<TabItem Header="Converter">
<StackPanel>
<!--这种方式需要CharConverter实现MarkupExtension基类-->
<TextBlock Text="{Binding CodeValue,Converter={local:CharConverter}}"/>
<!--这种方式需要提前定义资源-->
<TextBlock Text="{Binding CodeValue,Converter={StaticResource cc}}"/>
<!--这种方式 可以不用定义资源-->
<TextBlock>
<TextBlock.Text>
<Binding Path="CodeValue">
<Binding.Converter>
<local:CharConverter/>
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
<TextBox Text="{Binding CodeValue,Converter={StaticResource cc},UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged">
</TextBox>
</StackPanel>
</TabItem>
使用系统默认触发器转换控件属性
<Window.Resources>
<!--自定义转换器-->
<local:CharConverter x:Key="cc"/>
<!--系统默认的转换器,非常常用-->
<BooleanToVisibilityConverter x:Key="btv"/>
<!--主要针对GroupBox的边框处理,不常用-->
<BorderGapMaskConverter x:Key="bgmc"/>
</Window.Resources>
<CheckBox Content="展开" VerticalContentAlignment="Center" IsChecked="{x:Null}"
Name="cb"/>
<Border Height="30" Background="Orange"
Visibility="{Binding ElementName=cb,Path=IsChecked,Converter={StaticResource btv}}"/>
<Window.Resources>
<AlternationConverter x:Key="ac">
<SolidColorBrush>red</SolidColorBrush>
<SolidColorBrush>Green</SolidColorBrush>
<SolidColorBrush>orange</SolidColorBrush>
</AlternationConverter>
<x:Array Type="sys:Int32" x:Key="adatas">
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
<sys:Int32>3</sys:Int32>
<sys:Int32>4</sys:Int32>
<sys:Int32>5</sys:Int32>
</x:Array>
<Style TargetType="ListBoxItem">
<Setter Property="Background"
Value="{Binding RelativeSource={RelativeSource Self},Path=(ItemsControl.AlternationIndex),Converter={StaticResource ac}}"/>
</Style>
</Window.Resources>
<ListBox ItemsSource="{StaticResource adatas}" AlternationCount="3">
触发器参数传递——ConverterParameter
// 1:男 2:女
public int Gender { get; set; } = 2;
// MarupExtension这个基类不是必须继承,主要目标是希望CharConverter可以以{}的形式在XAML中实例化
// 比如:<TextBlock Text="{Binding CodeValue,Converter={local:CharConverter}}"/>
// 没有继承MarkupExtensionn的情况下,XAML中以资源或标签对象的方式进行引用
// 比如:<TextBlock Text="{Binding CodeValue,Converter={StaticResource cc}}"/>
public class CharConverter : MarkupExtension, IValueConverter
{
// 数据从源到目标的时候,执行这个方法,将这个方法的结果显示在目标
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//int.Parse(value.ToString()) * 0.1;
// &#@e618
// (char)e618 ' '
// 源的数据 64 int
int p = parameter == null ? 0 : int.Parse(parameter.ToString());
return (char)(int.Parse(value.ToString()) + p);
}
// 数据从目标到源的时候。执行这个方法,将这个方法的结果提交给源
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// 目标的数据 a 97
//value
return (int)(value.ToString()[0]);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class GenderConverter : IValueConverter
{
// 源 到 目标(IsChecked bool)
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//value是源的属性值
var v = int.Parse(value.ToString());
var p = int.Parse(parameter.ToString());
if (v == p) return true;
return false;
}
// 目标到 源
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//value是目标的属性值
if (Boolean.Parse(value.ToString()))
return parameter;
return null;
}
}
<Window.Resources>
<!--自定义转换器-->
<local:CharConverter x:Key="cc"/>
<local:GenderConverter x:Key="gc"/>
</Window.Resources>
<!--ConverterParameter可以参定参数参与到转换器逻辑中,但是不允许绑定动态值-->
<TextBlock Text="{Binding CodeValue,Converter={StaticResource cc},ConverterParameter=0}"/>
<TextBlock Text="{Binding CodeValue,Converter={StaticResource cc},ConverterParameter=2}"/>
<TextBlock Text="{Binding CodeValue,Converter={StaticResource cc},ConverterParameter=4}"/>
<!--小案例:用户编辑窗口,里面有个属性叫性别(可以下拉,RadioButton单选,选项是4个以内用单选)-->
<!--如何区分这两个信息-》数据中的一个属性中-->
<RadioButton Content="男" VerticalContentAlignment="Center"
IsChecked="{Binding Gender,Converter={StaticResource gc},ConverterParameter=1}"/>
<RadioButton Content="女" VerticalContentAlignment="Center"
IsChecked="{Binding Gender,Converter={StaticResource gc},ConverterParameter=2}"/>
绑定异常处理——FallbackValue-TargetNullValue
FallbackValue:无法绑定的时候,显示一个默认值
TargetNullValue:数据源属性的值为Null的时候目标属性中需要显示的信息
public class MyBinding : Binding
{
public MyBinding()
{
this.TargetNullValue = "AAAA";
}
}
<TabItem Header="FallbackValue-TargetNullValue">
<StackPanel>
<!--绑定失败的表示意思是:1、打不到数据源 2、Path路径无效 以上情况FallbackValue的指定值可以呈现-->
<TextBlock Text="{Binding ElementName=cb,Path=aaa,FallbackValue=绑定失败}"/>
<TextBlock Text="{Binding ElementName=cb,Path=IsChecked,FallbackValue=绑定失败,TargetNullValue=空值}"/>
<TextBlock Text="{local:MyBinding ElementName=cb,Path=IsChecked}"/>
</StackPanel>
</TabItem>
数据验证——ValidationRules
系统验证-ExceptionValidationRule
public class Data : INotifyPropertyChanged
{
private int _value;
public event PropertyChangedEventHandler? PropertyChanged;
public int Value
{
get { return _value; }
set
{
if (value == 123)
throw new Exception("比如不能输入123");
_value = value;
}
}
}
<Window.DataContext>
<local:Data/>
</Window.DataContext>
<StackPanel Margin="20">
<TextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged}" Name="tb1"/>
<TextBox TabIndex="0" Focusable="True" Name="tb2">
<TextBox.Text>
<Binding Path="Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Content="Get exception" Click="Button_Click"/>
<TextBlock Text="{Binding Path=(Validation.Errors)[0].ErrorContent,ElementName=tb2}" Foreground="Red"/>
</StackPanel>
目标到源自定义验证-ValidationRule
public class ValueValidationRule : ValidationRule
{
// 实例的调用时机:是在界面上控件被绑定的属性发生变化的时候
// 从目标到源过程,做这个处理
// 关于从源中赋值更新的异常提示:IDataErrorInfo
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// 做值的校验规则
//value参数指的是目标值
if (int.TryParse(value.ToString(), out int v))
{
if (v == 123)
return new ValidationResult(false, "比如不能输入123");
}
else
return new ValidationResult(false, "输入信息不对");
return new ValidationResult(true, string.Empty);
}
}
<StackPanel Margin="20">
<TextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged}" Name="tb1"/>
<TextBox TabIndex="0" Focusable="True" Name="tb2">
<TextBox.Text>
<Binding Path="Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Content="Get exception" Click="Button_Click"/>
<TextBlock Text="{Binding Path=(Validation.Errors)[0].ErrorContent,ElementName=tb3}" Foreground="Red"/>
<TextBox TabIndex="0" Focusable="True" Name="tb3">
<TextBox.Text>
<Binding Path="Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:ValueValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
源到目标自定义验证-IDataErrorInfo
public class Data : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler? PropertyChanged;
private int _value;
public int Value
{
get { return _value; }
set { _value = value;}
}
// 索引
public string this[string propName]
{
get
{
if (propName == "Value")
{
if (this.Value > 1000)
return "大于1000![IDataErrorInfo]";
}
return "";
}
}
}
<TextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}" Name="tb4"/>
ValidatesOnDataErrors验证开关,不设置此属性为True则不进行校验。
利用特性值进行验证
public class Data : DataBase, INotifyPropertyChanged, IDataErrorInfo
{
private int _value;
[Required]
[DisplayName("温度报警值")]
public int Value
{
get { return _value; }
set
{
//if (value == 123)
// throw new Exception("比如不能输入123");
_value = value;
}
}
[MaxLength(20)]
public string MyProperty { get; set; }
[MaxLength(20)]
public string MyProperty1 { get; set; }
[StringLength(maximumLength: 10, MinimumLength = 1)]
[EmailAddress(ErrorMessage = "邮箱地址不合法")]
public string Error { get; set; }
public string this[string propName]
{
get
{
//if (propName == "Value")
//{
// if (this.Value > 1000)
// return "大于1000![IDataErrorInfo]";
//}
//else if (propName == "MyProperty")
//{
// if (this.MyProperty.Length > 50)
// return "长度大于50";
//}
PropertyInfo pi = this.GetType().GetProperty(propName);
// IsDefined 检查当前属性是否添加了指定的特定
if (pi.IsDefined(typeof(MaxLengthAttribute)))
{
// 获取当前属性上指定的特定实例,一般是需要获取特性实例中的某些值的时候使用
MaxLengthAttribute ma = (MaxLengthAttribute)pi.GetCustomAttribute(typeof(MaxLengthAttribute));
string value = pi.GetValue(this).ToString();
if (value.Length > ma.Length)
return $"长度大于{ma.Length}";
}
if (pi.IsDefined(typeof(RequiredAttribute)))
{
//DisplayNameAttribute da = pi.GetCustomAttribute<DisplayNameAttribute>();
//var dn = da.DisplayName;
string value = pi.GetValue(this).ToString();
if (string.IsNullOrEmpty(value) || value == "0")
return $"必然输入{propName}";
}
if (pi.IsDefined(typeof(EmailAddressAttribute)))
{
EmailAddressAttribute ea = pi.GetCustomAttribute<EmailAddressAttribute>();
if (!ea.IsValid(pi.GetValue(this)))
return ea.ErrorMessage;
}
return "";
}
}
}
自定义特性验证-IP格式验证
Attribute-基础特性
ValidationAttribute-微软提供所有验证属性的基类,继承Attribute
//public class CheckRepeatAttribute : Attribute
//{
//}
public class IPCheckAttribute : ValidationAttribute
{
public override bool IsValid(object? value)
{
// 检查IP格式
// 正则表达式
// 核心 字符串中每个字符逐个判断
// 192.168.1.2
// 使用.间隔
// 每个间隔的数字都是0-255
// \. \d
string pattern = @"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";
//"^(([0-9]|[1-9][0-9]|1[0-9][0-9]2[0-4][0-9]25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]2[0-4][0-9]25[0-5])"
//"^((\d\[1-9]\d\1\d\d2([0-4]\d5[0-5]))\.){4}$"
// \w \W \s \S \d \D
// * + ? {10}
// 工艺编号 asdawd-20324-sdfse3
// @"^\w{6}\-\w{5}\-\w{6}$" 正则表达式
if (value == null || string.IsNullOrEmpty(value.ToString())) return true;
return Regex.IsMatch(value.ToString(), pattern);
}
}
public class Data : DataBase, INotifyPropertyChanged, IDataErrorInfo
{
[IPCheck(ErrorMessage = "IP格式不正确")]
[MaxLength(20)]
public string MyProperty { get; set; }
public string this[string propName]
{
get
{
if (pi.IsDefined(typeof(IPCheckAttribute)))
{
IPCheckAttribute ca = pi.GetCustomAttribute<IPCheckAttribute>();
if (!ca.IsValid(pi.GetValue(this)))
{
return ca.ErrorMessage;
}
}
return "";
}
}
}
样式模板分享
<Window.Resources>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="3"/>
<!--错误时不显示外框-->
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<AdornedElementPlaceholder/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="5">
<ScrollViewer Name="PART_ContentHost" VerticalScrollBarVisibility="Hidden"/>
</Border>
<!--数据源默认为Window的数据源,需要使用RelativeSource设置数据源为使用该模板的控件本身-->
<TextBlock Text="{Binding Path=(Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource Mode=TemplatedParent}}" Foreground="Red"
Visibility="Collapsed" Name="error"
Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter TargetName="error" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
多重绑定——MultiBinding
StringFormat
设置StringFormat属性指定显示格式
<Window.DataContext>
<local:Data/>
</Window.DataContext>
<Window.Resources>
<local:CombinValueConverter x:Key="cvc"/>
<local:WidthValueConverter x:Key="wvc"/>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}"/>
<TextBlock Text="{Binding Value2}"/>
</StackPanel>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="----{1}----{0}">
<Binding Path="Value2"/>
<Binding Path="Value"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
Converter
利用转换器进行处理
public class CombinValueConverter : IMultiValueConverter
{
// 数据源到目标的处理过程
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[1].ToString() + ";" + values[0].ToString();
}
// 从显示目标控件属性到数据源的过程
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string[] str = value.ToString().Split(";");
object[] result = new object[str.Length];
result[0] = str[0];
result[1] = int.Parse(str[1].ToString());
//return new object[] { "AAA", 789 };
return result;
}
}
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource cvc}">
<Binding Path="Value2"/>
<Binding Path="Value"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
样例分享
绑定一个百分比参数的进度条,需要跟随窗体大小自适应改变
public class WidthValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return double.Parse(values[0].ToString()) * double.Parse(values[1].ToString());
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
<Border Height="20" Background="Gray" Margin="30,10" Name="bor">
<Border Background="Orange" HorizontalAlignment="Left">
<Border.Width>
<MultiBinding Converter="{StaticResource wvc}">
<Binding Path="ProgressValue"/>
<Binding ElementName="bor" Path="ActualWidth"/>
</MultiBinding>
</Border.Width>
</Border>
</Border>
优先级绑定——PriorityBinding
IsAsync="True"异步
Value1延时3秒,Value2延时2秒,Value3延时1秒
TextBlock显示顺序为Value3-Value2。先显示Value3是因为Value2未到,不显示Value1因为Value2的优先级比Value1高。
<TextBlock>
<TextBlock.Text>
<PriorityBinding>
<Binding Path="Value2" IsAsync="True"/>
<!--2000-->
<Binding Path="Value1" IsAsync="True"/>
<!--3000-->
<Binding Path="Value3" IsAsync="True"/>
<!--1000-->
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
移除绑定——BindingOperations
//清除TextBox所有的绑定关系
BindingOperations.ClearAllBindings(this.tb);
//清除TextBox指定属性的绑定
BindingOperations.ClearBinding(this.tb, TextBox.TextProperty);