WPF Converter(转)
WPF Binding 用于数据有效性校验的关卡是它的 ValidationRules 属性,用于数据类型转换的关卡是它的 Converter 属性。下面是实例:
1. Binding 的数据校验
<Window x:Class="WpfStudy.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfStudy" Title="MainWindow" Height="266" Width="300"> <StackPanel> <TextBox x:Name="textBox1" Margin="5"/> <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/> </StackPanel> </Window>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Binding binding = new Binding("Value") { Source = this.slider1 }; binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; binding.NotifyOnValidationError = true; RangeValidationRule rvr = new RangeValidationRule(); rvr.ValidatesOnTargetUpdated = true; binding.ValidationRules.Add(rvr); this.textBox1.SetBinding(TextBox.TextProperty, binding); this.textBox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError)); } public void ValidationError(object sender, RoutedEventArgs e) { if (Validation.GetErrors(this.textBox1).Count > 0) { this.textBox1.ToolTip = Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString(); } } }
public class RangeValidationRule : ValidationRule { //需要实现 Validate 方法 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, "Validation Failed"); } }
完成后运行程序,当输入0到100之间的值时程序正常显示,但输入这个区间之外的值或不能被解析的值时 TextBox 会显示红色边框,表示值是错误的,不能把它传递给 Source。
Binding 进行校验时的默认行为是认为来自 Source 的数据总是正确的,只有来自 Target 的数据(因为 Target 多为 UI 控件,所以等价于用户输入的数据)才有可能有问题,为了不让有问题的数据污染 Source 所以需要校验。换句话说,Binding 只在 Target 被外部方法更新时校验数据,而来自 Binding 的 Source 数据更新 Target 时是不会进行校验的。如果想改变这种行为,或者说当来自 Source 的数据也有可能出现问题时,我们就需要将校验的条件的 ValidateOnTargetUpdated 属性设为 true。
2. Binding 的数据转换
当数据从 Binding 的 Source 流向 Target 时,Convert 方法将被调用;反之,ConvertBack 方法将被调用。这两个方法的参数列表一模一样:第一个参数为 object,最大限度地保证了 Converter 的重用性(可以在方法体内对实际类型进行判断);第二个参数用于确定方法的返回类型(个人认为形参名字叫 outputType 比 targetType 要好,可以避免与 Binding 的 Target 混淆);第三个参数用于把额外的信息传入方法,若需要传递多个信息则可把信息放入一个集合对象来传入方法。
Binding 对象的 Mode 属性会影响到这两个方法的调用。如果 Mode 为 TwoWay 或 Default 行为与 TwoWay 一致则两个方法都有可能被调用;如果 Mode 为 OneWay 或 Default 行为与 OneWay 一致则只有 Convert 方法会被调用;其他情况同理。
<Window x:Class="WpfStudy.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfStudy" Title="MainWindow" Height="266" Width="300"> <Window.Resources> <local:CategoryToSourceConverter x:Key="cts"/> <local:StateToNullableBoolConverter x:Key="stnb"/> </Window.Resources> <StackPanel Background="LightBlue"> <ListBox x:Name="listBoxPanel" 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="buttonLoad" Content="Load" Height="25" Margin="5,0" Click="ButtonLoad_Click"/> <Button x:Name="buttonSave" Content="Save" Height="25" Margin="5,5" Click="ButtonSave_Click"/> </StackPanel> </Window>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void ButtonLoad_Click(object sender, RoutedEventArgs e) { List<Plane> planeList = new List<Plane>() { new Plane() { Category = Category.Bomber, Name = "B-1", State = State.Unknown }, new Plane() { Category = Category.Bomber, Name = "B-2", State = State.Unknown }, new Plane() { Category = Category.Fighter, Name = "F-22", State = State.Unknown }, new Plane() { Category = Category.Fighter, Name = "Su-47", State = State.Unknown }, new Plane() { Category = Category.Bomber, Name = "B-52", State = State.Unknown }, new Plane() { Category = Category.Fighter, Name = "J-10", State = State.Unknown } }; this.listBoxPanel.ItemsSource = planeList; } private void ButtonSave_Click(object sender, RoutedEventArgs e) { StringBuilder sb = new StringBuilder(); foreach(Plane p in listBoxPanel.Items) { sb.AppendLine(string.Format("Category={0},Name={1},State={2}", p.Category, p.Name, p.State)); } File.WriteAllText(@"D:\PlaneList.txt", sb.ToString()); } }
// Converters public class CategoryToSourceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo cultureinfo) { 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 cultureinfo) { throw new NotImplementedException(); } } public class StateToNullableBoolConverter : IValueConverter { // 将 State 转换为 bool? public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { State s = (State)value; switch (s) { case State.Locked: return false; case State.Available: return true; case State.Unknown: default: return null; } } // 将 bool? 转换为 State public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { bool? nb = (bool?)value; switch (nb) { case true: return State.Available; case false: return State.Locked; case null: default: return State.Unknown; } } }
//种类 public enum Category { Bomber, Fighter } //状态 public enum State { Available, Locked, Unknown } //飞机 public class Plane { public Category Category { get; set; } public string Name { get; set; } public State State { get; set; } }
3. MultiBinding(多路 Binding)
<Window x:Class="WpfStudy.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfStudy" Title="MainWindow" Height="266" Width="300"> <StackPanel Background="LightBlue"> <TextBox x:Name="textBox1" Height="23" Margin="5"/> <TextBox x:Name="textBox2" Height="23" Margin="5,0"/> <TextBox x:Name="textBox3" Height="23" Margin="5"/> <TextBox x:Name="textBox4" Height="23" Margin="5,0"/> <Button x:Name="button1" Content="Submit" Width="80" Margin="5"/> </StackPanel> </Window>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.SetMultiBinding(); } private void SetMultiBinding() { //准备基础Binding Binding b1 = new Binding("Text") { Source = this.textBox1 }; Binding b2 = new Binding("Text") { Source = this.textBox2 }; Binding b3 = new Binding("Text") { Source = this.textBox3 }; Binding b4 = new Binding("Text") { Source = this.textBox4 }; //准备MultiBinding MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay }; mb.Bindings.Add(b1); //注意:MultiBinding对Add子Binding的顺序是敏感的 mb.Bindings.Add(b2); mb.Bindings.Add(b3); mb.Bindings.Add(b4); mb.Converter = new LogonMultiBindingConverters(); //将 Button 与 MultiBinding 对象关联 this.button1.SetBinding(Button.IsEnabledProperty, mb); } }
public class LogonMultiBindingConverters : 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(); } }
转自:《深入浅出 WPF》 第六章
posted on 2019-09-24 16:27 wangzhiliang 阅读(783) 评论(0) 编辑 收藏 举报