Binding对数据的转换和校验
Binding作为Source和Target之间的桥梁,可以在桥梁上设置校验,如果桥梁两端要求的数据类型不同时,还可以设置类型转换器。
Binding数据校验
Binding的ValidationRules属性类型Collection,即可以设置多个校验规则。
<StackPanel>
<TextBox x:Name="textBox1" Margin="5"/>
<Slider x:Name="slider" Minimum="0" Maximum="100" Margin="5"/>
</StackPanel>
public class RangeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
double d = 0;
if (double.TryParse(value.ToString(),out d))
{
if (d>=0 && d<=100)
{
return new ValidationResult(true, null);
}
}
return new ValidationResult(false, "错误内容");
}
}
Binding binding = new Binding("Value") { Source = this.slider };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rvr = new RangeValidationRule();
binding.ValidationRules.Add(rvr);
this.textBox1.SetBinding(TextBox.TextProperty, binding);
Binding默认来自Source的数据总是正确的,只有Target的数据才有问题。所以默认来自Source的数据更新Target时不会进行校验。如果要校验Source则要把ValidationRule的ValidatesOnTargetUpdated属性设置为True。
显示校验错误内容
将Binding对象的NotifyOnValidationError属性设置为true,这样失败时Binding触发信号,该信号会在以Binding对象的Target为起点的UI元素树上进行传播。信号每到达一个节点就要查看这个节点是否设置了对该信号的监听器,如果设置了监听器就会被触发。程序员也可以设置信号的继续传播还是就此终止,这就是路由事件,信号在UI元素树上传递的过程就称为路由。
Binding binding = new Binding("Value") { Source = this.slider };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rvr = new RangeValidationRule();
binding.ValidationRules.Add(rvr);
binding.NotifyOnValidationError = true;
this.textBox1.SetBinding(TextBox.TextProperty, binding);
this.textBox1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler( (sender, e) =>
{
if (Validation.GetErrors(this.textBox1).Count > 0)
{
this.textBox1.ToolTip = Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString();
}
}));
Binding的数据转换
Binding的转换机制(Data Convert),当Source端的Path属性类型和Target所需要的类型不一致时使用。
自定义Converter要继承IValueConverter
public interface IValueConverter
{
//从source到Target时使用
object Convert(object value, Type targetType, object parameter, CultureInfo culture);
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
//Mode会影响方法的调用,如TwoWay则两个方法都可能被调用,OneWay则知道用Convert
案例:
//种类
public enum Category { Bomber,Fighter}
//状态
public enum State { Available,Locked,Unknow}
//飞机
public class Plane
{
public Category Category { set; get; }
public string Name { set; get; }
public State State { set; get; }
}
public class CategoryToSourceConverter : IValueConverter
{
//将Category转换为Uri
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Category c = (Category)value;
switch (c)
{
case Category.Bomber:
return @"\Icons\bomber.png";
case Category.Fighter:
return @"\Icons\fighter.png";
default:
return null;
}
}
//单向不会被调用
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class StateToNullableBoolConvert : IValueConverter
{
//state转换为bool
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
State s = (State)value;
switch (s)
{
case State.Available:
return true;
case State.Locked:
return false;
case State.Unknow:
default:
return null;
}
}
//bool转State
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool? b = (bool?)value;
switch (b)
{
case true:
return State.Available;
case false:
return State.Locked;
case null:
default:
return State.Unknow;
}
}
}
<Window.Resources>
<local:CategoryToSourceConverter x:Key="cts"/>
<local:StateToNullableBoolConvert x:Key="stnb"/>
</Window.Resources>
<StackPanel Background="LightBlue">
<ListBox x:Name="lstBoxPlane" Height="160" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>
<CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="btnLoad" Content="Load" Height="25" Margin="5" Click="BtnLoad_Click"/>
<Button x:Name="btnSave" Content="Save" Height="25" Margin="5" Click="BtnSave_Click"/>
</StackPanel>
private void BtnLoad_Click(object sender, RoutedEventArgs e)
{
List<Plane> planes = new List<Plane>()
{
new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unknow},
new Plane(){Category=Category.Bomber,Name="B-2",State=State.Unknow},
new Plane(){Category=Category.Fighter,Name="f-3",State=State.Unknow},
new Plane(){Category=Category.Fighter,Name="f-1",State=State.Unknow},
new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unknow},
new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unknow},
new Plane(){Category=Category.Fighter,Name="f-1",State=State.Unknow},
};
this.lstBoxPlane.ItemsSource = planes;
}
private void BtnSave_Click(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (Plane p in lstBoxPlane.Items)
{
sb.AppendLine($"Category={p.Category},Name={p.Name},State={p.State}");
}
Debug.Write(sb.ToString());
}
多路Binding
当需要的信息不止一个数据源时,可以使用MultiBinding,MultiBinding具有一个Bindings的属性,类型是Collection,处在这个集合中的Binding对象可以拥有自己的数据校验和转换机制,他们汇总起来的数据将传递到Target上。
案例:用于新用户注册的UI,第一、二个TextBox要求内容一致;第三、四TextBox要求内容一致;当所有TextBox内容全部符合要求时,Button可用。
<StackPanel Background="LightBlue">
<TextBox x:Name="txt1" Height="23" Margin="5"/>
<TextBox x:Name="txt2" Height="23" Margin="5"/>
<TextBox x:Name="txt3" Height="23" Margin="5"/>
<TextBox x:Name="txt4" Height="23" Margin="5"/>
<Button x:Name="btn" Content="Submit" Width="80" Margin="5"/>
</StackPanel>
//继承自IMultiValueConverter
public class LogonMultiBindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (!values.Cast<string>().Any(text=>string.IsNullOrEmpty(text))&& values[0].ToString() == values[1].ToString() && values[2].ToString() == values[3].ToString())
{
return true;
}
return false;
}
//不会被调用
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
使用MultiBinding
//准备基础的Binding
Binding b1 = new Binding("Text") { Source = this.txt1 };
Binding b2 = new Binding("Text") { Source = this.txt2 };
Binding b3 = new Binding("Text") { Source = this.txt3 };
Binding b4 = new Binding("Text") { Source = this.txt4 };
//准备MultiBinding
MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay };
//注意添加的顺序,它决定了汇集到convert里数据的顺序
mb.Bindings.Add(b1);
mb.Bindings.Add(b2);
mb.Bindings.Add(b3);
mb.Bindings.Add(b4);
mb.Converter = new LogonMultiBindingConverter();
this.btn.SetBinding(Button.IsEnabledProperty, mb);