深入浅出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>