深入浅出WPF 学习笔记二

深入浅出WPF

Binding

其他的先不管,先把通知UI的工作做好,这个地方,吃了很多亏,踩了很多坑,总以为不写通知不会有什么问题,但是不写通知就是不行!

class Student : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        //Binding是一种自动机制,绑定之后,要有能力通知UI
        private string _Name;
        public string Name
        {
            get => _Name;
            set
            {
                _Name = value;
                // 激发事件
                if(this.PropertyChanged != null)
                {
                    this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
                }
            }
        }
    }
  • 通知不一定非要自己定义的数据下面,如果一个数据和其他数据绑定的时候,其他数据变化,也要通知。比如数据A的值由数据B和数据C联合得到,那么数据B和数据C变化的时候,也要通知数据A也发生了变化。例如下面的代码,数据变化不一定要自己下面通知,只要跟自己有关的数据变化,都要通知UI。
		private bool _LoadWaferEnable;
        public bool LoadWaferEnable
        {
            get => _LoadWaferEnable;
            set
            {
                _LoadWaferEnable = value;
                RaisePropertyChanged("LoadWaferEnable");
                RaisePropertyChanged("IsEnableLoadWaferAndAlign");
            }
        }
  • 如果想要Binding源的对象具有自动通知Binding自己的属性值已经变化的能力,那么就要让类实现INotifyPropertyChanged接口,并在属性的set语句中激发PropertyChanged事件

  • 在工作中,可能会出现的多种选择:

    • 控件把自己或自己的容器或子集元素当做源
    • 用一个控件当做另一个控件的源
    • 把集合作为ITemsControl的数据源
    • 使用XML作为TreeView或Menu的源
    • 把多个控件关联到一个“数据制高点”上
  • 把TextBox的Text属性关联到Slider的Value属性上,项目里面挺常用的,hh

<StackPanel>
   <TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider1}" BorderBrush="Black" Margin="5"/>
   <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5"/>
</StackPanel>
  • 有时候数据只需要展示给用户,不允许用户修改,这时候可以把Binding模式更改为从源向目标的单向沟通,Binding还支持从目标向源的单向沟通,以及只在Bingding关系确立时读取一次数据。而控制Binding数据流向的属性是Mode,Mode=OneWay的时候,就是读取只读,不能更改。
  • 有时候Binding的Path是一个. 有时候直接没有Path,这个时候说明绑定的源本身就是数据且不需要Path来指明,而多数情况都是可不需要写Path的。
<StackPanel>
	<StackPanel.Resources>
		<sys:String x:Key="myString">
           身是菩提树,心如明镜台。
           时时勤拂拭,勿使惹尘埃。
        </sys:String>
    </StackPanel.Resources>

    <TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="{Binding Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/>
</StackPanel>
  • 每个结点都有DataContext,当一个Binding只知道自己的Path而不知道自己的Source时,它会沿着UI元素树一路向树的根部找去,每路过一个结点,看看当前这个结点的DataContext是否具有Path所指定的属性,如果有,就把这个对象作为自己的Source,如果没有就继续找下去,如果到了树的根部还没有找到,那这个Binding就没有Source,也不会得到数据。救命,这个在工作中,带我的mentor说过。我当时根本没理解。
  • 当Binding的Source本身就是数据时,不需要使用属性来暴露数据时,有时候Binding的Path和Source都是可以不写的。
  • DataContext是一个“依赖属性”,当你没有为一个控件显式赋值时,控件会把自己容器的属性借过来当做自己属性。实际上是属性值沿着UI树向下传递了。
  • 外层容器的DataContext就相当于一个数据的“制高点”,只要把数据放上去,别的元素都能看见。

使用集合对象作为列表控件的ItemsSource

  • 使用集合作为列表控件的ItemsSource时一般会考虑使用ObservableCollection代替List使用,因为ObservableCollection实现了INotifyCollectionChanged和INotifyPropertyChanged接口,能把集合的变化立即通知到显示它的列表控件,改变会立即显现出来。
  • 对于有明确数据来源的时候,Binding通过Source和ElementName赋值的方法让Binding与之关联。
  • 不确定Source对象的名字,但知道Binding对象在UI布局上有相对关系的时候,比如空间关联自己某一级容器的数据的时候。需要使用Binding的RelativeSource属性。
<Grid x:Name="g1" Background="Red" Margin="10">
        <DockPanel x:Name="d1" Background="Orange" Margin="10">
            <Grid x:Name="g2" Background="Yellow" Margin="10">
                <DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
                    <!--<TextBox x:Name="textBox1" FontSize="24" Margin="10" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}, AncestorLevel=1}, Path=Name}"/>-->
                    <TextBox x:Name="textBox1" FontSize="24" Margin="10" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DockPanel}, AncestorLevel=1}, Path=Name}"/>
                    <TextBox x:Name="textBox1" FontSize="24" Margin="10" Text="{Binding Name, RelativeSource={RelativeSource Mode=Self}}"/>
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>
  • AncestorLevel是指以Binding为目标控件为起点的层级偏移量,d2的偏移量为1,g2的偏移量为2。
    AncestorType高速Binding寻找哪个类型的对象作为自己的源,不是这个类型的对象会被跳过。
    FindAncestor可以不写,Mode=Self的时候,可以绑定为自己。
  • Binding的数据校验,可以用ValidationRules为Binding设置多个数据校验条件,每个条件是一个ValidationRule类型对象。
  • 在创建Binding是把Binding对象的NotifyOnValidationError属性设置为true,这样当数据校验失败的时候Binding会像报警器一样发出一个信号,这个信号会以Binding对象的Target为起点在UI元素树上进行传播,信号的传播为路由事件。

Converter

  • 出现这样的情况,就需要用Converter来做转换:
    • Source里面的值对应按钮是否可点击
    • 输入文字后,等了按钮才会出现
    • 将male和female与头像进行绑定关联
  • 手写Converter并实现IValueConverter接口,必须要把Converter和ConverterBack两个都写上,因为是双向Binding。
  • 当出现以下情况,需要使用转换器Converter来做,并且要让这个类继承IValueConverter接口,并且要实现接口:
    • 控件属性对应到想要的类型
    • TextBox中输入文字后,登录的Button才会出现
    • Source里面的数据可能是枚举类型,但是UI上要显示Image图像
  • MultiBinding:多路Binding,有时候UI需要显示的信息由不止一个数据来源决定,这时候就需要使用MultiBinding。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.SetMultiBinding();
        }
        private void SetMultiBinding()
        {
            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 mb = new MultiBinding() { Mode = BindingMode.OneWay };
            mb.Bindings.Add(b1); // 对顺序敏感
            mb.Bindings.Add(b2);
            mb.Bindings.Add(b3);
            mb.Bindings.Add(b4);
            mb.Converter = new LogonMultiBindingConverter();

            this.button1.SetBinding(Button.IsEnabledProperty,mb);

        }
    }

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

<Window x:Class="WpfApp1.MainWindow"
        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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
        <StackPanel Orientation="Vertical" 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>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值