当前位置 博文首页 > Dwaynerbing:WPF 之 Binding 对数据的校验与转换(三)

    Dwaynerbing:WPF 之 Binding 对数据的校验与转换(三)

    作者:Dwaynerbing 时间:2021-02-06 10:24

    一、前言

    ? Binding 的作用就是架在 SourceTarget 之间的桥梁,数据可以在这座桥梁的帮助下来流通。就像现实中的桥梁会设置一些关卡进行安检一样,Binding 这座桥上也可以设置关卡对数据的有效性进行校验。不仅如此,当两端要求使用不同的数据类型时,我们还可以为数据设置转换器。

    ? Binding 用于数据有效性校验的关卡是它的 ValidationRules 属性,用于数据转换的关卡是它的 **Converter ** 属性。

    二、Binding 对数据的校验

    ? 例如:我们把一个 Slider 的Value 属性和 TextBox 的 Text 属性双向绑定在一起, Slider 的Value 的有效值是 0~100。当我们使用 Binding 绑定数值时,需要对该 Binding 的 ValidationRules 添加校验规则。具体实现如下所示:

    ? 第一步:我们声明一个 RangeValidationRule 类编写校验规则,该类需要继承 ValidationRule 类(该类为抽象类),ValidationRule类的作用是提供创建自定义规则的一个方式,旨在检查用户输入的有效性。具体代码如下:

      public class RangeValidationRule : ValidationRule
        {
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
                if (double.TryParse(value.ToString(), out var d))
                {
                    if (d >= 0 && d <= 100)
                    {
                        return new ValidationResult(true, null);
                    }
                }
                return new ValidationResult(false, "Validation value");
            }
        }
    

    ? 第二步:对 TextBox 的 Text 属性进行 Binding 绑定,并对该 Binding 的 ValidationRules 添加一个校验规则 RangeValidationRule 类,具体实现如下:

        <StackPanel Grid.Row="0">
               <TextBox x:Name="TextBox0" Margin="5" Height="50" VerticalContentAlignment="Center" ToolTip="{Binding ElementName=TextBox0,Path=e}">
                   <TextBox.Text>
                       <Binding ElementName="Slider0" Path="Value" UpdateSourceTrigger="PropertyChanged">
                           <Binding.ValidationRules>
                               <local:RangeValidationRule ></local:RangeValidationRule>
                           </Binding.ValidationRules>
                       </Binding>
                   </TextBox.Text>
               </TextBox>
                <Slider x:Name="Slider0" Margin="5" Minimum="0" Maximum="100"></Slider>
           </StackPanel>
    

    然后,我们在文本框中,输入200,发现 TextBox 会显示红色边框,这表示数值是错误的,具体显示如下图:

    image-20210205151612353

    三、Binding 的数据转换

    ? 例如:我们实现如下一个程序,程序的用途是在列表里向玩家显示一些军用飞机的状态。数据类型如下:

      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; }
        }
    

    ? 在UI中,Plane 的 Category 属性被映射为图片,State 被映射为 CheckBox 的 IsChecked 属性。具体转换规则如下:

    public class CategoryToSourceConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var c = (Category)value;
                switch (c)
                {
                    case Category.Bomber:
                        return "image/bomber.png";
                    case Category.Fighter:
                        return "image/fighter.png";
                }
                return new ValidationResult(false, "Validation Value");
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    
    
    
        public class StateToBoolConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var s = (State) value;
                switch (s)
                {
                    case State.Available:
                        return true;
                    case State.Locked:
                        return false;
                    default:
                        return null;
                }
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var b = (bool?) value;
                switch (b)
                {
                    case true:
                        return State.Available;
                    case false:
                        return State.Locked;
                    default:
                        return State.Unknown;
                }
            }
        }
    

    ? 我们添加一个 ViewModel 绑定到 UI 上,ViewModel 代码如下(属性变更可以阅读 ):

     public class Window3VM : NotifyProperty
        {
            private ObservableCollection<Plane> _planes;
            private string _output;
    
            public ObservableCollection<Plane> Planes
            {
                get => _planes;
                set => SetProperty(ref _planes, value);
            }
    
            public string Output
            {
                get => _output;
                set => SetProperty(ref _output, value);
            }
        }
    

    ? 我们把 ViewModel 绑定到 UI 界面上,并把转换规则添加到对应的 Binding 上,具体实现如下:

    <Window x:Class="UI.Window3"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:UI"
            mc:Ignorable="d"
            d:DataContext="{d:DesignInstance local:Window3VM}"
            Title="Window3" Height="500" Width="600">
        <Window.Resources>
           <local:CategoryToSourceConverter x:Key="CTS"></local:CategoryToSourceConverter>
            <local:StateToBoolConverter x:Key="STB"></local:StateToBoolConverter>
        </Window.Resources>
       <Grid>
           <Grid.RowDefinitions>
               <RowDefinition Height="100"></RowDefinition>
               <RowDefinition Height="*"></RowDefinition>
           </Grid.RowDefinitions>
           <StackPanel Grid.Row="0">
               <TextBox x:Name="TextBox0" Margin="5" Height="50" VerticalContentAlignment="Center" ToolTip="{Binding ElementName=TextBox0,Path=e}">
                   <TextBox.Text>
                       <Binding ElementName="Slider0" Path="Value" UpdateSourceTrigger="PropertyChanged">
                           <Binding.ValidationRules>
                               <local:RangeValidationRule ></local:RangeValidationRule>
                           </Binding.ValidationRules>
                       </Binding>
                   </TextBox.Text>
               </TextBox>
                <Slider x:Name="Slider0" Margin="5" Minimum="0" Maximum="100"></Slider>
           </StackPanel>
            <Grid Grid.Row="1">
               <Grid.ColumnDefinitions>
                   <ColumnDefinition Width="*"></ColumnDefinition>
                   <ColumnDefinition Width="200"></ColumnDefinition>
               </Grid.ColumnDefinitions>
               <StackPanel Grid.Column="0" Margin="10">
                   <ListBox Margin="5" Height="200" ItemsSource="{Binding Path=Planes}">
                       <ListBox.ItemTemplate>
                           <DataTemplate>
                               <StackPanel Orientation="Horizontal">
                                   <Image Margin="0,2,0,2" Width="50" Source="{Binding Path=Category,Converter={StaticResource CTS}}"></Image>
                                   <TextBlock Margin="0,2,0,2" Width="200" TextAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=Name}"></TextBlock>
                                   <CheckBox Margin="0,2,0,2" Width="20"  IsChecked="{Binding Path=State,Converter={StaticResource STB}}"></CheckBox>
                               </StackPanel>
                           </DataTemplate>
                       </ListBox.ItemTemplate>
                   </ListBox>
                   <Button  Margin="5" Height="50" Content="Load" Click="ButtonLoad_OnClick"></Button>
                   <Button Margin="5" Height="50" Content="Save" Click="ButtonSave_OnClick"></Button>
    
               </StackPanel>
               <StackPanel Grid.Column="1">
                   <TextBlock Margin="5,5,5,0" Text="Output Data:"></TextBlock>
                   <TextBox x:Name="TextBox" Margin="5" Height="310" BorderBrush="Black" Text="{Binding Path=Output}"></TextBox>
               </StackPanel>
           </Grid>
        </Grid>
    </Window>
    
    

    ? 如下所示,当我们点击Load 按钮后,加载出来列表,点击 Save 按钮后,把整个列表输出到右边的文本框:

    /// <summary>
        /// Window3.xaml 的交互逻辑
        /// </summary>
        public partial class Window3 : Window
        {
            private readonly Window3VM vm;
            public Window3()
            {
                InitializeComponent();
    
                this.DataContext= vm =new Window3VM();
            }
    
            private void ButtonLoad_OnClick(object sender, RoutedEventArgs e)
            {
                vm.Planes=new ObservableCollection<Plane>()
                {
                    new Plane(){Name = "B-1", Category = Category.Bomber, State = State.Unknown,},
                    new Plane(){Name = "F-35",Category = Category.Fighter,State = State.Unknown,},
                    new Plane(){Name = "B-6", Category = Category.Bomber, State = State.Unknown,},
                    new Plane(){Name = "F-22",Category = Category.Fighter,State = State.Unknown,},
                    new Plane(){Name = "J-20",Category = Category.Fighter,State = State.Unknown,},
                };
            }
    
            
            private void ButtonSave_OnClick(object sender, RoutedEventArgs e)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var plane in vm.Planes)
                {
                    sb.Append($"{plane.Category},{plane.Name},{plane.State}\r\n");
                }
    
                vm.Output = sb.ToString();
            }
        }
    

    image-20210205154832112

    bk